hibernate - 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;
}
}
解决方案
安德里亚正确地发现了这个问题。解决方案很简单
public void setConditions(List<UserRequirementStatus> newConditions) {
conditions = FXCollections.observableList(newConditions);
}
推荐阅读
- c# - Autofac System.ArgumentNullException:值不能为空。参数名称:上下文
- ruby - 抓取嵌套跨度 Nokogiri
- ios - Cocoapods 错误 v0.9.0
- scala - Scala Variance Concept,为什么它不能编译
- java - 应用程序在调试中立即终止
- php - $this->Auth->identify(); 在 cakephp 3.6.6 中总是返回假值
- docker - 运行 composer install 后 Docker 容器以代码 0 退出
- haskell - 暧昧的出现秀
- java - 停止从 Firebase 数据库刷新
- java - jstl c:set 不适用于 jsp 文件中的 js 代码