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!
}