diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java
index d2c092ac719..6f09ab6484a 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/config/PersistenceUnitProperties.java
@@ -1,6 +1,6 @@
/*
- * Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 1998, 2021 IBM Corporation. All rights reserved.
+ * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -1868,6 +1868,13 @@ public class PersistenceUnitProperties {
*
* {@code
*
+ * Example 2 : To change the value of
+ * DatabasePlatform.supportsReturnGeneratedKeys via configuration, provide the
+ * following :
+ *
+ * {@code
+ *
+ * Only set to true if {@link DatabasePlatform#supportsReturnGeneratedKeys()} + */ + public boolean setShouldReturnGeneratedKeys(boolean shouldReturnGeneratedKeys) { + return this.shouldReturnGeneratedKeys = shouldReturnGeneratedKeys; + } + /** * Callable statement is required if there is an output parameter. */ @@ -967,6 +990,13 @@ public void setQueryTimeoutUnit(TimeUnit queryTimeoutUnit) { this.queryTimeoutUnit = queryTimeoutUnit; } + /** + * The result set that stores the generated keys from the Statement + */ + public void setGeneratedKeys(ResultSet generatedKeys) { + this.generatedKeys = generatedKeys; + } + /** * The result set is stored for the return value of cursor selects. */ @@ -1085,6 +1115,13 @@ public boolean shouldIgnoreMaxResultsSetting(){ return this.ignoreMaxResultsSetting; } + /** + * Indicate that this call should set {@link java.sql.Statement#RETURN_GENERATED_KEYS} when executing + */ + public boolean shouldReturnGeneratedKeys() { + return this.shouldReturnGeneratedKeys; + } + /** * INTERNAL: * Print the SQL string. diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java index c374f6a2c06..3e6131bf49c 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java @@ -1,6 +1,6 @@ /* * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, 2020 IBM Corporation. All rights reserved. + * Copyright (c) 2019, 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -98,10 +98,28 @@ public class DatasourcePlatform implements Platform { /** If sequences should start at Next Value */ protected boolean defaultSeqenceAtNextValue; + /** + * This property configures if the database platform will use {@link java.sql.Statement#getGeneratedKeys()}, + * or a separate query, in order to obtain javax.persistence.GenerationType.IDENTITY generated values. + *
+ * Allowed Values: + *
true
" - IDENTITY generated values will be obtained with {@link java.sql.Statement#getGeneratedKeys()}
+ * false
" (DEFAULT) - IDENTITY generated values will be obtained with a separate query {@link #buildSelectQueryForIdentity()}
+ * + * See: + *
+ * Alternatively, if the platform supports {@link java.sql.Statement#getGeneratedKeys()}, + * see {@link DatabasePlatform#supportsReturnGeneratedKeys()} */ public ValueReadQuery buildSelectQueryForIdentity() { return null; diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DynamicSQLBatchWritingMechanism.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DynamicSQLBatchWritingMechanism.java index 26115d2ebb4..1edd1c21956 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DynamicSQLBatchWritingMechanism.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/DynamicSQLBatchWritingMechanism.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -150,9 +151,9 @@ private void executeBatch(AbstractSession session) { if (this.sqlStrings.size() == 1) { // If only one call, just execute normally. try { - int rowCount = (Integer)this.databaseAccessor.basicExecuteCall(this.lastCallAppended, null, session, false); - if (this.usesOptimisticLocking) { - if (rowCount != 1) { + Object rowCount = this.databaseAccessor.basicExecuteCall(this.lastCallAppended, null, session, false); + if (this.usesOptimisticLocking && rowCount instanceof Integer) { + if ((Integer)rowCount != 1) { throw OptimisticLockException.batchStatementExecutionFailure(); } } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/ParameterizedSQLBatchWritingMechanism.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/ParameterizedSQLBatchWritingMechanism.java index ecae2073806..e3203fba880 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/ParameterizedSQLBatchWritingMechanism.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/databaseaccess/ParameterizedSQLBatchWritingMechanism.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -151,9 +152,9 @@ private void executeBatch(AbstractSession session) { if (this.parameters.size() == 1) { // If only one call, just execute normally. try { - int rowCount = (Integer)this.databaseAccessor.basicExecuteCall(this.previousCall, null, session, false); - if (this.previousCall.hasOptimisticLock()) { - if (rowCount != 1) { + Object rowCount = this.databaseAccessor.basicExecuteCall(this.previousCall, null, session, false); + if (this.previousCall.hasOptimisticLock() && rowCount instanceof Integer) { + if ((Integer)rowCount != 1) { throw OptimisticLockException.batchStatementExecutionFailure(); } } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/descriptors/ObjectBuilder.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/descriptors/ObjectBuilder.java index 88694f8f074..6dfc8b76588 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/descriptors/ObjectBuilder.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/descriptors/ObjectBuilder.java @@ -1,6 +1,6 @@ /* * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 1998, 2021 IBM Corporation. All rights reserved. + * Copyright (c) 1998, 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -390,7 +390,7 @@ protected void assignReturnValueToMapping(Object object, ReadObjectQuery query, * @exception DatabaseException - an error has occurred on the database. */ public Object assignSequenceNumber(Object object, AbstractSession writeSession) throws DatabaseException { - return assignSequenceNumber(object, writeSession, null); + return assignSequenceNumber(object, null, writeSession, null); } /** @@ -404,7 +404,21 @@ public Object assignSequenceNumber(Object object, AbstractSession writeSession) * @exception DatabaseException - an error has occurred on the database. */ public Object assignSequenceNumber(WriteObjectQuery writeQuery) throws DatabaseException { - return assignSequenceNumber(writeQuery.getObject(), writeQuery.getSession(), writeQuery); + return assignSequenceNumber(writeQuery.getObject(), null, writeQuery.getSession(), writeQuery); + } + + /** + * INTERNAL: + * Update the writeQuery's object primary key by fetching a new sequence number from the accessor. + * This assume the uses sequence numbers check has already been done. + * Adds the assigned sequence value to writeQuery's modify row. + * If object has a changeSet then sets sequence value into change set as an Id + * adds it also to object's change set in a ChangeRecord if required. + * @return the sequence value or null if not assigned. + * @exception DatabaseException - an error has occurred on the database. + */ + public Object assignSequenceNumber(WriteObjectQuery writeQuery, Object sequenceValue) throws DatabaseException { + return assignSequenceNumber(writeQuery.getObject(), sequenceValue, writeQuery.getSession(), writeQuery); } /** @@ -417,7 +431,7 @@ public Object assignSequenceNumber(WriteObjectQuery writeQuery) throws DatabaseE * @return the sequence value or null if not assigned. * @exception DatabaseException - an error has occurred on the database. */ - protected Object assignSequenceNumber(Object object, AbstractSession writeSession, WriteObjectQuery writeQuery) throws DatabaseException { + protected Object assignSequenceNumber(Object object, Object sequenceValue, AbstractSession writeSession, WriteObjectQuery writeQuery) throws DatabaseException { DatabaseField sequenceNumberField = this.descriptor.getSequenceNumberField(); Object existingValue = null; if (this.sequenceMapping != null) { @@ -428,12 +442,12 @@ protected Object assignSequenceNumber(Object object, AbstractSession writeSessio // PERF: The (internal) support for letting the sequence decide this was removed, // as anything other than primitive should allow null and default as such. - Object sequenceValue; int index = this.descriptor.getPrimaryKeyFields().indexOf(sequenceNumberField); if (isPrimaryKeyComponentInvalid(existingValue, index) || this.descriptor.getSequence().shouldAlwaysOverrideExistingValue()) { - sequenceValue = writeSession.getSequencing().getNextValue(this.descriptor.getJavaClass()); - } else { - return null; + // If no sequence value was passed, obtain one from the Sequence + if(sequenceValue == null) { + sequenceValue = writeSession.getSequencing().getNextValue(this.descriptor.getJavaClass()); + } } // Check that the value is not null, this occurs on any databases using IDENTITY type sequencing. diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/CallQueryMechanism.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/CallQueryMechanism.java index 15282e40d4b..c81cbc7bdcf 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/CallQueryMechanism.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/CallQueryMechanism.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -153,11 +154,18 @@ protected void configureDatabaseCall(DatabaseCall call) { if (!this.query.shouldIgnoreCacheStatement()) { call.setShouldCacheStatement(this.query.shouldCacheStatement()); } + call.setQueryTimeout(this.query.getQueryTimeout()); call.setQueryTimeoutUnit(this.query.getQueryTimeoutUnit()); + if (this.query.isNativeConnectionRequired()) { call.setIsNativeConnectionRequired(true); } + + if (this.query.isInsertObjectQuery()) { + call.setShouldReturnGeneratedKeys(this.query.shouldReturnGeneratedKeys()); + } + if (this.query.isReadQuery()) { ReadQuery readQuery = (ReadQuery)this.query; // Some DB don't support FirstRow in SELECT statements in spite of supporting MaxResults(Symfoware). diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/DatabaseQueryMechanism.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/DatabaseQueryMechanism.java index ac466bfc9b9..7a6717d1a0d 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/DatabaseQueryMechanism.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/DatabaseQueryMechanism.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -21,6 +22,8 @@ package org.eclipse.persistence.internal.queries; import java.io.Serializable; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -35,6 +38,7 @@ import org.eclipse.persistence.exceptions.OptimisticLockException; import org.eclipse.persistence.exceptions.QueryException; import org.eclipse.persistence.expressions.Expression; +import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor; import org.eclipse.persistence.internal.databaseaccess.DatabaseCall; import org.eclipse.persistence.internal.databaseaccess.DatasourceCall; import org.eclipse.persistence.internal.descriptors.OptimisticLockingPolicy; @@ -218,7 +222,14 @@ public boolean isJPQLCallQueryMechanism() { * @exception DatabaseException * @return the row count. */ - public abstract Integer executeNoSelect() throws DatabaseException; + public abstract Object executeNoSelect() throws DatabaseException; + + /** + * Execute a non selecting SQL call, that returns the generated keys + * This should be overridden by subclasses. + * @exception DatabaseException + */ + public abstract DatabaseCall generateKeysExecuteNoSelect() throws DatabaseException; /** * Execute a select SQL call and return the rows. @@ -877,6 +888,64 @@ protected void updateObjectAndRowWithSequenceNumber() throws DatabaseException { writeQuery.getDescriptor().getObjectBuilder().assignSequenceNumber(writeQuery); } + /** + * Update the object's primary key by getting the generated keys from the call + * If there are no generated keys or the value is NULL, then default back to the {@link #updateObjectAndRowWithSequenceNumber()} + */ + protected void updateObjectAndRowWithSequenceNumber(DatabaseCall call) throws DatabaseException { + WriteObjectQuery writeQuery = getWriteObjectQuery(); + AbstractSession session = writeQuery.getSession(); + DatabaseAccessor dbAccessor = (DatabaseAccessor)writeQuery.getAccessor(); + + Object sequenceValue = null; + boolean exceptionOccured = false; + ResultSet resultSet = call.getGeneratedKeys(); + try { + if(resultSet.next()) { + sequenceValue = resultSet.getObject(1); + } + + if(sequenceValue != null) { + writeQuery.getDescriptor().getObjectBuilder().assignSequenceNumber(writeQuery, sequenceValue); + } + } catch (SQLException exception) { + exceptionOccured = true; + DatabaseException commException = dbAccessor.processExceptionForCommError(session, exception, call); + if (commException != null) { + throw commException; + } + throw DatabaseException.sqlException(exception, call, dbAccessor, session, false); + } catch (RuntimeException exception) { + exceptionOccured = true; + if (exception instanceof DatabaseException) { + ((DatabaseException)exception).setCall(call); + if(((DatabaseException)exception).getAccessor() == null) { + ((DatabaseException)exception).setAccessor(dbAccessor); + } + } + throw exception; + } finally { + try { + if (resultSet != null) { + resultSet.close(); + } + } catch (SQLException cleanupSQLException) { + if (!exceptionOccured) { + throw DatabaseException.sqlException(cleanupSQLException, call, dbAccessor, session, false); + } + } catch (RuntimeException cleanupException) { + if (!exceptionOccured) { + throw cleanupException; + } + } + } + + // Fallback on original implementation if no value was found in the generated keys + if(sequenceValue == null) { + updateObjectAndRowWithSequenceNumber(); + } + } + /** * Update the object. * This is only used for non-unit-of-work updates. diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/DatasourceCallQueryMechanism.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/DatasourceCallQueryMechanism.java index 8658ff162c6..19f20ffa054 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/DatasourceCallQueryMechanism.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/DatasourceCallQueryMechanism.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 1998, 2019 IBM Corporation. All rights reserved. + * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -37,10 +37,8 @@ import org.eclipse.persistence.internal.helper.DatabaseField; import org.eclipse.persistence.internal.helper.DatabaseTable; import org.eclipse.persistence.internal.helper.Helper; -import org.eclipse.persistence.internal.helper.NonSynchronizedVector; import org.eclipse.persistence.internal.sessions.AbstractRecord; import org.eclipse.persistence.internal.sessions.AbstractSession; -import org.eclipse.persistence.mappings.DatabaseMapping; import org.eclipse.persistence.mappings.DatabaseMapping.WriteType; import org.eclipse.persistence.queries.ConstructorReportItem; import org.eclipse.persistence.queries.DatabaseQuery; @@ -285,13 +283,26 @@ protected Object executeCall(DatasourceCall databaseCall) throws DatabaseExcepti /** * Execute a non selecting call. * @exception DatabaseException - an error has occurred on the database. - * @return the row count. + * @return Returns either a {@link DatabaseCall} or Integer value, + * depending on if this INSERT call needs to return generated keys */ @Override - public Integer executeNoSelect() throws DatabaseException { + public Object executeNoSelect() throws DatabaseException { + if(((DatabaseCall)this.call).shouldReturnGeneratedKeys()) { + return generateKeysExecuteNoSelect(); + } return executeNoSelectCall(); } + /** + * Execute a non selecting call. + * @exception DatabaseException - an error has occurred on the database. + * @return the row count. + */ + public DatabaseCall generateKeysExecuteNoSelect() throws DatabaseException { + return (DatabaseCall)executeCall(); + } + /** * Execute a non selecting call. * @exception DatabaseException - an error has occurred on the database. @@ -413,7 +424,11 @@ public void insertObject() throws DatabaseException { updateObjectAndRowWithReturnRow(returnFields, index == 0); } if ((index == 0) && usesSequencing && shouldAcquireValueAfterInsert) { - updateObjectAndRowWithSequenceNumber(); + if(result instanceof DatabaseCall) { + updateObjectAndRowWithSequenceNumber((DatabaseCall) result); + } else { + updateObjectAndRowWithSequenceNumber(); + } } } } @@ -427,7 +442,11 @@ public void insertObject() throws DatabaseException { updateObjectAndRowWithReturnRow(returnFields, true); } if (usesSequencing && shouldAcquireValueAfterInsert) { - updateObjectAndRowWithSequenceNumber(); + if(result instanceof DatabaseCall) { + updateObjectAndRowWithSequenceNumber((DatabaseCall) result); + } else { + updateObjectAndRowWithSequenceNumber(); + } } } diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/StatementQueryMechanism.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/StatementQueryMechanism.java index 524d4dac3f5..5a00eff033d 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/StatementQueryMechanism.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/queries/StatementQueryMechanism.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 1998, 2019 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -117,7 +118,7 @@ public Integer deleteObject() throws DatabaseException { * @return the row count. */ @Override - public Integer executeNoSelect() throws DatabaseException { + public Object executeNoSelect() throws DatabaseException { // Prepare the calls if not already set (prepare may not have had the modify row). if ((this.call == null) && (!hasMultipleCalls())) { prepareExecuteNoSelect(); diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/sessions/DatabaseSessionImpl.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/sessions/DatabaseSessionImpl.java index b0becb868e5..80414be96ff 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/sessions/DatabaseSessionImpl.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/internal/sessions/DatabaseSessionImpl.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2015, 2020 IBM Corporation. All rights reserved. + * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -47,6 +47,7 @@ import java.util.Vector; import org.eclipse.persistence.config.PersistenceUnitProperties; +import org.eclipse.persistence.config.PropertiesUtils; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.descriptors.partitioning.PartitioningPolicy; import org.eclipse.persistence.exceptions.DatabaseException; @@ -862,6 +863,13 @@ protected void preConnectDatasource(){ * is connected to. */ protected void postConnectDatasource(){ + // Initialize the Platform properties now that the datasource is connected and the Platform should be initialized + if((getDatasourcePlatform() instanceof DatabasePlatform)) { + final Platform platform = getDatasourcePlatform(); + String dbProperties = (String) getProperties().get(PersistenceUnitProperties.TARGET_DATABASE_PROPERTIES); + PropertiesUtils.set(platform, PersistenceUnitProperties.TARGET_DATABASE_PROPERTIES, dbProperties); + } + if (!hasBroker()) { initializeDescriptors(); diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/DatabaseQuery.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/DatabaseQuery.java index 36c5d35448d..c2b140c1826 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/DatabaseQuery.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/DatabaseQuery.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 1998, 2019 Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 1998, 2018 IBM Corporation. All rights reserved. + * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -268,6 +268,8 @@ public enum ParameterType {POSITIONAL, NAMED} protected TimeUnit queryTimeoutUnit; + protected boolean shouldReturnGeneratedKeys; + /* Used as default for read, means shallow write for modify. */ public static final int NoCascading = 1; @@ -2423,6 +2425,18 @@ public void setShouldRetrieveBypassCache(boolean shouldRetrieveBypassCache) { this.shouldRetrieveBypassCache = shouldRetrieveBypassCache; } + /** + * ADVANCED: JPA flag used to control the behavior of IDENTITY generation. This + * flag specifies the behavior when data is retrieved by the find methods + * and by the execution of queries. + *
+ * This flag is only applicable to Insert queries and will only apply if the database + * platform supports {@link java.sql.Statement#RETURN_GENERATED_KEYS} + */ + public void setShouldReturnGeneratedKeys(boolean shouldReturnGeneratedKeys) { + this.shouldReturnGeneratedKeys = shouldReturnGeneratedKeys; + } + /** * ADVANCED: JPA flag used to control the behavior of the shared cache. This * flag specifies the behavior when data is read from the database and when @@ -2634,6 +2648,18 @@ public boolean shouldRetrieveBypassCache() { return this.shouldRetrieveBypassCache; } + /** + * ADVANCED: JPA flag used to control the behavior of IDENTITY generation. This + * flag specifies the behavior when data is retrieved by the find methods + * and by the execution of queries. + *
+ * This flag is only applicable to Insert queries and will only apply if the database + * platform supports {@link java.sql.Statement#RETURN_GENERATED_KEYS} + */ + public boolean shouldReturnGeneratedKeys() { + return this.shouldReturnGeneratedKeys; + } + /** * ADVANCED: JPA flag used to control the behavior of the shared cache. This * flag specifies the behavior when data is read from the database and when diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/InsertObjectQuery.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/InsertObjectQuery.java index d28adab1b9a..e07c28c1dc7 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/InsertObjectQuery.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/InsertObjectQuery.java @@ -1,5 +1,6 @@ /* - * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022 IBM Corporation. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -18,6 +19,8 @@ import org.eclipse.persistence.exceptions.*; import org.eclipse.persistence.internal.sessions.AbstractRecord; import org.eclipse.persistence.internal.sessions.AbstractSession; +import org.eclipse.persistence.sequencing.NativeSequence; +import org.eclipse.persistence.sequencing.Sequence; /** *
Purpose:
@@ -114,6 +117,15 @@ protected void prepare() {
if (this.name == null) {
this.name = "insert" + this.descriptor.getJavaClass().getSimpleName();
}
+
+ // Insert queries may need to use generated keys. Determine here if the underlying sequence for the object will want to.
+ if(this.descriptor.usesSequenceNumbers()) {
+ Sequence sequence = this.descriptor.getSequence();
+ if(sequence.isNative() && ((NativeSequence)sequence).shouldUseGeneratedKeysIfPlatformSupports()) {
+ setShouldReturnGeneratedKeys(true);
+ }
+ }
+
getQueryMechanism().prepareInsertObject();
}
diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/sequencing/NativeSequence.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/sequencing/NativeSequence.java
index 038f0de4ad6..6ddb4849d62 100644
--- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/sequencing/NativeSequence.java
+++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/sequencing/NativeSequence.java
@@ -1,5 +1,6 @@
/*
- * Copyright (c) 1998, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022 IBM Corporation. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
@@ -36,6 +37,12 @@ public class NativeSequence extends QuerySequence {
*/
protected boolean shouldUseIdentityIfPlatformSupports = true;
+ /**
+ * true indicates that identity is used and generatedKeys should be used - if the platform supports generatedKeys
+ * false indicates that identity is used and generatedKeys should not be used - if the platform does not support generatedKeys
+ */
+ protected boolean shouldUseGeneratedKeysIfPlatformSupports = false;
+
/**
* Allow sequencing to be delegated to another sequence if native sequencing is not supported.
*/
@@ -107,6 +114,14 @@ public boolean shouldUseIdentityIfPlatformSupports() {
return shouldUseIdentityIfPlatformSupports;
}
+ public void setShouldUseGeneratedKeysIfPlatformSupports(boolean shouldUseGeneratedKeysIfPlatformSupports) {
+ this.shouldUseGeneratedKeysIfPlatformSupports = shouldUseGeneratedKeysIfPlatformSupports;
+ }
+
+ public boolean shouldUseGeneratedKeysIfPlatformSupports() {
+ return shouldUseGeneratedKeysIfPlatformSupports;
+ }
+
@Override
public boolean equals(Object obj) {
if (obj instanceof NativeSequence) {
@@ -183,9 +198,18 @@ public void setDelegateSequence(QuerySequence delegateSequence) {
public void onConnect() {
DatasourcePlatform platform = (DatasourcePlatform)getDatasourcePlatform();
// Set shouldAcquireValueAfterInsert flag: identity -> true; sequence objects -> false.
- if (platform.supportsIdentity() && shouldUseIdentityIfPlatformSupports()) {
+ if ((platform.supportsIdentity() || platform.supportsReturnGeneratedKeys()) && shouldUseIdentityIfPlatformSupports()) {
// identity is both supported by platform and desired by the NativeSequence
setShouldAcquireValueAfterInsert(true);
+
+ //Here we can ask the platform if it supports generatedKeys
+ // We know that it's IDENTITY and not some other generation type
+ // I should set here that generated keys will be used and Sequence can expect a ResultSet from the InsertQuery later
+
+ // set a flag so that Sequence knows how to obtain values later
+ if(platform.supportsReturnGeneratedKeys()) {
+ setShouldUseGeneratedKeysIfPlatformSupports(true);
+ }
} else if (platform.supportsSequenceObjects() && !shouldUseIdentityIfPlatformSupports()) {
// sequence objects is both supported by platform and desired by the NativeSequence
setShouldAcquireValueAfterInsert(false);
diff --git a/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/sequence/TestIdentityGeneration.java b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/sequence/TestIdentityGeneration.java
new file mode 100644
index 00000000000..6e15bbad8a5
--- /dev/null
+++ b/jpa/eclipselink.jpa.test.jse/src/it/java/org/eclipse/persistence/jpa/test/sequence/TestIdentityGeneration.java
@@ -0,0 +1,215 @@
+/*
+ * Copyright (c) 2022 IBM Corporation and/or affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0,
+ * or the Eclipse Distribution License v. 1.0 which is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
+ */
+// Contributors:
+// IBM - Bug 579409: Add support for accurate IDENTITY generation when the database contains separate TRIGGER objects
+package org.eclipse.persistence.jpa.test.sequence;
+
+import java.math.BigDecimal;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import jakarta.persistence.EntityManager;
+import jakarta.persistence.EntityManagerFactory;
+
+import org.eclipse.persistence.internal.jpa.EntityManagerFactoryImpl;
+import org.eclipse.persistence.jpa.test.framework.DDLGen;
+import org.eclipse.persistence.jpa.test.framework.Emf;
+import org.eclipse.persistence.jpa.test.framework.EmfRunner;
+import org.eclipse.persistence.jpa.test.framework.Property;
+import org.eclipse.persistence.jpa.test.sequence.model.Coffee;
+import org.eclipse.persistence.jpa.test.sequence.model.Tea;
+import org.eclipse.persistence.jpa.test.sequence.model.TeaShop;
+import org.eclipse.persistence.platform.database.DatabasePlatform;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(EmfRunner.class)
+public class TestIdentityGeneration {
+ @Emf(createTables = DDLGen.DROP_CREATE, classes = { Coffee.class, Tea.class, TeaShop.class },
+ properties = {
+ @Property(name="eclipselink.logging.level", value="FINE")})
+ private EntityManagerFactory emf;
+
+ @Test
+ public void testPersistWithSecondaryTables() {
+ if (emf == null)
+ return;
+
+ EntityManager em = emf.createEntityManager();
+ try {
+ Set