带有JTA的JPA:持久化实体并合并级联子实体 [英] JPA with JTA: Persist entity and merge cascaded child entities
问题描述
我与以下实体类具有双向一对多关系:
I have a bidirectional one-to-many relationship with the following entity classes:
0或1客户< - > 0或更多产品订单
当持久化客户端实体时,我希望关联的产品订单实体也被保留(因为它们的父客户端的外键可能已经存在更新)。
When persisting the client entity I want the associated product order entities to be persisted, too (as their foreign key to the "parent" client may have been updated).
当然,所有必需的CASCADE选项都在客户端设置。但是如果新引用的客户端在引用现有产品订单时第一次持久化则不起作用
Of course all required CASCADE options are set on the client side. But it does not work if a newly created client is persisted for the first time while referencing an existing product order as in this scenario:
- 产品订单'1'已创建并保留。工作正常。
- 创建客户'2'并将产品订单'1'添加到其产品订单列表中。然后它坚持下去。不起作用。
我尝试了几个apporaches,但没有一个显示出预期的结果。请参阅下面的结果。我在这里阅读了所有相关问题,但他们没有帮助我。我在GlassFish 3.1.2上的Apache Derby(JavaDB)内存数据库中使用EclipseLink 2.3.0,纯JPA 2.0注释和JTA作为事务类型。实体关系由JSF GUI管理。对象级关系管理工作(除了持久化),我用JUnit测试测试它。
I tried several apporaches, but none of them showed the expected result. See those results below. I read all related questions here, but they didn't help me. I use EclipseLink 2.3.0, pure JPA 2.0 Annotations, and JTA as transaction type on an Apache Derby (JavaDB) in-memory DB on GlassFish 3.1.2. Entity relationships are managed by a JSF GUI. Object level relationship management works (apart from persisting), I tested it with JUnit tests.
方法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
}
ProductOrder:
ProductOrder:
@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()立即抛出此异常:
create() throws this Exception immediatly:
警告:在EJB
调用期间发生系统异常ClientFacade方法public void
javaee6test.beans.AbstractFacade.create(java.lang.Object)
javax.ejb.EJBException:Transaction aborted ...
Warning: A system exception occurred during an invocation on EJB ClientFacade method public void javaee6test.beans.AbstractFacade.create(java.lang.Object) javax.ejb.EJBException: Transaction aborted ...
引起的:
javax.transaction.RollbackException:标记为回滚的事务。
...
Caused by: javax.transaction.RollbackException: Transaction marked for rollback. ...
引起:异常[EclipseLink-4002](Eclipse持久性
服务 - 2.3.0.v20110604-r9504):
org.eclipse.persistence.exceptions.DatabaseException内部
例外:java.sql.SQLIntegrityConstraintViolationException:
状态已中止,因为它会导致重复键
的值唯一或主键约束或唯一索引通过'PRODUCTORDER'上定义的'SQL120513133540930'标识
。错误代码:-1
调用:INSERT INTO PRODUCTORDER(ID,CLIENT_ID)VALUES(?,?)bind =>
[2个参数绑定]查询:
InsertObjectQuery(javaee6test.model.ProductOrder [id = 1])...
Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.0.v20110604-r9504): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: java.sql.SQLIntegrityConstraintViolationException: The state-ment was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'SQL120513133540930' defined on 'PRODUCTORDER'. Error Code: -1 Call: INSERT INTO PRODUCTORDER (ID, CLIENT_ID) VALUES (?, ?) bind => [2 parameters bound] Query: InsertObjectQuery(javaee6test.model.ProductOrder[ id=1 ]) ...
引起:
java.sql.SQLIntegrityConstraintViolationException:该语句被
中止,因为它会导致由'PRO-DUCTORDER'上定义的
'SQL120513133540930'标识的唯一
或主键约束或唯一索引中的重复键值。 ...
Caused by: java.sql.SQLIntegrityConstraintViolationException: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'SQL120513133540930' defined on 'PRO-DUCTORDER'. ...
引起:
org.apache.derby.client.am.SqlException:语句已中止
因为它会已在'PRODUCTOR-DER'中定义的
'SQL120513133540930'标识的唯一或
主键约束或唯一索引中导致重复键值。
Caused by: org.apache.derby.client.am.SqlException: The statement was aborted be-cause it would have caused a duplicate key value in a unique or primary key con-straint or unique index identified by 'SQL120513133540930' defined on 'PRODUCTOR-DER'.
我不明白这个例外。编辑()工作正常。但我想在创建时向客户添加产品订单,因此这是不够的。
I don't understand this exception. edit() works fine. BUT I would like to add product orders to a client at its creation time, so this is insufficient.
方法2)仅合并()
对通用持久性外观的更改:
Changes to Generic Persistence Facade:
// 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 Logging输出显示:
On create(), the EclipseLink Logging output says:
好的:INSERT INTO CLIENT(ID,NAME,ADDRESS_ID)VALUES(?, ?,?)
bind => [3个参数绑定]
Fine: INSERT INTO CLIENT (ID, NAME, ADDRESS_ID) VALUES (?, ?, ?) bind => [3 parameters bound]
但产品订单表上没有UPDATE。因此,这种关系没有建立。同样,另一方面,edit()工作正常。
but NO "UPDATE" on the product order table. Thus, the relationship is not established. Again, edit(), on the other hand, works fine.
Apporach 3)两种实体类型的Id GenerationType.IDENTITY
对客户和产品订单类的更改:
Changes to both client and product order class:
...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
结果:
在create()上,EclipseLink Logging输出显示:
On create(), the EclipseLink Logging output says:
罚款:INSERT INTO CLIENT(NAME,ADDRESS_ID)VALUES(?, ?)bind => [2
参数绑定]
Fine: INSERT INTO CLIENT (NAME, ADDRESS_ID) VALUES (?, ?) bind => [2 parameters bound]
罚款:值IDENTITY_VAL_LOCAL()
Fine: values IDENTITY_VAL_LOCAL()
好的:INSERT INTO
PRODUCTORDER(ORDERDATE,CLIENT_ID)VALUES(?,?)bind => [2
参数绑定]
Fine: INSERT INTO PRODUCTORDER (ORDERDATE, CLIENT_ID) VALUES (?, ?) bind => [2 parameters bound]
罚款:值IDENTITY_VAL_LOCAL()
Fine: values IDENTITY_VAL_LOCAL()
因此,不是建立与添加到客户列表的产品订单的关系,而是创建一个新的产品订单实体,持久化(!)并建立与该实体的关系。同样在这里,edit()工作正常。
thus instead of estabilshing a relationship to the product order added to the client's list, a new prodcut order entity is created and persisted (!) and a relationship to that entity is estabilshed. Same here, edit() works fine.
Apporach 4)方法(2)和(3)合并
Apporach 4) Approach (2) and (3) combined
结果:与方法(2)相同。
Result: Same as Approach (2).
我的问题是:有没有办法实现上述方案?如何实现?我想继续使用JPA(没有供应商特定的解决方案)。
My question is: Is there any way to realize the scenario described above? How can it be archieved? I'd like to stay with JPA (no vendor-specific solution).
推荐答案
您好我今天遇到了同样的问题,我通过这封电子邮件向openJPA邮件列表询问:
嗨。我在同一实体中插入和更新引用时遇到问题。
Hi. I have a problem with insert and updating a reference in the same entity.
我试图插入一个新对象(Exam),它引用了另一个对象(Person)同时我想更新Person对象的属性(birthDate)。尽管我将CascadeType设置为ALL,但更新从未发生过。这种方法的唯一方法是执行持久化并在此之后执行合并操作。这是正常的吗?我是否必须改变一些东西?
Im trying to insert a new object (Exam) that has a reference to another object (Person) and at the same time i want to update an attribute (birthDate) of the Person object. The update never happens although i set CascadeType to ALL. The only way this works is doing a persist and after that a merge operation. Is this normal? Do i have to change something??
我不喜欢在Person对象中使用merge进行手动更新的想法,因为我不知道有多少对象(考试的子对象)用户想要更新。
I dont like the idea of a "manual update" using merge in the Person object because i don't know how many objects (child object of Exam) the user want to update.
实体:
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每个子对象的方法?
Do i have to use the MERGE method for every child object?
答案是这样的:
它看起来你的问题是考试是新的,但是人是
存在并且当级联持久
操作时现有实体被忽略。我相信这是按预期工作的。
It looks like your problem is that the Exam is new, yet the Person is existing and the existing Entity gets ignored when cascading the persist operation. I believe this is working as expected.
只要您的关系设置为CascadeType.ALL,您总是可以
更改您的em.persist(考试) ;到em.merge(考试);这将照顾
坚持新的考试,它也会将合并电话级联到
的人。
As long as your relationships are set to CascadeType.ALL, you could always change your em.persist(exam); to em.merge(exam);. That would take care of persisting the new exam, and it would also cascade the merge call to the person.
谢谢,
Rick
Thanks, Rick
我希望这可以帮到你。
这篇关于带有JTA的JPA:持久化实体并合并级联子实体的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!