Spring JPA / Hibernate无法保存一对多关系

时间:2018-08-05 18:21:06

标签: hibernate spring-boot jpa one-to-many many-to-one

我使用Hibernate和MySQL创建了一个小的Spring引导项目,以将我的问题隔离到一个更大的项目中,即在保存父对象时,添加到双向OneToMany关联的父列表中的对象不会被保存。尽管我在网上阅读了几篇文章,但看不到自己在做错什么。

我有一个Entity类UserRequirement。此类的实例将在特定的工作流程中进行处理。在此过程中,它将几次更改其状态(状态)。稍后,我需要评估流程进度,因此需要保留所有条件更改。

@Entity
@Access(AccessType.PROPERTY)    // JPA reading and writing attributes through their accessor getter and setter methods
public class UserRequirement implements Serializable {

    @Transient
    private static final transient Logger logger = LoggerFactory.getLogger(UserRequirement.class);

    private static final long serialVersionUID = 7664828413867109618L;

    protected Long reqId;

    @Id
    public Long getReqId() {
        return reqId;
    }

    public void setReqId(Long id) {
        this.reqId = id;
    }

    protected String what;  // what does the user wants to do

    private Product product;

    /*
     * A requirement may be broken down into sub-userRequirements, called children.
     * Between parent and child is a bi-directional relationship
     */
    protected UserRequirement parent;

    protected List<UserRequirement> children;   // sub-requirements which will provide more details

    protected List<UserRequirementStatus> conditions;   // List of all status changes the requirement went through


    protected UserRequirement() {
        children = FXCollections.observableArrayList();
        conditions = FXCollections.observableArrayList();
    }


    @ManyToOne  
    public Product getProduct() {
        return product;
    }

    public void setProduct(Product product) {
        this.product = product;
    }


    @NonNull
    public String getWhat() {
        return what;
    }

    public void setWhat(String what) {
        this.what = what;
    }

    /*
     * Parent handling
     */
    @ManyToOne  
    public UserRequirement getParent() {
        return parent;
    }

    public void setParent(UserRequirement parent) {
        this.parent = parent;
    }

    /*
     * Status handling
     */
    @OneToMany(mappedBy="parent", cascade = CascadeType.ALL) //, fetch = FetchType.LAZY)
    public List<UserRequirementStatus> getConditions() {
        return conditions;
    }

    public void setConditions(List<UserRequirementStatus> newConditions) {

        if(newConditions == null || newConditions.isEmpty()) {
            conditions.clear(); 
        } else {
            ((ObservableList<UserRequirementStatus>)conditions).setAll(newConditions);
        }
    }

    public void addCondition(UserRequirementStatus condition) {
        getConditions().add(condition);
    }

    @Transient
    protected UserRequirementStatus getCurrentCondition() {
        List<UserRequirementStatus> states = getConditions();
        if(states != null && ! states.isEmpty()) {
            return states.get(states.size()-1);
        }
        return null;
    }

    /*
     * Children Handling
     */
    @OneToMany(mappedBy="parent", cascade = CascadeType.ALL) //, fetch = FetchType.LAZY)
    public List<UserRequirement> getChildren() {
        return children;
    }

    protected void setChildren(List<UserRequirement> newChildren) {

        if(newChildren != null) {
            ((ObservableList<UserRequirement>)children).setAll(newChildren);    
        } else {
            children.clear();
        }
    }

    public void addChild(UserRequirement child) {
        getChildren().add(child);
    }

    @Override
    public String toString() {

        String condition = "";

        UserRequirementStatus state = getCurrentCondition();
        if(state != null) {
            condition = state.toString();
        }

        return getReqId() + ") " + getWhat() + ": " + condition;
    }
}

这是UserRequirementStatus类

@Entity
@Access(AccessType.PROPERTY)    // JPA reading and writing attributes through their accessor getter and setter methods
public class UserRequirementStatus implements Serializable {


    private static final long serialVersionUID = -2439050670222929991L;

    private Long id;
    private Integer version;

    private State state = State.In_Definition;
    private LocalDateTime creationTime;
    protected String justification;

    private UserRequirement parent;

    public UserRequirementStatus() {
        // JPA default constructor
    }

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    protected Long getId() {
        return id;
    }

    protected void setId(Long id) {
        this.id = id;
    }

    @Version
    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    @ManyToOne
    public UserRequirement getParent() {
        return parent;
    }

    public void setParent(UserRequirement parentalRequirement) {
        this.parent = parentalRequirement;
    }

    public State getState() {
        return state;
    }

    public void setState(State state) {
        this.state = state;
    }

    public LocalDateTime getCreationTime() {
        return creationTime;
    }

    public void setCreationTime(LocalDateTime creationTime) {
        this.creationTime = creationTime;
    }

    public String getJustification() {
        return justification;
    }

    public void setJustification(String justification) {
        this.justification = justification;
    }

    @Override
    public String toString() {
        return state.toString();
    }
}

在包含业务逻辑的RequirementService之后,介绍了将UserRequirementStatus对象添加到UserRequirement的代码

@Service
public class RequirementService {

    @Autowired  private UserRequirementStatusRepository statusRepository;
    @Autowired  private UserRequirementRepository userRequirementRepository;
    @Autowired private ProductRepository productRepository;


    public Product createProduct(String id) {
        Product product = new Product();
        product.setIdentifier(id);

        return product;
    }

    public Product save(Product product) {
        return productRepository.save(product);
    }


    protected void initRequirement(UserRequirement  req, String id) {
        req.setWhat(id);

        UserRequirementStatus status = new UserRequirementStatus();
        status.setState(State.In_Definition);
        status.setParent(req);
        req.addCondition(status);
    }

    /*
     * Create a user requirement which is attached to a product
     */
    public UserRequirement createUserRequirement(Product product, String id) {

        UserRequirement req = new UserRequirement();
        product.addRequirement(req);
        req.setProduct(product);

        initRequirement(req, id);

        return req;
    }


    /*
     * Create a user requirement which is attached to another user requirement
     */
    public UserRequirement createUserRequirement(UserRequirement parent, String id) {

        UserRequirement req = new UserRequirement();
        req.setParent(parent);
        parent.addChild(req);

        initRequirement(req, id);

        return req;
    }

    @Transactional
    public UserRequirement save(UserRequirement req) {
        return userRequirementRepository.save(req);
    }

    @Transactional
    public UserRequirementStatus save(UserRequirementStatus status) {
        return statusRepository.save(status);
    }
}

最后但并非最不重要的是,该应用程序利用CommandLineRunner创建结构并记录结果

@SpringBootApplication
public class PersistanceApplication {

    @Transient
    private static final transient Logger logger = LoggerFactory.getLogger(PersistanceApplication.class);

    @Autowired
    RequirementService requirementsService;

    public static void main(String[] args) {
        SpringApplication.run(PersistanceApplication.class, args);
    }

    @Bean
    public CommandLineRunner demo() {
        return (args) -> {

            Product product = requirementsService.createProduct("first product");
            product = requirementsService.save(product);
            logger.debug(product.toString());

            UserRequirement req  = requirementsService.createUserRequirement(product, "epic");
            logger.debug(req.toString());
            req = requirementsService.save(req);
            logger.debug(req.toString());

            req  = requirementsService.createUserRequirement(req, "theme");
            logger.debug(req.toString());
            req = requirementsService.save(req);
            logger.debug(req.toString());

        };
    }
}

这是日志输出

DEBUG 19:41 c.a.p.PersistanceApplication.lambda$0:33: 1) first product 
DEBUG 19:41 c.a.p.PersistanceApplication.lambda$0:36: null) epic: In Definition 
DEBUG 19:41 c.a.p.PersistanceApplication.lambda$0:38: 2) epic:  
DEBUG 19:41 c.a.p.PersistanceApplication.lambda$0:41: null) theme: In Definition 
DEBUG 19:41 c.a.p.PersistanceApplication.lambda$0:43: 3) theme: 

在保存之前,我没有id-这很有意义,因为该对象尚未保存,但是我有条件。保存后,我有了ID,但是丢失了条件信息。在检查MySQL数据库时,需求已保存,但相关的UserRequirementStatus表为空。未保存UserRequirementStatus记录。为什么?我的理解是,我不必显式保存附加对象。我的理解不对吗?

谢谢您的帮助,想法和澄清!

如果您还需要存储库接口,请告诉我。它们都扩展了JpaRepository。

我想我可以用这样的东西来规避我的问题

UserRequirement req  = requirementsService.createUserRequirement(product, "epic");
UserRequirement savedReq = requirementsService.save(req);
req.setReqId(savedReq.getReqId());

然后一切都会“完成”,但这不是故意的,对吧?

最后一点,在我的真实项目中,我有一个约束:因为我想在JavaFX的实体类中使用ObservableLists,所以最初我在专用的List实现方面存在问题,并且由于默认情况下使用Hibernate进行反射而注入问题。因此,我决定用@Access(AccessType.PROPERTY)注释我的Entity类,因为我想强制Hibernate使用我的getter和setter方法,而不是反射。

编辑: 我更改了UserRequirement以将PropertyAccess限制为访问列表的方法-我希望降低Hibernate的复杂性。但是,它不起作用-结果相同。 @实体 公共类UserRequirement实现了Serializable {

@Transient
private static final transient Logger logger = LoggerFactory.getLogger(UserRequirement.class);

private static final long serialVersionUID = 7664828413867109618L;

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
protected Long reqId;

public Long getReqId() {
    return reqId;
}

public void setReqId(Long id) {
    this.reqId = id;
}


protected String what;  // what does the user wants to do

@ManyToOne(fetch = FetchType.LAZY)
private Product product;

/*
 * A requirement may be broken down into sub-userRequirements, called children.
 * Between parent and child is a bi-directional relationship
 */
@ManyToOne  
protected UserRequirement parent;

@Transient
protected List<UserRequirement> children;   // sub-requirements which will provide more details

@Transient
protected List<UserRequirementStatus> conditions;   // List of all status changes the requirement went through


/* 
 * Methods
 */

public UserRequirement() {
    children = FXCollections.observableArrayList();
    conditions = FXCollections.observableArrayList();
}


public Product getProduct() {
    return product;
}

public void setProduct(Product product) {
    this.product = product;
}


public String getWhat() {
    return what;
}

public void setWhat(String what) {
    this.what = what;
}


/*
 * Parent handling
 */
public UserRequirement getParent() {
    return parent;
}

public void setParent(UserRequirement parent) {
    this.parent = parent;
}

/*
 * Status handling
 */
@Access(AccessType.PROPERTY)    // JPA reading and writing attributes through their accessor getter and setter methods
@OneToMany(mappedBy="parent", cascade = CascadeType.ALL) //, fetch = FetchType.LAZY)
public List<UserRequirementStatus> getConditions() {
    return conditions;
}

public void setConditions(List<UserRequirementStatus> newConditions) {

    if(newConditions == null || newConditions.isEmpty()) {
        conditions.clear(); 
    } else {
        ((ObservableList<UserRequirementStatus>)conditions).setAll(newConditions);
    }
}

public void addCondition(UserRequirementStatus condition) {
    getConditions().add(condition);
}

@Transient
protected UserRequirementStatus getCurrentCondition() {
    List<UserRequirementStatus> states = getConditions();
    if(states != null && ! states.isEmpty()) {
        return states.get(states.size()-1);
    }
    return null;
}



/*
 * Children Handling
 */

@Access(AccessType.PROPERTY)    // JPA reading and writing attributes through their accessor getter and setter methods
@OneToMany(mappedBy="parent", cascade = CascadeType.ALL) //, fetch = FetchType.LAZY)
public List<UserRequirement> getChildren() {
    return children;
}

protected void setChildren(List<UserRequirement> newChildren) {

    if(newChildren != null) {
        ((ObservableList<UserRequirement>)children).setAll(newChildren);
    } else {
        children.clear();
    }
}

public void addChild(UserRequirement child) {
    getChildren().add(child);
}

@Override
public String toString() {

    String condition = "";

    UserRequirementStatus state = getCurrentCondition();
    if(state != null) {
        condition = state.toString();
    }

    return getReqId() + ") " + getWhat() + ": " + condition;
}

}

2 个答案:

答案 0 :(得分:0)

问题在于UserRequirement方法的实现

public void setConditions(List<UserRequirementStatus> newConditions) {

        if(newConditions == null || newConditions.isEmpty()) {
            conditions.clear(); 
        } else {
            ((ObservableList<UserRequirementStatus>)conditions).setAll(newConditions);
        }
    }

由于您正在使用属性访问,因此应尽量避免向其添加任何逻辑,因此请尝试将其更改为

public void setConditions(List<UserRequirementStatus> newConditions) {
            conditions = newConditions;
        }

答案 1 :(得分:0)

安德里亚(Andrea)正确地发现了这个问题。解决方案很简单

public void setConditions(List<UserRequirementStatus> newConditions) {
     conditions = FXCollections.observableList(newConditions);
}