eclipselink icon indicating copy to clipboard operation
eclipselink copied to clipboard

ClassCastException updating foreignkeys with identity generation

Open dazey3 opened this issue 3 years ago • 1 comments

We are seeing a ClassCastException from EclipseLink 4.0.0. Setting <property name="eclipselink.target-database-properties" value="supportsReturnGeneratedKeys=false" /> resolves the issue. It appears to be a bug in the GeneratedKeys support https://github.com/eclipse-ee4j/eclipselink/issues/1480 I added.

Entity:

@Entity
public class StepExecutionEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @ManyToOne
    @JoinColumn(name = "FK_ID")
    private StepExecutionEntity childEntity;

Test:

    EntityManager em = emf.createEntityManager();
    try {
        StepExecutionEntity e1 = new StepExecutionEntity();
        e1.setChildEntity(e1);

        em.getTransaction().begin();
        em.persist(e1);
        em.getTransaction().commit();

Exception:

Caused by: java.lang.ClassCastException: org.eclipse.persistence.queries.SQLCall incompatible with java.lang.Integer
	at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.updateObject(DatasourceCallQueryMechanism.java:905)
	at org.eclipse.persistence.internal.queries.StatementQueryMechanism.updateObject(StatementQueryMechanism.java:462)
	at org.eclipse.persistence.internal.queries.CallQueryMechanism.updateForeignKeyFieldAfterInsert(CallQueryMechanism.java:435)
	at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.updateForeignKeyFieldAfterInsert(DatabaseQueryMechanism.java:795)
	at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.insertObjectForWrite(DatabaseQueryMechanism.java:471)
	at org.eclipse.persistence.queries.InsertObjectQuery.executeCommit(InsertObjectQuery.java:86)
	at org.eclipse.persistence.queries.InsertObjectQuery.executeCommitWithChangeSet(InsertObjectQuery.java:97)
	at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.executeWriteWithChangeSet(DatabaseQueryMechanism.java:326)
	at org.eclipse.persistence.queries.WriteObjectQuery.executeDatabaseQuery(WriteObjectQuery.java:61)
	at org.eclipse.persistence.queries.DatabaseQuery.execute(DatabaseQuery.java:913)
	at org.eclipse.persistence.queries.DatabaseQuery.executeInUnitOfWork(DatabaseQuery.java:812)
	at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWorkObjectLevelModifyQuery(ObjectLevelModifyQuery.java:109)
	at org.eclipse.persistence.queries.ObjectLevelModifyQuery.executeInUnitOfWork(ObjectLevelModifyQuery.java:86)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.internalExecuteQuery(UnitOfWorkImpl.java:3008)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1841)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1823)
	at org.eclipse.persistence.internal.sessions.AbstractSession.executeQuery(AbstractSession.java:1773)
	at org.eclipse.persistence.internal.sessions.CommitManager.commitNewObjectsForClassWithChangeSet(CommitManager.java:248)
	at org.eclipse.persistence.internal.sessions.CommitManager.commitAllObjectsWithChangeSet(CommitManager.java:147)
	at org.eclipse.persistence.internal.sessions.AbstractSession.writeAllObjectsWithChangeSet(AbstractSession.java:4335)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1507)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithChangeSet(UnitOfWorkImpl.java:1597)
	at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitRootUnitOfWork(RepeatableWriteUnitOfWork.java:287)
	at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitAndResume(UnitOfWorkImpl.java:1234)
	at org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:137)

dazey3 avatar Sep 24 '22 00:09 dazey3

In this test, EclipseLink is creating an InsertObjectQuery for the new Object. However, EclipseLink also recognizes that a FK update will be needed AND because the FK Object is also new (it's the same object, which is important here) then the code enters this method:

https://github.com/eclipse-ee4j/eclipselink/blob/fd9e4ecbee675d15ac3b0a5081189e4f88cfa0e9/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/CallQueryMechanism.java#L416-L437

So, within the execution of the INSERT, EclipseLink creates a new SQLCall for the UPDATE. The issue is that the Query is STILL for the InsertObjectQuery... that means this is going to fail:

https://github.com/eclipse-ee4j/eclipselink/blob/fd9e4ecbee675d15ac3b0a5081189e4f88cfa0e9/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/CallQueryMechanism.java#L150-L167

The new SQLCall is for an UPDATE query, but this.query still refers to the InsertObjectQuery. So, shouldReturnGeneratedKeys is copied from the InsertObjectQuery to the new UPDATE SQLCall... that isn't allowed and is the reason for the failure

dazey3 avatar Sep 24 '22 00:09 dazey3