首页 > 解决方案 > Spring JPA/Hibernate 不保存 OneToMany 关系

问题描述

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

我有一个实体类 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,最初我遇到了 Hibernate 默认使用的专用 List 实现和反射注入的问题。因此我决定用@Access(AccessType.PROPERTY) 注释我的实体类,因为我想强制Hibernate 使用我的getter 和setter 方法而不是反射。

编辑: 我更改了 UserRequirement 以将 PropertyAccess 限制为访问列表的方法 - 我希望降低 Hibernate 的复杂性。但是,它不起作用 - 结果相同。@Entity 公共类 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;
}

}

标签: hibernatespring-bootjpaone-to-manymany-to-one

解决方案


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

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

推荐阅读