Closed KyleAure closed 2 months ago
Java records are not valid entities. So the results are implementation specific and may vary. Try classes instead of records.
@AleksNo Same thing happens with classes (I just put in the record for brevity)
OK, i have read the description of this Exception: https://docs.oracle.com/javaee%2F6%2Fapi%2F%2F/javax/persistence/EntityExistsException.html
"Thrown by the persistence provider when EntityManager.persist(Object) is called and the entity already exists."
I guess, it is meant, that if there is already a managed entity with an ID and you try to persist a detached entity with the same ID then the exception will be thrown. But if there is no managed entity (in the EntityManager context) with the ID and you try to persist a detached entity with this ID then Eclipselink creates an INSERT statement and throws it against the database. And it fails because the database server blocks it because you cannot insert a second row with the same ID.
So in your case: use EntityManager.merge(Object) instead.
Java records are not valid entities. So the results are implementation specific and may vary. Try classes instead of records.
It is part of Jakarta Persistence 3.2 https://deploy-preview-652--jakartaee-specifications.netlify.app/specifications/persistence/3.2/jakarta-persistence-spec-3.2-draft01#a487 2.6. Embeddable Classes
Improvement into EclipseLink 5.0 is in progress https://github.com/eclipse-ee4j/eclipselink/pull/2163
But if there is no managed entity (in the EntityManager context) with the ID and you try to persist a detached entity with this ID then Eclipselink creates an INSERT statement and throws it against the database. And it fails because the database server blocks it because you cannot insert a second row with the same ID.
@AleksNo When I run this same test against Derby, DB2, or Oracle an EntityExistsException is thrown.
For more context here is the full test (from a Jakarta Data level):
Receipt r = receipts.insert(new Receipt(1200L, "C0002-12-002", 102.20f));
try {
receipts.insert(new Receipt(1200L, "C0002-10-002", 22.99f));
fail("Inserted an entity with an Id that already exists.");
} catch (EntityExistsException x) {
// expected
}
Where insert
does em.persist(r); em.flush();
I'm not an expert in Jakarta Persistence but it seems wrong for some databases to throw EntityExistsException and others throw DatabaseException.
Especially since persist(object) javadoc does not mention any other exceptions being thrown for this type of situation.
I'm working on the Jakarta Data implementation for Open Liberty and without consistent behavior from the underlying entity manager we cannot provide the correct exceptions from our layer.
When attempting to re-create this issue only using JPA API + Eclipselink I did find that in all cases Eclipselink does only throw EntityExistsException
during em.persist()
and PersistenceException
during tx.commit()
Sorry for the misunderstanding I think my original testing showed a stack trace with line numbers that were offset from my current dev environment which resulted in me thinking we were missing a catch(EntityExistsException x)
block.
Thanks for taking the time to review this issue.
Describe the bug When attempting to persist an entity that already exists in the database we are using the entity manager persist method. We attempt to catch an
EntityExistsException
, but EclipseLink throws aorg.eclipse.persistence.exceptions.DatabaseException
This seems to be contrary to the Jakarta Persistence javadoc for the persist() method.Entity:
Entity Manager Usage:
Exception Stack
``` Caused by: jakarta.persistence.PersistenceException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 4.0.2.v202306161219): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: com.microsoft.sqlserver.jdbc.SQLServerException: Violation of PRIMARY KEY constraint 'PK__WLPRecei__F32854EDD3466F3A'. Cannot insert duplicate key in object 'dbo.WLPReceiptEntity'. The duplicate key value is (1200). Error Code: 2627 Call: INSERT INTO WLPReceiptEntity (PURCHASEID, CUSTOMER, TOTAL) VALUES (?, ?, ?) bind => [3 parameters bound] Query: InsertObjectQuery(test.jakarta.data.web.ReceiptEntity@f0f03b7) at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:967) at io.openliberty.data.internal.persistence.RepositoryImpl.insert(RepositoryImpl.java:606) at io.openliberty.data.internal.persistence.RepositoryImpl.invoke(RepositoryImpl.java:762) Caused by: Exception [EclipseLink-4002] (Eclipse Persistence Services - 4.0.2.v202306161219): org.eclipse.persistence.exceptions.DatabaseException Internal Exception: com.microsoft.sqlserver.jdbc.SQLServerException: Violation of PRIMARY KEY constraint 'PK__WLPRecei__F32854EDD3466F3A'. Cannot insert duplicate key in object 'dbo.WLPReceiptEntity'. The duplicate key value is (1200). Error Code: 2627 Call: INSERT INTO WLPReceiptEntity (PURCHASEID, CUSTOMER, TOTAL) VALUES (?, ?, ?) bind => [3 parameters bound] Query: InsertObjectQuery(test.jakarta.data.web.ReceiptEntity@f0f03b7) at org.eclipse.persistence.exceptions.DatabaseException.sqlException(DatabaseException.java:334) at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:919) at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeNoSelect(DatabaseAccessor.java:981) at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:642) at org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor.executeCall(DatabaseAccessor.java:569) at org.eclipse.persistence.internal.sessions.AbstractSession.basicExecuteCall(AbstractSession.java:2048) at org.eclipse.persistence.sessions.server.ClientSession.executeCall(ClientSession.java:311) at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:280) at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.executeCall(DatasourceCallQueryMechanism.java:266) at org.eclipse.persistence.internal.queries.DatasourceCallQueryMechanism.insertObject(DatasourceCallQueryMechanism.java:436) at org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:193) at org.eclipse.persistence.internal.queries.StatementQueryMechanism.insertObject(StatementQueryMechanism.java:209) at org.eclipse.persistence.internal.queries.DatabaseQueryMechanism.insertObjectForWrite(DatabaseQueryMechanism.java:514) 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:3025) 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:1515) at org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkImpl.java:1661) at org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.writeChanges(RepeatableWriteUnitOfWork.java:472) at org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:962) Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Violation of PRIMARY KEY constraint 'PK__WLPRecei__F32854EDD3466F3A'. Cannot insert duplicate key in object 'dbo.WLPReceiptEntity'. The duplicate key value is (1200). at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:262) at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1632) at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(SQLServerPreparedStatement.java:602) at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(SQLServerPreparedStatement.java:524) at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7418) at com.microsoft.sqlserver.jdbc.SQLServerConnection.executeCommand(SQLServerConnection.java:3272) at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeCommand(SQLServerStatement.java:247) at com.microsoft.sqlserver.jdbc.SQLServerStatement.executeStatement(SQLServerStatement.java:222) at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.executeUpdate(SQLServerPreparedStatement.java:473) at com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatement.executeUpdate(WSJdbcPreparedStatement.java:522) ```To Reproduce Steps/resources to reproduce the behavior:
EclipseLink-4002
IBM Semeru Runtime Open Edition (17.0.11+9)
Entity source (mainly applied annotations)
persistence.xml
settings or related system properties (in case of JPA)Expected behavior I would expect the
persist()
method to throw an EntityExistsExceptionAdditional context It seems what makes PostgreSQL and SQLServer unique is that both JDBC drivers fail to throw a
SQLIntegrityConstraintViolationException
as specified by the JDBC 4.1 specification. Both drivers have open issues for this:But both of these drivers do not seem to be interested in implementing this exception. EclipseLink should handle this in a way that results in an EntityExistsException for consistent behavior between JDBC drivers.