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:
- product order '1' is created and persisted. Works fine.
- client '2' is created and product order '1' is added to its product orders list. Then it is persisted. Does not work.
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).
cascade
options, and I persisted / merged "depending" entities manually, i.e. by maintaining a list of "depending" entities and merging them when the "parent" is persisted / merged. BUT I will not accept this answer as this may never be the "official JPA" apporach. Also I do not detach any entity explicitly, and EclipseLink logging doesn't mention any detach operation. I also don't understand what thesecascade
options are for, as they obviously don't work. – Reach