Trisfall Trisfall - 1 month ago 22
Java Question

Eclipselink merge/persist with joined objects

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);
}

Answer

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