Bug 426852 - @GeneratedValue(strategy=GenerationType.IDENTITY) support in Oracle 12c
Signed-off-by: Tomas Kraus <tomas.kraus@oracle.com>
Reviewed-by: Petros Splinakis <petros.splinakis@oracle.com>
diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java
index 5914ec1..607a526 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/databaseaccess/DatasourcePlatform.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
@@ -9,6 +9,8 @@
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
+ * 09/29/2016-2.7 Tomas Kraus
+ * - 426852: @GeneratedValue(strategy=GenerationType.IDENTITY) support in Oracle 12c
******************************************************************************/
package org.eclipse.persistence.internal.databaseaccess;
@@ -18,6 +20,7 @@
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
+import java.util.Set;
import java.util.Vector;
import org.eclipse.persistence.descriptors.DescriptorQueryManager;
@@ -37,6 +40,7 @@
import org.eclipse.persistence.sequencing.DefaultSequence;
import org.eclipse.persistence.sequencing.QuerySequence;
import org.eclipse.persistence.sequencing.Sequence;
+import org.eclipse.persistence.sessions.Session;
/**
* DatasourcePlatform is private to TopLink. It encapsulates behavior specific to a datasource platform
@@ -71,7 +75,7 @@ public class DatasourcePlatform implements Platform {
protected Sequence defaultSequence;
/** Store map of sequence names to sequences */
- protected Map sequences;
+ protected Map<String, Sequence> sequences;
/** Delimiter to use for fields and tables using spaces or other special values */
protected String startDelimiter = null;
@@ -551,6 +555,11 @@ public boolean isOracle9() {
return false;
}
+ @Override
+ public boolean isOracle12() {
+ return false;
+ }
+
public boolean isPervasive(){
return false;
}
@@ -817,7 +826,7 @@ public void removeAllSequences() {
* Returns a map of sequence names to Sequences (may be null).
*/
@Override
- public Map getSequences() {
+ public Map<String, Sequence> getSequences() {
return this.sequences;
}
@@ -826,7 +835,7 @@ public Map getSequences() {
* Used only for writing into XML or Java.
*/
@Override
- public Map getSequencesToWrite() {
+ public Map<String, Sequence> getSequencesToWrite() {
if ((getSequences() == null) || getSequences().isEmpty()) {
return null;
}
@@ -1007,4 +1016,29 @@ public ValueReadQuery buildSelectQueryForIdentity(String seqName, Integer size)
public DatasourceCall buildNativeCall(String queryString) {
return new SQLCall(queryString);
}
+
+ /**
+ * INTERNAL:
+ * Initialize platform specific identity sequences.
+ * @param session Active database session (in connected state).
+ * @param defaultIdentityGenerator Default identity generator sequence name.
+ * @since 2.7
+ */
+ @Override
+ public void initIdentitySequences(final Session session, final String defaultIdentityGenerator) {
+ }
+
+ /**
+ * INTERNAL:
+ * Remove platform specific identity sequences for specified tables. Default identity sequences are restored.
+ * @param dbSession Active database session (in connected state).
+ * @param defaultIdentityGenerator Default identity generator sequence name.
+ * @param tableNames Set of table names to check for identity sequence removal.
+ * @since 2.7
+ */
+ @Override
+ public void removeIdentitySequences(
+ final Session session, final String defaultIdentityGenerator, final Set<String> tableNames) {
+ }
+
}
diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/databaseaccess/Platform.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/databaseaccess/Platform.java
index d40335f..a47b9a0 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/databaseaccess/Platform.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/databaseaccess/Platform.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
@@ -12,14 +12,20 @@
******************************************************************************/
package org.eclipse.persistence.internal.databaseaccess;
-import java.io.*;
+import java.io.Serializable;
+import java.io.Writer;
import java.util.Map;
-import org.eclipse.persistence.exceptions.*;
-import org.eclipse.persistence.queries.*;
+import java.util.Set;
+
+import org.eclipse.persistence.exceptions.ConversionException;
import org.eclipse.persistence.internal.core.databaseaccess.CorePlatform;
-import org.eclipse.persistence.internal.helper.*;
+import org.eclipse.persistence.internal.helper.ConversionManager;
+import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.sessions.AbstractSession;
+import org.eclipse.persistence.queries.Call;
+import org.eclipse.persistence.queries.ValueReadQuery;
import org.eclipse.persistence.sequencing.Sequence;
+import org.eclipse.persistence.sessions.Session;
/**
* Platform is private to TopLink. It encapsulates behavior specific to a datasource platform
@@ -108,6 +114,8 @@ public interface Platform extends CorePlatform<ConversionManager>, Serializable,
public boolean isOracle9();
+ public boolean isOracle12();
+
public boolean isPointBase();
public boolean isSQLAnywhere();
@@ -259,4 +267,28 @@ public interface Platform extends CorePlatform<ConversionManager>, Serializable,
* Indicates whether defaultSequence is the same as platform default sequence.
*/
public boolean usesPlatformDefaultSequence();
+
+ /**
+ * INTERNAL:
+ * Initialize platform specific identity sequences.
+ * This method is called from {@code EntityManagerSetupImpl} after login and optional schema generation.
+ * Method is also called from {@code TableCreator} class during tables creation and update..
+ * @param session Active database session (in connected state).
+ * @param defaultIdentityGenerator Default identity generator sequence name.
+ * @since 2.7
+ */
+ public void initIdentitySequences(final Session session, final String defaultIdentityGenerator);
+
+ /**
+ * INTERNAL:
+ * Remove platform specific identity sequences for specified tables. Default identity sequences are restored.
+ * Method is also called from {@code TableCreator} class during tables removal.
+ * @param dbSession Active database session (in connected state).
+ * @param defaultIdentityGenerator Default identity generator sequence name.
+ * @param tableNames Set of table names to check for identity sequence removal.
+ * @since 2.7
+ */
+ public void removeIdentitySequences(
+ final Session session, final String defaultIdentityGenerator, final Set<String> tableNames);
+
}
diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java
index cb51f71..d85edf8 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/internal/localization/i18n/LoggingLocalizationResource.java
@@ -740,6 +740,8 @@ public class LoggingLocalizationResource extends ListResourceBundle {
{ "dbPlatformHelper_detectedVendorPlatform", "Detected database platform: {0}"},
{ "dbPlatformHelper_regExprDbPlatform", "Database platform: {1}, regular expression: {0}"},
{ "dbPlatformHelper_patternSyntaxException", "Exception while using regular expression: {0}" },
+ { "platform_ora_init_id_seq", "Init Oracle identity sequence {0} -> {1} for table {2}"},
+ { "platform_ora_remove_id_seq", "Remove Oracle identity sequence {0} -> {1} for table {2}"},
{ "unknown_query_hint", "query {0}: unknown query hint {1} will be ignored"},
{ "query_hint", "query {0}: query hint {1}; value {2}"},
{ "property_value_specified", "property={0}; value={1}"},
diff --git a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/tools/schemaframework/TableCreator.java b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/tools/schemaframework/TableCreator.java
index b459239..1ebc535 100644
--- a/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/tools/schemaframework/TableCreator.java
+++ b/foundation/org.eclipse.persistence.core/src/org/eclipse/persistence/tools/schemaframework/TableCreator.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 1998, 2015 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1998, 2016 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
@@ -17,8 +17,10 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.Vector;
import org.eclipse.persistence.exceptions.DatabaseException;
@@ -41,6 +43,10 @@
* @author Peter Krogh
*/
public class TableCreator {
+ /** Default identity generator sequence name.
+ * Copy of value from JPA: {@code MetadataProject.DEFAULT_IDENTITY_GENERATOR}. */
+ public static final String DEFAULT_IDENTITY_GENERATOR = "SEQ_GEN_IDENTITY";
+
/** Flag to disable table existence check before create. */
public static boolean CHECK_EXISTENCE = true;
@@ -151,12 +157,19 @@ public void createTables(DatabaseSession session, SchemaManager schemaManager, b
/**
* This creates the tables on the database.
* If the table already exists this will fail.
+ * @param session Active database session.
+ * @param schemaManager Database schema manipulation manager.
+ * @param build Whether to build constraints.
+ * @param check Whether to check for tables existence.
+ * @param createSequenceTables Whether to create sequence tables.
+ * @param createSequences Whether to create sequences.
*/
- public void createTables(DatabaseSession session, SchemaManager schemaManager, boolean build, boolean check, boolean createSequenceTables, boolean createSequences) {
+ public void createTables(final DatabaseSession session, final SchemaManager schemaManager, final boolean build,
+ final boolean check, final boolean createSequenceTables, final boolean createSequences) {
buildConstraints(schemaManager, build);
- String sequenceTableName = getSequenceTableName(session);
- List<TableDefinition> missingTables = new ArrayList<TableDefinition>();
+ final String sequenceTableName = getSequenceTableName(session);
+ final List<TableDefinition> missingTables = new ArrayList<TableDefinition>();
for (TableDefinition table : getTableDefinitions()) {
// Must not create sequence table as done in createSequences.
if (!table.getName().equals(sequenceTableName)) {
@@ -183,6 +196,7 @@ public void createTables(DatabaseSession session, SchemaManager schemaManager, b
createConstraints(missingTables, session, schemaManager, false);
schemaManager.createOrReplaceSequences(createSequenceTables, createSequences);
+ session.getDatasourcePlatform().initIdentitySequences(session, DEFAULT_IDENTITY_GENERATOR);
}
/**
@@ -232,13 +246,16 @@ public void dropTables(DatabaseSession session, SchemaManager schemaManager) {
/**
* Drop the tables from the database.
+ * @param session Active database session.
+ * @param schemaManager Database schema manipulation manager.
+ * @param build Whether to build constraints.
*/
- public void dropTables(DatabaseSession session, SchemaManager schemaManager, boolean build) {
+ public void dropTables(final DatabaseSession session, final SchemaManager schemaManager, final boolean build) {
buildConstraints(schemaManager, build);
// CR 3870467, do not log stack, or log at all if not fine
boolean shouldLogExceptionStackTrace = session.getSessionLog().shouldLogExceptionStackTrace();
- int level = session.getSessionLog().getLevel();
+ final int level = session.getSessionLog().getLevel();
if (shouldLogExceptionStackTrace) {
session.getSessionLog().setShouldLogExceptionStackTrace(false);
}
@@ -248,7 +265,7 @@ public void dropTables(DatabaseSession session, SchemaManager schemaManager, boo
try {
dropConstraints(session, schemaManager, false);
- String sequenceTableName = getSequenceTableName(session);
+ final String sequenceTableName = getSequenceTableName(session);
List<TableDefinition> tables = getTableDefinitions();
int trys = 1;
if (SchemaManager.FORCE_DROP) {
@@ -256,12 +273,15 @@ public void dropTables(DatabaseSession session, SchemaManager schemaManager, boo
}
while ((trys > 0) && !tables.isEmpty()) {
trys--;
- List<TableDefinition> failed = new ArrayList<TableDefinition>();
- for (TableDefinition table : tables) {
+ final List<TableDefinition> failed = new ArrayList<TableDefinition>();
+ final Set<String> tableNames = new HashSet<>(tables.size());
+ for (final TableDefinition table : tables) {
+ final String tableName = table.getName();
// Must not create sequence table as done in createSequences.
- if (!table.getName().equals(sequenceTableName)) {
+ if (!tableName.equals(sequenceTableName)) {
try {
schemaManager.dropObject(table);
+ tableNames.add(tableName);
} catch (DatabaseException exception) {
failed.add(table);
if (!shouldIgnoreDatabaseException()) {
@@ -270,6 +290,7 @@ public void dropTables(DatabaseSession session, SchemaManager schemaManager, boo
}
}
}
+ session.getDatasourcePlatform().removeIdentitySequences(session, DEFAULT_IDENTITY_GENERATOR, tableNames);
tables = failed;
}
} finally {
@@ -422,15 +443,18 @@ protected void extendTablesAndConstraints(SchemaManager schemaManager, DatabaseS
/**
* This creates/extends the tables on the database.
+ * @param session Active database session.
+ * @param schemaManager Database schema manipulation manager.
+ * @param build Whether to build constraints.
*/
- public void extendTables(DatabaseSession session, SchemaManager schemaManager, boolean build) {
+ public void extendTables(final DatabaseSession session, final SchemaManager schemaManager, final boolean build) {
buildConstraints(schemaManager, build);
- String sequenceTableName = getSequenceTableName(session);
- for (TableDefinition table : getTableDefinitions()) {
+ final String sequenceTableName = getSequenceTableName(session);
+ for (final TableDefinition table : getTableDefinitions()) {
// Must not create sequence table as done in createSequences.
if (!table.getName().equals(sequenceTableName)) {
- AbstractSession abstractSession = (AbstractSession) session;
+ final AbstractSession abstractSession = (AbstractSession) session;
boolean alreadyExists = false;
// Check if the table already exists, to avoid logging create error.
if (CHECK_EXISTENCE && schemaManager.shouldWriteToDatabase()) {
@@ -442,7 +466,7 @@ public void extendTables(DatabaseSession session, SchemaManager schemaManager, b
try {
schemaManager.createObject(table);
session.getSessionLog().log(SessionLog.FINEST, SessionLog.DDL, "default_tables_created", table.getFullName());
- } catch (DatabaseException exception) {
+ } catch (final DatabaseException exception) {
createTableException = exception;
alreadyExists = true;
}
@@ -453,7 +477,7 @@ public void extendTables(DatabaseSession session, SchemaManager schemaManager, b
//While SQL is case insensitive, getColumnInfo is and will not return the table info unless the name is passed in
//as it is stored internally.
String tableName = table.getTable()==null? table.getName(): table.getTable().getName();
- boolean usesDelimiting = (table.getTable()!=null && table.getTable().shouldUseDelimiters());
+ final boolean usesDelimiting = (table.getTable()!=null && table.getTable().shouldUseDelimiters());
List<DatabaseRecord> columnInfo = null;
//I need the actual table catalog, schema and tableName for getTableInfo.
@@ -471,9 +495,9 @@ public void extendTables(DatabaseSession session, SchemaManager schemaManager, b
//Table exists, add individual fields as necessary
//hash the table's existing columns by name
- Map<DatabaseField, DatabaseRecord> columns = new HashMap(columnInfo.size());
- DatabaseField columnNameLookupField = new DatabaseField("COLUMN_NAME");
- DatabaseField schemaLookupField = new DatabaseField("TABLE_SCHEM");
+ final Map<DatabaseField, DatabaseRecord> columns = new HashMap(columnInfo.size());
+ final DatabaseField columnNameLookupField = new DatabaseField("COLUMN_NAME");
+ final DatabaseField schemaLookupField = new DatabaseField("TABLE_SCHEM");
boolean schemaMatchFound = false;
// Determine the probably schema for the table, this is a heuristic, so should not cause issues if wrong.
String qualifier = table.getQualifier();
@@ -490,15 +514,15 @@ public void extendTables(DatabaseSession session, SchemaManager schemaManager, b
}
}
}
- boolean checkSchema = (qualifier != null) && (qualifier.length() > 0);
- for (DatabaseRecord record : columnInfo) {
- String fieldName = (String)record.get(columnNameLookupField);
+ final boolean checkSchema = (qualifier != null) && (qualifier.length() > 0);
+ for (final DatabaseRecord record : columnInfo) {
+ final String fieldName = (String)record.get(columnNameLookupField);
if (fieldName != null && fieldName.length() > 0) {
- DatabaseField column = new DatabaseField(fieldName);
+ final DatabaseField column = new DatabaseField(fieldName);
if (session.getPlatform().shouldForceFieldNamesToUpperCase()) {
column.useUpperCaseForComparisons(true);
}
- String schema = (String)record.get(schemaLookupField);
+ final String schema = (String)record.get(schemaLookupField);
// Check the schema as well. Ignore columns for other schema if a schema match is found.
if (schemaMatchFound) {
if (qualifier.equalsIgnoreCase(schema)) {
@@ -519,7 +543,7 @@ public void extendTables(DatabaseSession session, SchemaManager schemaManager, b
}
//Go through each field we need to have in the table to see if it already exists
- for (FieldDefinition fieldDef : table.getFields()){
+ for (final FieldDefinition fieldDef : table.getFields()){
DatabaseField dbField = fieldDef.getDatabaseField();
if ( dbField == null ) {
dbField = new DatabaseField(fieldDef.getName());
@@ -528,7 +552,7 @@ public void extendTables(DatabaseSession session, SchemaManager schemaManager, b
//field does not exist so add it to the table
try {
table.addFieldOnDatabase(abstractSession, fieldDef);
- } catch(DatabaseException addFieldEx) {
+ } catch (final DatabaseException addFieldEx) {
session.getSessionLog().log(SessionLog.FINEST, SessionLog.DDL, "table_cannot_add_field", dbField.getName(), table.getFullName(), addFieldEx.getMessage());
if (!shouldIgnoreDatabaseException()) {
throw addFieldEx;
@@ -548,5 +572,7 @@ public void extendTables(DatabaseSession session, SchemaManager schemaManager, b
createConstraints(session, schemaManager, false);
schemaManager.createSequences();
+ session.getDatasourcePlatform().initIdentitySequences(session, DEFAULT_IDENTITY_GENERATOR);
+
}
}
diff --git a/foundation/org.eclipse.persistence.oracle/src/org/eclipse/persistence/platform/database/oracle/Oracle12Platform.java b/foundation/org.eclipse.persistence.oracle/src/org/eclipse/persistence/platform/database/oracle/Oracle12Platform.java
index 8c816e5..2540ee5 100644
--- a/foundation/org.eclipse.persistence.oracle/src/org/eclipse/persistence/platform/database/oracle/Oracle12Platform.java
+++ b/foundation/org.eclipse.persistence.oracle/src/org/eclipse/persistence/platform/database/oracle/Oracle12Platform.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2014, 2015 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016 Oracle and/or its affiliates. All rights reserved.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
@@ -9,15 +9,144 @@
*
* Contributors:
* Oracle - initial API and implementation from Oracle TopLink
+ * 09/29/2016-2.7 Tomas Kraus
+ * - 426852: @GeneratedValue(strategy=GenerationType.IDENTITY) support in Oracle 12c
******************************************************************************/
package org.eclipse.persistence.platform.database.oracle;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.persistence.descriptors.ClassDescriptor;
+import org.eclipse.persistence.exceptions.ValidationException;
+import org.eclipse.persistence.internal.sessions.AbstractSession;
+import org.eclipse.persistence.internal.sessions.EmptyRecord;
+import org.eclipse.persistence.logging.SessionLog;
+import org.eclipse.persistence.queries.ValueReadQuery;
+import org.eclipse.persistence.sequencing.NativeSequence;
+import org.eclipse.persistence.sequencing.Sequence;
+import org.eclipse.persistence.sessions.Session;
+
/**
* <p><b>Purpose:</b>
- * Supports usage of certain Oracle JDBC specific APIs for the Oracle 12 database.
+ * Supports usage of certain Oracle JDBC specific APIs for the Oracle 12 database.<br>
+ * Identity column:<br>
+ * <dd>{@code GENERATED [ ALWAYS | BY DEFAULT [ ON NULL ] ] AS IDENTITY [ ( identity_options ) ]}
*/
public class Oracle12Platform extends Oracle11Platform {
+
+ /** Table name to identity sequence name storage. */
+ private final Map<String, String> identitySequences;
+
public Oracle12Platform() {
super();
+ supportsIdentity = true;
+ identitySequences = new ConcurrentHashMap<>();
}
+
+ /**
+ * INTERNAL:
+ * Check whether current platform is Oracle 12c or later.
+ * @return Always returns {@code true} for instances of Oracle 12c platform.
+ * @since 2.7
+ */
+ @Override
+ public boolean isOracle12() {
+ return true;
+ }
+
+ /**
+ * INTERNAL:
+ * Initialize platform specific identity sequences.
+ * @param dbSession Active database session (in connected state).
+ * @param defaultIdentityGenerator Default identity generator sequence name.
+ * @since 2.7
+ */
+ @Override
+ public void initIdentitySequences(final Session session, final String defaultIdentityGenerator) {
+ if (sequences != null && sequences.containsKey(defaultIdentityGenerator)) {
+ for (final ClassDescriptor descriptor : session.getDescriptors().values()) {
+ final Sequence sequence = descriptor.getSequence();
+ if (sequence != null && defaultIdentityGenerator.equals(sequence.getName())) {
+ final String tableName = descriptor.getTableName();
+ final String seqName = getIdentitySequence(tableName, session);
+ if (seqName != null) {
+ final NativeSequence newSequence = new NativeSequence(seqName, 1, true);
+ newSequence.setShouldAcquireValueAfterInsert(true);
+ newSequence.onConnect(this);
+ descriptor.setSequence(newSequence);
+ descriptor.setSequenceNumberName(seqName);
+ identitySequences.put(tableName, seqName);
+ addSequence(newSequence);
+ if (session.getSessionLog().shouldLog(SessionLog.FINE)) {
+ session.getSessionLog().log(SessionLog.FINE, "platform_ora_init_id_seq",
+ new Object[] {defaultIdentityGenerator, seqName, tableName});
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * INTERNAL:
+ * Remove platform specific identity sequence for specified table. Default identity sequence is restored.
+ * @param dbSession Active database session (in connected state).
+ * @param defaultIdentityGenerator Default identity generator sequence name.
+ * @param tableNames Set of table names to check for identity sequence removal.
+ * @since 2.7
+ */
+ @Override
+ public void removeIdentitySequences(final Session session, final String defaultIdentityGenerator, final Set<String> tableNames) {
+ if (sequences != null && sequences.containsKey(defaultIdentityGenerator)) {
+ final Sequence defaultSeq = getSequence(defaultIdentityGenerator);
+ for (final ClassDescriptor descriptor : session.getDescriptors().values()) {
+ final String tableName = descriptor.getTableName();
+ if (tableName != null && identitySequences.containsKey(tableName)) {
+ final String seqName = identitySequences.remove(tableName);
+ removeSequence(seqName);
+ descriptor.setSequence(defaultSeq);
+ descriptor.setSequenceNumberName(defaultIdentityGenerator);
+ if (session.getSessionLog().shouldLog(SessionLog.FINE)) {
+ session.getSessionLog().log(SessionLog.FINE, "platform_ora_remove_id_seq",
+ new Object[] {seqName, defaultIdentityGenerator, tableName});
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Get sequence name corresponding to the table name.
+ * @param tableName Name of the table.
+ * @param session Active data source session.
+ * @return Sequence name corresponding to the table name or {@code null} if no such sequence exists.
+ * @since 2.7
+ */
+ private String getIdentitySequence(final String tableName, final Session session) {
+ // TABLE_NAME values are converted to upper case by default.
+ // Also TableDefinition.buildCreationWriter(AbstractSession,Writer) does not have support for quoting table names
+ // to make them case sensitive on Oracle DB.
+ final String sql = "SELECT SEQUENCE_NAME FROM USER_TAB_IDENTITY_COLS WHERE TABLE_NAME='" + tableName.toUpperCase() + "'";
+ return (String)(new ValueReadQuery(sql)).execute((AbstractSession)session, EmptyRecord.getEmptyRecord());
+ }
+
+ /**
+ * INTERNAL:
+ * Append the receiver's field 'identity' constraint clause to a writer.
+ * @param writer Target writer.
+ * @since 2.7
+ */
+ @Override
+ public void printFieldIdentityClause(final Writer writer) throws ValidationException {
+ try {
+ writer.write(" GENERATED AS IDENTITY");
+ } catch (IOException ioException) {
+ throw ValidationException.fileError(ioException);
+ }
+ }
+
}
diff --git a/jpa/eclipselink.jpa.test/antbuild.properties b/jpa/eclipselink.jpa.test/antbuild.properties
index d3de5a8..acd0bd1 100644
--- a/jpa/eclipselink.jpa.test/antbuild.properties
+++ b/jpa/eclipselink.jpa.test/antbuild.properties
@@ -102,6 +102,7 @@
eclipselink.validation.failed.model=eclipselink-validation-failed-model
eclipselink.advanced.field.access.model=eclipselink-advanced-field-access-model
eclipselink.advanced.properties=eclipselink-advanced-properties
+eclipselink.identity.model=eclipselink-identity-model
eclipselink.xml.extended.model=eclipselink-xml-extended-model
eclipselink.jpa21.model=eclipselink-jpa21-model
eclipselink.jpa22.model=eclipselink-jpa22-model
diff --git a/jpa/eclipselink.jpa.test/antbuild.xml b/jpa/eclipselink.jpa.test/antbuild.xml
index 7214403..4375dd0 100644
--- a/jpa/eclipselink.jpa.test/antbuild.xml
+++ b/jpa/eclipselink.jpa.test/antbuild.xml
@@ -619,6 +619,7 @@
<mkdir dir="${jpatest.basedir}/${build.dir}/${eclipselink.validation.failed.model}"/>
<mkdir dir="${jpatest.basedir}/${build.dir}/${eclipselink.advanced.field.access.model}"/>
<mkdir dir="${jpatest.basedir}/${build.dir}/${eclipselink.advanced.properties}"/>
+ <mkdir dir="${jpatest.basedir}/${build.dir}/${eclipselink.identity.model}"/>
<mkdir dir="${jpatest.basedir}/${build.dir}/${eclipselink.pu.with.spaces}"/>
<mkdir dir="${jpatest.basedir}/${build.dir}/eclipselink-partitioned-model"/>
<mkdir dir="${jpatest.basedir}/${build.dir}/eclipselink-plsql-model"/>
@@ -663,7 +664,7 @@
<!-- compilerarg value="-Xlint:unchecked" compiler="javac1.7"/ -->
<compilerarg value="-Aeclipselink.persistencexml=${PERSISTENCE_XML_PATH}"/>
<!-- compilerarg value="-Aeclipselink.canonicalmodel.prefix=_" compiler="javac1.7"/ -->
- <classpath refid="${COMPILE_PATH_REF}"/>
+ <classpath refid="${COMPILE_PATH_REF}"/>
</javac>
</target>
@@ -734,6 +735,13 @@
<param name="PERSISTENCE_XML_PATH" value="eclipselink-metamodel-model/persistence.xml"/>
<param name="COMPILE_PATH_REF" value="compile.modelgen.path"/>
</antcall>
+ <antcall target="model-compile" inheritRefs="true">
+ <param name="SRC_PATH" value="${jpatest.basedir}/${src.dir}"/>
+ <param name="DEST_PATH" value="${jpatest.basedir}/${classes.dir}"/>
+ <param name="MODEL_PATH" value="org/eclipse/persistence/testing/models/jpa/identity"/>
+ <param name="PERSISTENCE_XML_PATH" value="eclipselink-identity-model/persistence.xml"/>
+ <param name="COMPILE_PATH_REF" value="compile.modelgen.path"/>
+ </antcall>
<!-- This persistence.xml MUST be deleted because tests will pick it up otherwise and fail -->
<delete dir="${jpatest.build.location}/${classes.dir}/META-INF"/>
<available file="${jpatest.build.location}/${classes.dir}/org/eclipse/persistence/testing/models/jpa/advanced/Address_.class" property="modelgen.created.classes.exist"/>
@@ -912,7 +920,8 @@
package-composite-advanced-member_1, package-composite-advanced-member_2, package-composite-advanced-member_3, package-xml-composite-advanced,
package-xml-composite-advanced-member_1, package-xml-composite-advanced-member_2, package-xml-composite-advanced-member_3,
package-xml-extended-composite-advanced, package-xml-extended-composite-advanced-member_1, package-xml-extended-composite-advanced-member_2,
- package-xml-extended-composite-advanced-member_3, package-extensibility, package-jpa-remote, package-xml-mapping-metadata-complete" description="build EclipseLink jar">
+ package-xml-extended-composite-advanced-member_3, package-extensibility, package-jpa-remote, package-identity-model,
+ package-xml-mapping-metadata-complete" description="build EclipseLink jar">
<jar jarfile="${jpatest.basedir}/${jpatest.framework.jar}">
<fileset dir="${jpatest.basedir}/${classes.dir}" includes="org/eclipse/persistence/testing/framework/**/*.class"/>
</jar>
@@ -1299,6 +1308,20 @@
</jar>
</target>
+ <target name="package-identity-model" depends="">
+ <copy todir="${jpatest.basedir}/${build.dir}/${eclipselink.identity.model}/META-INF">
+ <fileset dir="${jpatest.basedir}/resource/${eclipselink.identity.model}" includes="*.xml"/>
+ </copy>
+ <copy todir="${jpatest.basedir}/${build.dir}/${eclipselink.identity.model}">
+ <fileset dir="${jpatest.basedir}/${classes22.dir}"
+ includes="org/eclipse/persistence/testing/models/jpa22/identity/**"/>
+ </copy>
+ <jar jarfile="${jpatest.basedir}/${eclipselink.identity.model}.jar">
+ <fileset dir="${jpatest.basedir}/${build.dir}/${eclipselink.identity.model}">
+ </fileset>
+ </jar>
+ </target>
+
<target name="package-jpa-performance2" depends="">
<copy todir="${jpatest.basedir}/${build.dir}/${jpa.performance2}/META-INF">
<fileset dir="${jpatest.basedir}/resource/${jpa.performance2}/META-INF" includes="*.xml"/>
@@ -1711,6 +1734,14 @@
<pathelement path="${jpatest.basedir}/${eclipselink.xml.extended.composite.advanced.model.member_3}.jar"/>
<pathelement path="${jpatest.2.common.plugins.dir}/${json.jar}"/>
</path>
+ <path id="run.identity.classpath">
+ <pathelement path="${jpatest.basedir}/${classes22.dir}"/>
+ <path refid="jpa22.compile.path"/>
+ <pathelement path="${jdbc.driver.jar}"/>
+ <pathelement path="${jpatest.basedir}/${jpatest.framework.jar}"/>
+ <pathelement path="${jpatest.basedir}/${jpa22.test.jar}"/>
+ <pathelement path="${jpatest.basedir}/${eclipselink.identity.model}.jar"/>
+ </path>
<path id="run.weaver.classpath">
<pathelement path="${jpatest.basedir}/${classes.dir}"/>
<path refid="jpa21.compile.path"/>
@@ -1786,6 +1817,7 @@
</target>
<!-- Default test target, run the FullRegressionTestSuite -->
+ <!-- persistence.xml with PU "default" is in resource/eclipselink-annotation-model -->
<target name="test" depends="clean-test, config-classpath">
<antcall target="run-test" inheritRefs="true">
<param name="TEST_CLASS" value="${test.class}"/>
@@ -1943,7 +1975,20 @@
</antcall>
</target>
- <!-- Run dynamic entities tests standalone and generate tests report. -->
+ <!-- Run dynamic entities tests standalone. -->
+ <target name="test-identity" depends="config-classpath">
+ <antcall target="run-test" inheritRefs="true">
+ <param name="TEST_CLASS" value="org.eclipse.persistence.testing.tests.jpa22.identity.IdentityTest"/>
+ <param name="TEST_AGENT" value="${test.agent}"/>
+ <param name="TEST_WEAVING" value="${test.weaving}"/>
+ <param name="TEST_WEAVING_OPTION" value="${test.weaving.option}"/>
+ <param name="ORM_TESTING" value="-Dorm.testing=eclipselink"/>
+ <param name="RUN_PATH" value="run.identity.classpath"/>
+ <param name="DB_URL" value="${db.url}"/>
+ </antcall>
+ </target>
+
+ <!-- Run dynamic entities tests standalone and generate tests report. -->
<target name="test-dynamic-report" depends="test-dynamic, generate-report"/>
<!-- Runs JPA 2.0 BeanValidation tests. -->
diff --git a/jpa/eclipselink.jpa.test/resource/eclipselink-identity-model/persistence.xml b/jpa/eclipselink.jpa.test/resource/eclipselink-identity-model/persistence.xml
new file mode 100644
index 0000000..6fe4d91
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/resource/eclipselink-identity-model/persistence.xml
@@ -0,0 +1,15 @@
+<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence persistence_1_0.xsd" version="1.0">
+ <persistence-unit name="identity-pu" transaction-type="RESOURCE_LOCAL">
+ <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
+ <exclude-unlisted-classes>false</exclude-unlisted-classes>
+ <properties>
+ <property name="javax.persistence.jdbc.driver" value="@driverClass@"/>
+ <property name="javax.persistence.jdbc.url" value="@dbURL@"/>
+ <property name="javax.persistence.jdbc.user" value="@dbUser@"/>
+ <property name="javax.persistence.jdbc.password" value="@dbPassword@"/>
+ <property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
+ <!--property name="eclipselink.logging.level.sql" value="FINE"/-->
+ </properties>
+ <class>org.eclipse.persistence.testing.models.jpa.identity.Person</class>
+ </persistence-unit>
+</persistence>
diff --git a/jpa/eclipselink.jpa.test/src/org/eclipse/persistence/testing/models/jpa22/identity/Person.java b/jpa/eclipselink.jpa.test/src/org/eclipse/persistence/testing/models/jpa22/identity/Person.java
new file mode 100644
index 0000000..a0c131a
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/org/eclipse/persistence/testing/models/jpa22/identity/Person.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
+ * which accompanies this distribution.
+ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * 09/29/2016-2.7 Tomas Kraus
+ * - 426852: @GeneratedValue(strategy=GenerationType.IDENTITY) support in Oracle 12c
+ ******************************************************************************/
+package org.eclipse.persistence.testing.models.jpa22.identity;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+/**
+ * Simple entity with ID as generated value using identity column
+ */
+@Entity
+@Table(name="MOD_IDENT_PERSON")
+public class Person {
+
+ @Id
+ @GeneratedValue(strategy=GenerationType.IDENTITY)
+ private int id;
+
+ @Column
+ private String firstName;
+
+ @Column String secondName;
+
+ public Person() {
+ }
+
+ public Person(final String firstName, final String secondName) {
+ this.firstName = firstName;
+ this.secondName = secondName;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setSecondName(final String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getSecondName() {
+ return secondName;
+ }
+
+ public void setFirstName(final String secondName) {
+ this.secondName = secondName;
+ }
+
+}
diff --git a/jpa/eclipselink.jpa.test/src/org/eclipse/persistence/testing/tests/jpa22/identity/IdentityTest.java b/jpa/eclipselink.jpa.test/src/org/eclipse/persistence/testing/tests/jpa22/identity/IdentityTest.java
new file mode 100644
index 0000000..943bfbb
--- /dev/null
+++ b/jpa/eclipselink.jpa.test/src/org/eclipse/persistence/testing/tests/jpa22/identity/IdentityTest.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved.
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
+ * which accompanies this distribution.
+ * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
+ * and the Eclipse Distribution License is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * Contributors:
+ * 09/29/2016-2.7 Tomas Kraus
+ * - 426852: @GeneratedValue(strategy=GenerationType.IDENTITY) support in Oracle 12c
+ ******************************************************************************/
+package org.eclipse.persistence.testing.tests.jpa22.identity;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.EntityTransaction;
+import javax.persistence.PersistenceException;
+import javax.persistence.TypedQuery;
+
+import org.eclipse.persistence.internal.databaseaccess.DatasourcePlatform;
+import org.eclipse.persistence.platform.database.DatabasePlatform;
+import org.eclipse.persistence.sessions.server.ServerSession;
+import org.eclipse.persistence.testing.framework.junit.JUnitTestCase;
+import org.eclipse.persistence.testing.models.jpa22.identity.Person;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * Test proper identity column value generation.
+ */
+public class IdentityTest {
+
+ /** Name of persistence unit used in test. */
+ private static final String PU_NAME = "identity-pu";
+
+ /** Entity manager factory. */
+ private static EntityManagerFactory EMF;
+
+ /** Database platform. */
+ private static DatabasePlatform DBP;
+
+ /** Entity manager. */
+ private EntityManager em;
+
+ /**
+ * Initialize test static content.
+ */
+ @BeforeClass
+ public static void setupClass() {
+ EMF = JUnitTestCase.getEntityManagerFactory(PU_NAME);
+ DBP = EMF.<ServerSession>unwrap(ServerSession.class).getPlatform();
+ }
+
+ /**
+ * Destroy test static content.
+ */
+ @AfterClass
+ public static void cleanupClass() {
+ EMF = null;
+ DBP = null;
+ }
+
+ /**
+ * Initialize test environment.
+ */
+ @Before
+ public void setup() {
+ em = EMF.createEntityManager();
+ }
+
+ /**
+ * Destroy test environment.
+ */
+ @After
+ public void cleanup() {
+ if (em != null) {
+ em.close();
+ }
+ }
+
+ /**
+ * Test identity column value generation.
+ */
+ @Test
+ public void testIdentity() {
+ if (!DBP.supportsIdentity()) {
+ return;
+ }
+ EntityTransaction t = em.getTransaction();
+ final Person p1 = new Person("John", "Smith");
+ final Person p2 = new Person("Bob", "Brown");
+ t.begin();
+ try {
+ em.persist(p1);
+ em.persist(p2);
+ t.commit();
+ } catch (PersistenceException | IllegalArgumentException ex) {
+ if (t.isActive()) {
+ t.rollback();
+ }
+ ex.printStackTrace();
+ throw ex;
+ }
+ final Map<String, Person> pMap = new HashMap<>(2);
+ pMap.put(p1.getSecondName(), p1);
+ pMap.put(p2.getSecondName(), p2);
+ final TypedQuery<Person> pQuery = em.createQuery("SELECT p FROM Person p", Person.class);
+ final List<Person> pList = pQuery.getResultList();
+ for (final Person p : pList) {
+ final Person pV = pMap.get(p.getSecondName());
+ assertEquals(p.getId(), pV.getId());
+ }
+ }
+
+}
diff --git a/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/EntityManagerSetupImpl.java b/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/EntityManagerSetupImpl.java
index bfebad2..97a5121 100644
--- a/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/EntityManagerSetupImpl.java
+++ b/jpa/org.eclipse.persistence.jpa/src/org/eclipse/persistence/internal/jpa/EntityManagerSetupImpl.java
@@ -72,6 +72,8 @@
* - 480787 : Wrap several privileged method calls with a doPrivileged block
* 12/03/2015-2.6 Dalia Abo Sheasha
* - 483582: Add the javax.persistence.sharedCache.mode property
+ * 09/29/2016-2.7 Tomas Kraus
+ * - 426852: @GeneratedValue(strategy=GenerationType.IDENTITY) support in Oracle 12c
*****************************************************************************/
package org.eclipse.persistence.internal.jpa;
@@ -802,6 +804,8 @@ public Object handleException(RuntimeException exception) {
writeDDL(deployProperties, getDatabaseSession(deployProperties), classLoaderToUse);
}
}
+ // Initialize platform specific identity sequences.
+ session.getDatasourcePlatform().initIdentitySequences(getDatabaseSession(), MetadataProject.DEFAULT_IDENTITY_GENERATOR);
updateTunerPostDeploy(deployProperties, classLoaderToUse);
this.deployLock.release();
isLockAcquired = false;