Some Eclipse Foundation services are deprecated, or will be soon. Please ensure you've read this important communication.
Bug 331011 - @ElementCollection on Map<Primitive, @Embeddable> wrong row inserted into collection table
Summary: @ElementCollection on Map<Primitive, @Embeddable> wrong row inserted into col...
Status: NEW
Alias: None
Product: z_Archived
Classification: Eclipse Foundation
Component: Eclipselink (show other bugs)
Version: unspecified   Edit
Hardware: All All
: P2 normal (vote)
Target Milestone: ---   Edit
Assignee: Nobody - feel free to take it CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2010-11-24 08:53 EST by Ronny Völker CLA
Modified: 2022-06-09 10:26 EDT (History)
2 users (show)

See Also:


Attachments
test case (5.94 KB, application/x-sdlc)
2010-11-24 08:55 EST, Ronny Völker CLA
no flags Details

Note You need to log in before you can comment on or make changes to this bug.
Description Ronny Völker CLA 2010-11-24 08:53:13 EST
Build Identifier: 2.1.1

An attribute in an Entity is of the type Map<Primitive, @Embeddable>.
It contains two elements where the values are equal.
The map is in sync with the DB (Oracle in this case), so the collection table contains two rows.

One of the two existing map-values is changed or replaced by the program.
When flushing the changes to DB, EclipseLink issues just one insert-command, instead of an update or a delete/insert-sequence.

As a result the collection contains two elements, which is correct, but the collection table contains three rows.



Reproducible: Always

Steps to Reproduce:
1. unzip the Attachment.
2. adjust src/META-INF/persistence.xml.
3. run maven.
4. run Test.class
5. inspect the DB.
Comment 1 Ronny Völker CLA 2010-11-24 08:55:33 EST
Created attachment 183752 [details]
test case
Comment 2 Tom Ware CLA 2010-12-09 09:02:18 EST
Setting target and priority.  See the following page for the meanings of these fields:

http://wiki.eclipse.org/EclipseLink/Development/Bugs/Guidelines
Comment 3 Ronny Völker CLA 2012-04-11 07:20:31 EDT
The Bug is independent of the type of the Map-Key and Map-Value.
Eg. a Map<@Entity, @Embeddable> has the same behaviour.

A workaround for the original case is, to put the value of the key into an own field in the Embeddable (and using @MapKey). After that, there never are two equal values in the Map, because the values always differ at least in the key-field.
Comment 4 Will Dazey CLA 2021-09-21 16:00:44 EDT
Nearly a decade after this issue was posted, I think I am hitting this same issue.

---Usecase---

```
@Entity
public class SimpleEntity {
    @Id private int id;

    @ElementCollection(fetch = FetchType.EAGER)
    @CollectionTable(name = "EntMapIntegerInteger", joinColumns = @JoinColumn(name = "parent_id"))
    @MapKeyColumn(name = "mykey")
    private Map<Integer, SimpleIntegerEmbed> mapKeyIntegerValueIntegerEmbed;
}

@Embeddable
public class SimpleTemporalEmbed {
    private Integer integerValueColumn;
}
```

Test:
```
    Map<Integer, SimpleIntegerEmbed> map1 = new HashMap<Integer, SimpleIntegerEmbed>();
    map1.put(new Integer(1), new SimpleIntegerEmbed(new Integer(2)));
    map1.put(new Integer(2), new SimpleIntegerEmbed(new Integer(1)));

    SimpleEntity newEntity = new SimpleEntity(5, map1);
    em.persist(newEntity);

    // T1 commit, T2 start

    Map<Integer, SimpleIntegerEmbed> map2 = new HashMap<Integer, SimpleIntegerEmbed>();
    map2.put(new Integer(0), new SimpleIntegerEmbed(new Integer(2)));
    map2.put(new Integer(1), new SimpleIntegerEmbed(new Integer(0)));
    map2.put(new Integer(2), new SimpleIntegerEmbed(new Integer(1)));

    SimpleEntity findEntity = em.find(SimpleEntity.class, 5);
    findEntity.setMapKeyIntegerValueIntegerEmbed(map2);

    // T2 commit
```

Output:
```
INSERT INTO EntMapIntegerInteger (parent_id, mykey, INTEGERVALUECOLUMN) VALUES (?, ?, ?)
	bind => [5, 2, 1]
INSERT INTO EntMapIntegerInteger (parent_id, mykey, INTEGERVALUECOLUMN) VALUES (?, ?, ?)
	bind => [5, 1, 2]

// T1 commit, T2 start

INSERT INTO EntMapIntegerInteger (parent_id, mykey, INTEGERVALUECOLUMN) VALUES (?, ?, ?)
	bind => [5, 1, 0]

// T2 commit
```
Comment 5 Will Dazey CLA 2021-09-21 17:29:34 EDT
It seems the story starts in `org.eclipse.persistence.mappings.CollectionMapping.compareObjectsAndWrite(WriteObjectQuery query)` where EclipseLink calculates the differences on merge/update. 

In this method, EclipseLink iterates over the "currentObjects" and the "previousObjects" in order to compare the differences and formulate the necessary queries (INSERT/UPDATE/DELETE). For the above usecase, both current/previousObjects are HashMaps.

    currentObjects: {2=SimpleIntegerEmbed(1), 1=SimpleIntegerEmbed(2)}
    previousObjects: {}

This is a sample of what these objects look like for the first transaction commit (T1). You can see the previousObjects are empty and both INSERTs are generated because of this. This looks correct so far, but there isn't really much to compare here since the previousObjects is empty.

---

    currentObjects: {2=SimpleIntegerEmbed(1), 1=SimpleIntegerEmbed(0), 0=SimpleIntegerEmbed(2)}
    previousObjects: {2=SimpleIntegerEmbed(1), 1=SimpleIntegerEmbed(2)}

This is a sample of what these objects look like for the second transaction commit (T2). This looks right so far, unfortunately, these Maps are compared against each other and only 1 INSERT is determined, with 0 UPDATEs. And the 1 INSERT is determined to be the "1=SimpleIntegerEmbed(0)" element, which should have been an UPDATE, not an INSERT.

From `CollectionMapping.compareObjectsAndWrite()`, the ObjectBuilder attempts to get the primary keys for the SimpleIntegerEmbed objects. For this, `org.eclipse.persistence.internal.descriptors.ObjectBuilder.extractPrimaryKeyFromObject()` is called and it is here where the issues start.

When passing in a SimpleIntegerEmbed object, `ObjectBuilder.extractPrimaryKeyFromObject()` first gets the descriptor, then gets the PrimaryKeyFields. These are the "primaryKeyFields"

    [EntMapIntegerInteger.INTEGERVALUECOLUMN, EntMapIntegerInteger.parent_id, EntMapIntegerInteger.mykey]

"INTEGERVALUECOLUMN" seems out of place; that field isn't a "primary key". Then, `ObjectBuilder.getPrimaryKeyMappings()` is called, which returns the following "Primary Key Mappings":

    [org.eclipse.persistence.mappings.DirectToFieldMapping[integerValueColumn-->EntMapIntegerInteger.INTEGERVALUECOLUMN], null, null]

This is the reason that the wrong INSERTs and UPDATEs are created. The comparison that formulates the differences is using the INTEGERVALUECOLUMN field in the Embeddable object as the primary key! This is causing the code to make decisions based on the assumption that the value of INTEGERVALUECOLUMN is unique! Which is wrong and leads the wrong objects being looked up.
Comment 6 Will Dazey CLA 2021-09-21 19:47:15 EDT
Hmm. Unfortunately, it seems deeply ingrained in the code that Embeddable objects have primary key fields: the attributes of the Embeddable... this just seems wrong conceptually, but it's how `ObjectBuilder.extractPrimaryKeyFromObject(Object domainObject, AbstractSession session, boolean shouldReturnNullIfNull)` gets away with passing Embeddable domainObjects.
Comment 7 Eclipse Webmaster CLA 2022-06-09 10:26:44 EDT
The Eclipselink project has moved to Github: https://github.com/eclipse-ee4j/eclipselink