I have two entities, A and B. When I merge A, I want B to be merged as well - so I never do operations on B, just always operations on A. The B object will be accessible via the A object, but not vice versa (if possible). A and B share the same primary key field, that is, the primary key field of B is a foreign key to the primary key of A. However, I've finally got it to the point where the INSERT
queries are in the correct order, but because it's not binding the ID in query parameters, it's throwing an InsertNullViolation
as the primary key is not set. Does anyone know how to get EclipseLink to bind the primary key for the joined objects like B?
public class A {
...
@Id
@Column(name = "A_ID")
@SequenceGenerator(...)
@GeneratedValue(...)
public Long getA_ID();
@OneToOne(mappedBy = "a", targetEntity = B.class)
public B getB();
...
}
public class B {
...
@Id
public Long getA_ID();
@MapsId
@OneToOne(targetEntity = A.class)
@JoinColumn(name="A_ID")
public A getA();
...
}
The above setup does not set the primary key on B so throws an InsertNullViolation trying to insert into B.
public class A {
...
@Id
@Column(name = "A_ID")
@SequenceGenerator(...)
@GeneratedValue(...)
public Long getA_ID();
@OneToOne(mappedBy = "a", targetEntity = B.class)
public B getB();
...
}
public class B {
...
@Id
@OneToOne(targetEntity = A.class)
@JoinColumn(name="A_ID")
public A getA();
...
}
This setup also has the same issue. The exception is:
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: ORA-01400: cannot insert NULL into ("SOME_USER"."B"."A_ID")
Error Code: 1400
Call: INSERT INTO B (SOME_FIELD1, SOME_FIELD2, ..., A_ID) VALUES (?, ?, ..., ?)
bind => [null, SOME_VALUE, ..., null]
Query: InsertObjectQuery(packagename.B@ObjectId)
That last null
on the bind line needs to be filled in by EclipseLink with the actual ID that it previously retrieved from the sequence when it inserted object A.
What I'm doing for merging boils down to essentially this:
public void merge(A objectA, B objectB) {
objectA.setB(objectB);
entitymanager.merge(objectA);
}
The issue seems to be that you are not setting the A on your B.
You must maintain bi-directional relationships, there is no magic that does this for you.
When you create your A and assign the B you also must assign the A to the B, otherwise it is null, and will insert null. Thus, you should be doing this instead:
public void merge(A objectA, B objectB) {
A.setB(objectB); //so when you merge A it knows about the B objects
B.setA(objectA); //so B knows to look at the A object for it's key
entitymanager.merge(objectA);
}
Further, in your first example, you also have a duplicate A_ID field in B, so you must also set this value when setting the A of B. Ensure you have persisted A first, otherwise its Id will be null, you could avoid this by removing the A_ID and putting the @Id on the OneToOne as you have done in your second example. You could also put insert/updateable=false in the A_ID and true in the OneToOne, then the foreign key value will come from the OneToOne.
You get the multiple writable mappings error because both the A_ID and the OneToOne map the same column, you need to mark one of them insert/updateable=false.
See,
http://en.wikibooks.org/wiki/Java_Persistence/Identity_and_Sequencing#Primary_Keys_through_OneToOne_and_ManyToOne_Relationships
and,
http://en.wikibooks.org/wiki/Java_Persistence/Relationships#Object_corruption.2C_one_side_of_the_relationship_is_not_updated_after_updating_the_other_side
Here's the documentation on how to do it with Hibernate. Since it's standard JPA, you should be able to do the same thing with EclipseLink.
Either you replace the A_ID in B with a mapped OneToOne association to A (and use mappedBy
on the other side), or you keep the A_ID, but also add a OneToOne association to A and annotated it with @MapsId
(and you also use mappedBy
on the other side).
I got this error because I been doing em.persist(parent), even when I been doing parent.setChildren(child). So I had to add child.setParent(parent). But it's relative to the entity you are persisting because you also can do em.persist(child). For brevity, try to set the relation in both entities.
An extra comment, when I did what I'm telling in the previous lines then I got an "ORA-02291" error, having an inconsistence with my foreign key. It was due that I had a TRIGGER to set up the PK of my table and I was also using @SequenceGenerator in my entity. To solve this, I had to validate in my TRIGGER that only the PK can be set up to MYSEQUENCE.nexval if :new.PK_TABLE is null.
Hope this could help someone with the same trouble because I was stuck with this problem since yesterday and it was too much wasting of time.