Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[eclipselink-users] Changing a reference to an existing (but detached) entity causes a duplicate to be inserted

Hi everyone.

I am hoping someone can shed some light on why Eclipselink seems to not be following part of the JPA spec for flushing and handling references to detached entities.

If the format of the below description gets messed up when I send this email, feel free to check out the question I posted on Stack Overflow describing this issue - http://www.stackoverflow.com/questions/15515681/

Any help is much apreciated, thanks!

-- Ryan

------------------------

I have two entities with a uni-directional many-to-one relationship:

  • Many Bars have one Foo

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

  1. merging the detached Foo into the same Persistence Context that is used when updating bar, then
  2. 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!
    }

Back to the top