我使用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;
}
}
答案 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);
}