我与以下实体类具有双向一对多关系:
0 或 1 个客户<-> 0 个或多个产品订单
在持久化客户端实体时,我也希望保留关联的产品订单实体(因为它们对"父"客户端的外键可能已更新)。
当然,所有必需的级联选项都是在客户端设置的。但是,如果在引用现有产品订单时首次保留新创建的客户端,则它不起作用,如下所示:
- 创建并保留产品订单"1"。工作正常。
- 创建客户端"2",并将产品订单"1"添加到其产品订单列表中。然后它被保留。不工作。
我尝试了几种分配方法,但没有一个显示出预期的结果。请参阅下面的结果。我在这里阅读了所有相关问题,但它们对我没有帮助。我在 GlassFish 3.1.2 上的 Apache Derby (JavaDB) 内存数据库中使用 EclipseLink 2.3.0、纯 JPA 2.0 注释和 JTA 作为事务类型。实体关系由 JSF GUI 管理。对象级关系管理有效(除了持久化),我用 JUnit 测试对其进行了测试。
方法 1) "默认"(基于 NetBeans 类模板)
客户:
@Entity
public class Client implements Serializable, ParentEntity {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@OneToMany(mappedBy = "client", cascade={CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH},
fetch= FetchType.LAZY)
private List<ProductOrder> orders = new ArrayList<>();
// other fields, getters and setters
}
产品订购:
@Entity
public class ProductOrder implements Serializable, ChildEntity {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne // owning side
private Client client;
// other fields, getters and setters
}
通用持久性外观:
// Called when pressing "save" on the "create new..." JSF page
public void create(T entity) {
getEntityManager().persist(entity);
}
// Called when pressing "save" on the "edit..." JSF page
public void edit(T entity) {
getEntityManager().merge(entity);
}
结果:
create() 立即抛出此异常:
警告:在 EJB 上调用期间发生系统异常 客户端立面方法公共无效 javaee6test.beans.AbstractFacade.create(java.lang.Object) javax.ejb.EJBException: 事务中止...
引起: javax.transaction.RollbackException:标记为回滚的事务。 ...
由以下原因引起:异常 [EclipseLink-4002] (Eclipse 持久性) 服务 - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException Internal 异常:java.sql.SQLIntegrityConstraintViolationException: 状态已中止,因为它会导致重复键 唯一或主键约束或标识的唯一索引中的值 通过"产品订单"上定义的"SQL120513133540930"。错误代码:-1 调用:插入到产品订单(ID,CLIENT_ID)值(?, ?) 绑定 => [2 参数绑定]查询: InsertObjectQuery(javaee6test.model.ProductOrder[ id=1 ]) ...
引起: java.sql.SQLIntegrityConstraintViolationException:该语句是 中止,因为它会导致唯一 或主键约束或唯一索引由 "SQL120513133540930"在"PRO-DUCTORDER"上定义。...
引起: org.apache.derby.client.am.SqlException:语句已中止 因为它会导致唯一或 主键应变或唯一索引由 "SQL120513133540930"在"生产者-德尔"上定义。
我不明白这个例外。 edit() 工作正常。但是我想在创建时将产品订单添加到客户端,因此这还不够。
方法 2) 仅合并()
对通用持久性外观的更改:
// Called when pressing "save" on the "create new..." JSF page
public void create(T entity) {
getEntityManager().merge(entity);
}
// Called when pressing "save" on the "edit..." JSF page
public void edit(T entity) {
getEntityManager().merge(entity);
}
结果:
在 create() 上,EclipseLink 日志记录输出显示:
精细:插入客户端(ID、名称ADDRESS_ID)值 (?, ?, ?) 绑定 => [绑定 3 个参数]
但产品订单表上没有"更新"。因此,关系没有建立。同样,另一方面,edit()工作正常。
Apporach 3) Id GenerationType.IDENTITY 在两种实体类型上
对客户端和产品订单类的更改:
...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
结果:
在 create() 上,EclipseLink 日志记录输出显示:
精细:插入到客户端(名称、ADDRESS_ID)值 (?, ?) 绑定 => [2 参数绑定]
精细:值 IDENTITY_VAL_LOCAL()
精细:插入 产品订单(订单日期,CLIENT_ID) 值 (?, ?) 绑定 => [2 参数绑定]
精细:值 IDENTITY_VAL_LOCAL()
因此,不是稳定与添加到客户列表中的产品订单的关系,而是创建新的 Prodcut 订单实体 (!),并稳定与该实体的关系。同样在这里,edit() 工作正常。
附录 4)方法(2)和(3)相结合
结果:与方法 (2) 相同。
我的问题是:有没有办法实现上述场景?如何存档?我想继续使用 JPA(没有特定于供应商的解决方案)。
今天遇到了同样的问题,我用这封电子邮件向 openJPA 邮件列表询问:
你好。我在同一实体中插入和更新引用时遇到问题。
我试图插入一个引用另一个对象(人)的新对象(考试),同时我想更新 Person 对象的属性(出生日期)。尽管我将CascadeType设置为ALL,但更新永远不会发生。 唯一的方法是执行持久处理,然后执行合并操作。 这正常吗?我必须改变什么吗??
我不喜欢在 Person 对象中使用合并进行"手动更新"的想法,因为我不知道用户要更新多少个对象(考试的子对象)。
实体:
public class Exam{
@ManyToOne(cascade= CascadeType.ALL)
@JoinColumn(name = "person_id")
public Person person;
......
}
public class Person{
private Date birthDate;
@OneToMany(mappedBy = "person")
private List<Exam> exams
.......
}
public class SomeClass{
public void someMethod(){
exam = new Exam()
person.setBirthDate(new Date());
exam.setPerson(person);
someEJB.saveExam(exam);
}
}
public class someEJB(){
public void saveExam(Exam exam){
ejbContext.getUserTransaction().begin();
em.persist(exam);
//THIS WORKS
em.merge(exam.getPerson());
ejbContext.getUserTransaction().commit();
}
}
我是否必须对每个子对象使用 MERGE 方法?
答案是这样的:
看起来你的问题是考试是新的,但这个人是现有实体和现有实体在级联持久时被忽略操作。我相信这正在按预期工作。
只要您的关系设置为 CascadeType.ALL,您就可以始终更改您的 EM.Persist(考试);to em.merge(exam);.那会照顾保留新考试,它还会将合并调用级联到人。
谢谢瑞克
我希望这能帮助你。
确保你设置了关系的双方,你不能只向客户端添加订单,你还需要设置订单的客户端。 此外,您还需要合并已更改的两个对象。 如果您只是合并客户端,则订单的客户端将不会合并(尽管您的级联会导致它被合并)。
持久化不起作用,因为持久化要求要持久化的对象对于持久性上下文是正确的,即不引用分离的对象,它必须引用托管对象。
您的问题来自您分离对象。 如果不分离对象,则不会遇到相同的问题。 通常在 JPA 中,您将创建一个实体管理器,查找/查询您的对象,编辑/持久化它们,调用 commit。 无需合并。
@SputNick,我通过下面描述的步骤解决了它。我将使用您的代码片段来演示我所做的,
您有客户端实体 -@OneToMany 和产品订单实体 -@ManyToOne 。我会用
// Called when pressing "save" on the "edit..."
public void edit(T entity) {
getEntityManager().merge(entity);}
为了持久性,因为它可以让我灵活地保存和更新。
要创建客户和相应的产品订单,请使用
client.set..(some property to be saved);
productOrder.set..(some property to be saved);
为客户端设置产品订单
List<ProductOrder> order = new ArrayList<>();
client.setOrders(order);
//请注意,这需要传递一个列表
产品订单的设置客户端
productOrder.setClient(productOrder);
.edit(client) or .edit(productOrder)
请注意,无论哪种方式都可以使用您的更改更新两个实体表。
虽然晚了,但希望能帮助别人
在产品订单实体中使用@Joincolumn注释,请参阅下文。
@Entity
public class ProductOrder implements Serializable, ChildEntity {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@ManyToOne // owning side
@JoinColumn(name = "PRODUCTORDER_ID", referencedColumnName = "ID")
//write your database column names into name and referencedColumnName attributes.
private Client client;
// other fields, getters and setters
}