I have a bidirectional one-to-many relationship with the following entity classes:
0 or 1 client <-> 0 or more product orders
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).
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:
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.
Approach 1) "Default" (based on NetBeans class templates)
Client:
@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:
@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
}
Generic Persistence Facade:
// 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);
}
Result:
create() throws this Exception immediatly:
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 ...
Caused by: javax.transaction.RollbackException: Transaction marked for rollback. ...
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 ]) ...
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'. ...
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.
Approach 2) merge() only
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);
}
Result:
On create(), the EclipseLink Logging output says:
Fine: INSERT INTO CLIENT (ID, NAME, ADDRESS_ID) VALUES (?, ?, ?) bind => [3 parameters bound]
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 on both entity types
Changes to both client and product order class:
...
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
Result:
On create(), the EclipseLink Logging output says:
Fine: INSERT INTO CLIENT (NAME, ADDRESS_ID) VALUES (?, ?) bind => [2 parameters bound]
Fine: values IDENTITY_VAL_LOCAL()
Fine: INSERT INTO PRODUCTORDER (ORDERDATE, CLIENT_ID) VALUES (?, ?) bind => [2 parameters bound]
Fine: values IDENTITY_VAL_LOCAL()
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) Approach (2) and (3) combined
Result: Same as Approach (2).
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).
Hi i had the same problem today, i ask to the openJPA mailing list with this email:
Hi. I have a problem with insert and updating a reference in the same entity.
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??
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.
Entities:
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();
}
}
Do i have to use the MERGE method for every child object?
And the answer was this:
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.
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.
Thanks, Rick
I hope this can help you.