I have two entities with a uni-directional many-to-one relationship:
When I try to update a managed Bar instance by changing its Foo to an existing detached Foo instance, the flush to the DB fails.
I use facades (FooFacade & BarFacade) to create and modify the entities. Here is my test code:
public void test() {
FooFacade fooFacade = (FooFacade) lookupEJB(FooFacade.class);
BarFacade barFacade = (BarFacade) lookupEJB(BarFacade.class);
Foo originalFoo = fooFacade.createFoo(); // returned entity is detatched
Foo newFoo = fooFacade.createFoo(); // returned entity is detatched
Bar bar = barFacade.createBar(originalFoo); // returned entity is detatched
barFacade.changeFoo(bar.getId(), newFoo);
}
This code will cause the following SQL error:
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 'SQL130321134048610' defined on 'FOO'.
Eclipselink is trying to insert newFoo when flushing the change of bar, even though it already exists!
However, the fact that the newFoo instance is detached should make no difference - see the relevent part of the EJB persistence spec (section 3.2.3, page 50) in bold below:
The semantics of the flush operation, applied to an entity X are as
follows:
- If X is a managed entity, it is synchronized to the database.
- For all entities Y referenced by a relationship from X, if the
relationship to Y has been annotated with the cascade element value
cascade=PERSIST or cascade= ALL, the persist operation is applied to
Y.
- For any entity Y referenced by a relationship from X, where the
relationship to Y has not been annotated with the cascade element
value cascade=PERSIST or cascade= ALL:
- If Y is new or removed, an IllegalStateException will be thrown by the flush operation (and the transaction rolled back) or the
transaction commit will fail.
- If Y is detached, the semantics depend upon the ownership of the relationship. If X owns the relationship, any changes to the
relationship are synchronized with the database; otherwise, if Y owns
the relationships, the behavior is undefined.
- If X is a removed entity, it is removed from the database. No cascade options are relevant.
Can anybody help explain why this doesn't seem to work? I cannot find
an existing eclipselink bug report. I have some code below, and the
technologies are:
- EclipseLink (testing using the embedded Glassfish EJB container)
- MS SQL
Thanks!
Please note: I understand that I could make this work by
- merging the detached
Foo into the same Persistence Context that is used when updating bar, then - setting that merged instance of
foo on the bar.
However, this is not the approach I wish to take in my application.
Entities
@Entity
@TableGenerator(name="test_generator", table="SEQUENCE", pkColumnName="SEQ_NAME", valueColumnName="SEQ_VALUE", pkColumnValue="TEST_SEQUENCE")
public class Foo {
@Id
@GeneratedValue(strategy=GenerationType.TABLE, generator="test_generator")
private int id;
public int getId() {return this.id;}
public void setId(int id) {this.id = id;}
}
and
@Entity
public class Bar {
@Id
@GeneratedValue(strategy=GenerationType.TABLE, generator="test_generator")
private int id;
@ManyToOne
private Foo foo;
public int getId() {return this.id;}
public void setId(int id) {this.id = id;}
public Foo getFoo() {return this.foo;}
public void setFoo(Foo foo) {this.foo = foo;}
}
The Facades
@Singleton
@LocalBean
public class FooFacade {
@PersistenceContext(unitName="CurriculumManagementSystem")
private EntityManager em;
public Foo createFoo() {
Foo newFoo = new Foo();
em.persist(newFoo);
return newFoo;
}
}
and
@Singleton
@LocalBean
public class BarFacade {
@PersistenceContext(unitName="CurriculumManagementSystem")
private EntityManager em;
public Bar createBar(Foo parent) {
Bar newBar = new Bar();
newBar.setFoo(parent);
em.persist(newBar);
return newBar;
}
public void changeFoo(int barID, Foo detachedFoo) {
Bar bar = (Bar) em.find(Bar.class, barID);
bar.setFoo(detachedFoo);
// when this method exits, the transaction completes
// and any changes are flushed to the DB.
// instead of only updating the Bar table by changing the
// foreign key reference to the detachedFoo's ID, elipselink
// tries to insert the detachedFoo as a new row in the
// Foo table!
}