Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.

Bug 324941

Summary: Cascade Fails: OneToMany Bidirectional, CascadeType.ALL: Detached children not persisted in attached or not yet persisted parent
Product: z_Archived Reporter: J H <jhasenbe>
Component: EclipselinkAssignee: Project Inbox <eclipselink.orm-inbox>
Status: NEW --- QA Contact:
Severity: critical    
Priority: P2 CC: christopher.delahunt, rolf.paulsen, tom.ware
Version: unspecified   
Target Milestone: ---   
Hardware: PC   
OS: Windows XP   
Whiteboard:
Attachments:
Description Flags
Test Case none

Description J H CLA 2010-09-10 06:06:48 EDT
Build Identifier: 2.1.1.v20100817-r8050

I have a simple OneToMany bidirectional mapping of Person to Apartments: 
Persons may reside in zero or many Apartments, an Apartment is inhabited by zero or one person (see code below and attached test case).

When adding a detached Apartment entity to an either a) not yet managed Person entity or b) managed Person entity, 
and calling em.merge(Person), EclipseLink fails to correctly update the Apartment association (i.e. the ForeignKey in Apartment pointing to Person) for detached Apartments. 

The case succeeds only when a) Person as well as Apartment are detached entities, b) when Apartment is already managed or c) both entities are not yet persisted. 

The mapping classes are like this:

@Entity
public class Person implements Serializable {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private Long id;
    
    private String name;
    
    // Bidirectional 
    @OneToMany(
            mappedBy="tenant", 
            fetch=FetchType.EAGER,
            cascade=CascadeType.ALL,  
            orphanRemoval=true)
    private Set<Apartment> apartments = new HashSet<Apartment>();
    
    @Version
    @SuppressWarnings("unused")
    private Timestamp version;

    ...
        
    public Collection<Apartment> getApartments() {
        return apartments;
    }

    public void addApartment(Apartment apartment) {
        this.getApartments().add(apartment);
        apartment.setTenant(this);
    }
}

@Entity
public class Apartment {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    @SuppressWarnings("unused")
    private Long id;

    @Version
    @SuppressWarnings("unused")
    private Timestamp version;
    
    @ManyToOne(fetch=FetchType.EAGER, optional=true)
    private Person tenant;

	  ...    

    public void setTenant(Person tenant) {
        this.tenant = tenant;
    }

    public Person getTenant() {
        return tenant;
    }
}


The test code is doing something like this (also compare attached test case):
    @Test
    public void testPersistPersonWithDetachedApartment() {
        
        // persist an apartment first (assuming we have existing apartments)
        Apartment apartment1 = new Apartment();
        apartment1.setDescription("Apartment in San Francisco");
        
        EntityTransaction tx = em.getTransaction();
        
        tx.begin();
        em.persist(apartment1);
        tx.commit();
        
        em.clear(); // clearing will detach apartment
        
        // create a new person and and assign it the apartment
        Person person1 = new Person();
        person1.setName("Jo");
        person1.addApartment(apartment1);
        
        // try to merge person now
        tx.begin();
        person1 = em.merge(person1);
        tx.commit();

        em.clear(); // clear persistence context
        
        // re-read person and check apartment
        person1 = em.find(Person.class, person1.getId());
        Assert.assertEquals(person1.getApartments().size(), 1);      
        // Fails for EL 2.1.1.v20100817-r8050
    }

The EL SQL trace shows that APARTMENT is not updated (even so there is a check for existence of APARTMENT instance (SELECT ID FROM APARTMENT WHERE (ID = ?)):

[EL Config]: 2010-09-10 11:52:26.203--ServerSession(4469286)--Thread(Thread[main,5,main])--The access type for the persi
stent class [class mytest.Person] is set to [FIELD].
[EL Config]: 2010-09-10 11:52:26.265--ServerSession(4469286)--Thread(Thread[main,5,main])--The target entity (reference)
 class for the one to many mapping element [field apartments] is being defaulted to: class mytest.Apartment.
[EL Config]: 2010-09-10 11:52:26.281--ServerSession(4469286)--Thread(Thread[main,5,main])--The access type for the persi
stent class [class mytest.Apartment] is set to [FIELD].
[EL Config]: 2010-09-10 11:52:26.281--ServerSession(4469286)--Thread(Thread[main,5,main])--The target entity (reference)
 class for the many to one mapping element [field tenant] is being defaulted to: class mytest.Person.
[EL Config]: 2010-09-10 11:52:26.281--ServerSession(4469286)--Thread(Thread[main,5,main])--The alias name for the entity
 class [class mytest.Person] is being defaulted to: Person.
[EL Config]: 2010-09-10 11:52:26.281--ServerSession(4469286)--Thread(Thread[main,5,main])--The table name for entity [cl
ass mytest.Person] is being defaulted to: PERSON.
[EL Config]: 2010-09-10 11:52:26.312--ServerSession(4469286)--Thread(Thread[main,5,main])--The column name for element [
field id] is being defaulted to: ID.
[EL Config]: 2010-09-10 11:52:26.312--ServerSession(4469286)--Thread(Thread[main,5,main])--The column name for element [
field name] is being defaulted to: NAME.
[EL Config]: 2010-09-10 11:52:26.312--ServerSession(4469286)--Thread(Thread[main,5,main])--The column name for element [
field version] is being defaulted to: VERSION.
[EL Config]: 2010-09-10 11:52:26.312--ServerSession(4469286)--Thread(Thread[main,5,main])--The alias name for the entity
 class [class mytest.Apartment] is being defaulted to: Apartment.
[EL Config]: 2010-09-10 11:52:26.312--ServerSession(4469286)--Thread(Thread[main,5,main])--The table name for entity [cl
ass mytest.Apartment] is being defaulted to: APARTMENT.
[EL Config]: 2010-09-10 11:52:26.312--ServerSession(4469286)--Thread(Thread[main,5,main])--The column name for element [
field id] is being defaulted to: ID.
[EL Config]: 2010-09-10 11:52:26.312--ServerSession(4469286)--Thread(Thread[main,5,main])--The column name for element [
field description] is being defaulted to: DESCRIPTION.
[EL Config]: 2010-09-10 11:52:26.312--ServerSession(4469286)--Thread(Thread[main,5,main])--The column name for element [
field version] is being defaulted to: VERSION.
[EL Config]: 2010-09-10 11:52:26.343--ServerSession(4469286)--Thread(Thread[main,5,main])--The primary key column name f
or the mapping element [field tenant] is being defaulted to: ID.
[EL Config]: 2010-09-10 11:52:26.343--ServerSession(4469286)--Thread(Thread[main,5,main])--The foreign key column name f
or the mapping element [field tenant] is being defaulted to: TENANT_ID.
[EL Config]: 2010-09-10 11:52:26.359--ServerSession(4469286)--Thread(Thread[main,5,main])--Class mytest.Person could not
 be weaved for change tracking as it is not supported by its mappings.
[Parser] Running:
  C:\Documents and Settings\JH\Local Settings\Temp\testng-eclipse\testng-customsuite.xml

[EL Info]: 2010-09-10 11:52:27.046--ServerSession(4469286)--Thread(Thread[main,5,main])--EclipseLink, version: Eclipse P
ersistence Services - 2.1.1.v20100817-r8050
[EL Fine]: 2010-09-10 11:52:27.14--Thread(Thread[main,5,main])--Detected Vendor platform: org.eclipse.persistence.platfo
rm.database.HSQLPlatform
[EL Config]: 2010-09-10 11:52:27.156--ServerSession(4469286)--Connection(30969271)--Thread(Thread[main,5,main])--connect
ing(DatabaseLogin(
	platform=>HSQLPlatform
	user name=> "sa"
	datasource URL=> "jdbc:hsqldb:mem:test;shutdown=true"
))
[EL Config]: 2010-09-10 11:52:27.171--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--Connect
ed: jdbc:hsqldb:mem:test;shutdown=true
	User: SA
	Database: HSQL Database Engine  Version: 1.8.0
	Driver: HSQL Database Engine Driver  Version: 1.8.0
[EL Info]: 2010-09-10 11:52:27.218--ServerSession(4469286)--Thread(Thread[main,5,main])--MpJPA-Test login successful
[EL Fine]: 2010-09-10 11:52:27.234--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--CREATE TA
BLE APARTMENT (ID BIGINT NOT NULL, DESCRIPTION VARCHAR, VERSION TIMESTAMP, TENANT_ID BIGINT, PRIMARY KEY (ID))
[EL Fine]: 2010-09-10 11:52:27.25--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--CREATE TAB
LE PERSON (ID BIGINT NOT NULL, NAME VARCHAR, VERSION TIMESTAMP, PRIMARY KEY (ID))
[EL Fine]: 2010-09-10 11:52:27.25--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--ALTER TABL
E APARTMENT ADD CONSTRAINT FK_APARTMENT_TENANT_ID FOREIGN KEY (TENANT_ID) REFERENCES PERSON (ID)
[EL Fine]: 2010-09-10 11:52:27.25--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--CREATE TAB
LE SEQUENCE (SEQ_NAME VARCHAR(50) NOT NULL, SEQ_COUNT NUMERIC(38), PRIMARY KEY (SEQ_NAME))
[EL Fine]: 2010-09-10 11:52:27.25--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--SELECT * F
ROM SEQUENCE WHERE SEQ_NAME = SEQ_GEN
[EL Fine]: 2010-09-10 11:52:27.25--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--INSERT INT
O SEQUENCE(SEQ_NAME, SEQ_COUNT) values (SEQ_GEN, 0)
[EL Fine]: 2010-09-10 11:52:27.312--ClientSession(14342692)--Connection(20039836)--Thread(Thread[main,5,main])--UPDATE S
EQUENCE SET SEQ_COUNT = SEQ_COUNT + ? WHERE SEQ_NAME = ?
	bind => [50, SEQ_GEN]
[EL Fine]: 2010-09-10 11:52:27.312--ClientSession(14342692)--Connection(20039836)--Thread(Thread[main,5,main])--SELECT S
EQ_COUNT FROM SEQUENCE WHERE SEQ_NAME = ?
	bind => [SEQ_GEN]
[EL Fine]: 2010-09-10 11:52:27.312--ClientSession(14342692)--Connection(20039836)--Thread(Thread[main,5,main])--CALL CUR
RENT_TIMESTAMP
[EL Fine]: 2010-09-10 11:52:27.312--ClientSession(14342692)--Connection(20039836)--Thread(Thread[main,5,main])--INSERT I
NTO APARTMENT (ID, DESCRIPTION, VERSION, TENANT_ID) VALUES (?, ?, ?, ?)
	bind => [1, Apartment in San Francisco, 2010-09-10 11:52:27.312, null]
[EL Fine]: 2010-09-10 11:52:27.328--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--SELECT ID
 FROM APARTMENT WHERE (ID = ?)
	bind => [1]
[EL Fine]: 2010-09-10 11:52:27.328--ClientSession(4641320)--Connection(20039836)--Thread(Thread[main,5,main])--CALL CURR
ENT_TIMESTAMP
[EL Fine]: 2010-09-10 11:52:27.328--ClientSession(4641320)--Connection(20039836)--Thread(Thread[main,5,main])--INSERT IN
TO PERSON (ID, NAME, VERSION) VALUES (?, ?, ?)
	bind => [2, Jo, 2010-09-10 11:52:27.328]
[EL Fine]: 2010-09-10 11:52:27.343--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--SELECT ID
, NAME, VERSION FROM PERSON WHERE (ID = ?)
	bind => [2]
[EL Fine]: 2010-09-10 11:52:27.343--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--SELECT ID
, DESCRIPTION, VERSION, TENANT_ID FROM APARTMENT WHERE (TENANT_ID = ?)
	bind => [2]
[EL Config]: 2010-09-10 11:52:27.343--ServerSession(4469286)--Connection(20039836)--Thread(Thread[main,5,main])--disconn
ect
[EL Info]: 2010-09-10 11:52:27.343--ServerSession(4469286)--Thread(Thread[main,5,main])--MpJPA-Test logout successful
[EL Config]: 2010-09-10 11:52:27.343--ServerSession(4469286)--Connection(30969271)--Thread(Thread[main,5,main])--disconn
ect
FAILED: testPersistPersonWithDetachedApartment
java.lang.AssertionError: expected:<1> but was:<0>
	at mytest.MyTest.testPersistPersonWithDetachedApartment(MyTest.java:84)
... Removed 27 stack frames











Reproducible: Always

Steps to Reproduce:
Run provided test cases (Maven project, TestNG test
Comment 1 J H CLA 2010-09-10 06:09:37 EDT
Created attachment 178602 [details]
Test Case

The attached test case can be run as either Maven project or imported into Eclipse IDE (Helios)
Comment 2 J H CLA 2010-09-10 06:17:40 EDT
On a side note:
The issue came up when we wanted re-create a scenario where EL tries to persist detached entities that are associated with a new entity (calling merge() on the new entity). This then lead to a constraint violation in the database (detached entity already exist).
Comment 3 Tom Ware CLA 2010-09-20 08:49:48 EDT
Setting target and priority.  See the following page for details of the meanings of these fields:

http://wiki.eclipse.org/EclipseLink/Development/Bugs/Guidelines
Comment 4 Tom Ware CLA 2010-10-07 13:58:25 EDT
You may be able to workaround this by setting orphanRemoval=false
Comment 5 J H CLA 2010-10-08 05:53:23 EDT
(In reply to comment #4)
> You may be able to workaround this by setting orphanRemoval=false

Thanks for the information. 
However, our application has many mappings where orphanRemoval is required and we cannot omit it.
Comment 6 Chris Delahunt CLA 2010-10-15 16:02:44 EDT
The test case provided has nothing to do with orphan removal. Instead, it is has to do with calling merge on new objects that reference existing objects (a  side effects of the poorly described behavior in bug 247662).  

The problem is do to a combination of a few reasons.  First, the EntityManager is being cleared, causing the existing Apartment to be removed.  When merge is then called on Person that references the existing Apartment, it causes a RegisterObject api to be used on Person and all its references.  RegisterObject api is not appropriate here, as it cascades to the existing Apartment - the side effect of this is that any changes made to the Appartment are not picked up.  This is why there is no update statement in the commit, and later why when the Person is read back in it only references the new Appartment instead of the previous existing one.  

This is the result of calling merge on a new object.  Workarounds are to read in the existing Appartment into the EntityManger before the merge call on Person, or to call merge on the existing Appartment directly (and set the cascade merge or persist on the Appartment->Person relationship).

I will look into potential fixes, but this issue will require not insignificant changes that will not be able to put in the 2.1.2 release.
Comment 7 Chris Delahunt CLA 2010-10-15 16:12:57 EDT
Putting to future because there is an available workaround.
Comment 8 J H CLA 2010-10-19 03:16:55 EDT
Thank you for adding explanations to this bug and for the proposed workaround (which is in my opinion not a workaround, see below).

We have already implied such workaround in parts of our code. However, it's not a practical solution and is very hard (to almost impossible) to make it work in a general manner.
Your workaround assumes that business logic that operates on JPA objects knows about the persistence mechanism of the JPA objects, especially persistence operation cascading and persistence state of objects. Each use case would need to take that into account and integrate logic for checking or updating persistence state of objects (e.g. for correct collection handling).  This is exposing persistence mechanism internals to business logic. 
The problem becomes harder, so more complex the JPA object graph is and when the JPA objects had become detached during an operation (e.g. when using JPA objects in a Servlet Session that is not associated with a JTA Transaction anymore). One needs to assure that all objects participating in the graph are attached or have otherwise the correct/expected state, before we can safely call persistence operations. This is almost as complex as building our own persistence framework.

I therefore urge you to fix this bug as soon as possible. I can see that the problem is not easy to fix, but the described workaround is in my opinion not practical and puts all the burden to the users of the persistence framework. 

Thanks!
Comment 9 Eclipse Webmaster CLA 2022-06-09 10:09:48 EDT
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink