Browse Source

Support for Id generation in Oracle using quoted identifiers.

The latest Oracle JDBC driver properly supports returning of generated ids,
both in batches and for quoted identifiers.

This allows us to now support this feature.

Closes #1666
Original pull request: #1667
3.1.x
Jens Schauder 2 years ago committed by Mark Paluch
parent
commit
fb8ef0f6e0
No known key found for this signature in database
GPG Key ID: 4406B84C1661DCD1
  1. 6
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingBatchInsertStrategy.java
  2. 6
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingInsertStrategy.java
  3. 29
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java
  4. 1
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateSchemaIntegrationTests.java
  5. 12
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestDatabaseFeatures.java
  6. 4
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql
  7. 3
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateSchemaIntegrationTests-oracle.sql
  8. 14
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/IdGeneration.java
  9. 6
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java

6
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingBatchInsertStrategy.java

@ -64,7 +64,7 @@ class IdGeneratingBatchInsertStrategy implements BatchInsertStrategy {
IdGeneration idGeneration = dialect.getIdGeneration(); IdGeneration idGeneration = dialect.getIdGeneration();
if (idGeneration.driverRequiresKeyColumnNames()) { if (idGeneration.driverRequiresKeyColumnNames()) {
String[] keyColumnNames = getKeyColumnNames(); String[] keyColumnNames = getKeyColumnNames(idGeneration);
if (keyColumnNames.length == 0) { if (keyColumnNames.length == 0) {
batchJdbcOperations.batchUpdate(sql, sqlParameterSources, holder); batchJdbcOperations.batchUpdate(sql, sqlParameterSources, holder);
} else { } else {
@ -91,10 +91,10 @@ class IdGeneratingBatchInsertStrategy implements BatchInsertStrategy {
return ids; return ids;
} }
private String[] getKeyColumnNames() { private String[] getKeyColumnNames(IdGeneration idGeneration) {
return Optional.ofNullable(idColumn) return Optional.ofNullable(idColumn)
.map(idColumn -> new String[] { idColumn.getReference() }) .map(idColumn -> new String[] {idGeneration.getKeyColumnName( idColumn) })
.orElse(new String[0]); .orElse(new String[0]);
} }
} }

6
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingInsertStrategy.java

@ -58,7 +58,7 @@ class IdGeneratingInsertStrategy implements InsertStrategy {
if (idGeneration.driverRequiresKeyColumnNames()) { if (idGeneration.driverRequiresKeyColumnNames()) {
String[] keyColumnNames = getKeyColumnNames(); String[] keyColumnNames = getKeyColumnNames(idGeneration);
if (keyColumnNames.length == 0) { if (keyColumnNames.length == 0) {
jdbcOperations.update(sql, sqlParameterSource, holder); jdbcOperations.update(sql, sqlParameterSource, holder);
} else { } else {
@ -84,8 +84,8 @@ class IdGeneratingInsertStrategy implements InsertStrategy {
} }
} }
private String[] getKeyColumnNames() { private String[] getKeyColumnNames(IdGeneration idGeneration) {
return Optional.ofNullable(idColumn).map(idColumn -> new String[] { idColumn.getReference() }) return Optional.ofNullable(idColumn).map(idColumn -> new String[] { idGeneration.getKeyColumnName(idColumn) })
.orElse(new String[0]); .orElse(new String[0]);
} }
} }

29
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java

@ -198,7 +198,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-112 @Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithReferencedEntityById() { void saveAndLoadAnEntityWithReferencedEntityById() {
template.save(legoSet); template.save(legoSet);
@ -219,7 +218,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-112 @Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadManyEntitiesWithReferencedEntity() { void saveAndLoadManyEntitiesWithReferencedEntity() {
template.save(legoSet); template.save(legoSet);
@ -232,7 +230,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-101 @Test // DATAJDBC-101
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadManyEntitiesWithReferencedEntitySorted() { void saveAndLoadManyEntitiesWithReferencedEntitySorted() {
template.save(createLegoSet("Lava")); template.save(createLegoSet("Lava"));
@ -247,7 +244,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-101 @Test // DATAJDBC-101
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadManyEntitiesWithReferencedEntitySortedAndPaged() { void saveAndLoadManyEntitiesWithReferencedEntitySortedAndPaged() {
template.save(createLegoSet("Lava")); template.save(createLegoSet("Lava"));
@ -262,7 +258,7 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // GH-821 @Test // GH-821
@EnabledOnFeature({ SUPPORTS_QUOTED_IDS, SUPPORTS_NULL_PRECEDENCE }) @EnabledOnFeature(SUPPORTS_NULL_PRECEDENCE)
void saveAndLoadManyEntitiesWithReferencedEntitySortedWithNullPrecedence() { void saveAndLoadManyEntitiesWithReferencedEntitySortedWithNullPrecedence() {
template.save(createLegoSet(null)); template.save(createLegoSet(null));
@ -279,7 +275,6 @@ class JdbcAggregateTemplateIntegrationTests {
@Test // @Test //
@EnabledOnFeature({ SUPPORTS_QUOTED_IDS})
void findByNonPropertySortFails() { void findByNonPropertySortFails() {
assertThatThrownBy(() -> template.findAll(LegoSet.class, assertThatThrownBy(() -> template.findAll(LegoSet.class,
@ -289,7 +284,6 @@ class JdbcAggregateTemplateIntegrationTests {
@Test // DATAJDBC-112 @Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadManyEntitiesByIdWithReferencedEntity() { void saveAndLoadManyEntitiesByIdWithReferencedEntity() {
template.save(legoSet); template.save(legoSet);
@ -301,7 +295,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-112 @Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithReferencedNullEntity() { void saveAndLoadAnEntityWithReferencedNullEntity() {
legoSet.setManual(null); legoSet.setManual(null);
@ -314,7 +307,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-112 @Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndDeleteAnEntityWithReferencedEntity() { void saveAndDeleteAnEntityWithReferencedEntity() {
template.save(legoSet); template.save(legoSet);
@ -329,7 +321,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-112 @Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndDeleteAllWithReferencedEntity() { void saveAndDeleteAllWithReferencedEntity() {
template.save(legoSet); template.save(legoSet);
@ -345,7 +336,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // GH-537 @Test // GH-537
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndDeleteAllByAggregateRootsWithReferencedEntity() { void saveAndDeleteAllByAggregateRootsWithReferencedEntity() {
LegoSet legoSet1 = template.save(legoSet); LegoSet legoSet1 = template.save(legoSet);
@ -362,7 +352,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // GH-537 @Test // GH-537
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndDeleteAllByIdsWithReferencedEntity() { void saveAndDeleteAllByIdsWithReferencedEntity() {
LegoSet legoSet1 = template.save(legoSet); LegoSet legoSet1 = template.save(legoSet);
@ -423,7 +412,7 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-112 @Test // DATAJDBC-112
@EnabledOnFeature({ SUPPORTS_QUOTED_IDS, SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES }) @EnabledOnFeature(SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES)
void updateReferencedEntityFromNull() { void updateReferencedEntityFromNull() {
legoSet.setManual(null); legoSet.setManual(null);
@ -442,7 +431,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-112 @Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void updateReferencedEntityToNull() { void updateReferencedEntityToNull() {
template.save(legoSet); template.save(legoSet);
@ -473,7 +461,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-112 @Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void replaceReferencedEntity() { void replaceReferencedEntity() {
template.save(legoSet); template.save(legoSet);
@ -494,7 +481,7 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-112 @Test // DATAJDBC-112
@EnabledOnFeature({ SUPPORTS_QUOTED_IDS, TestDatabaseFeatures.Feature.SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES }) @EnabledOnFeature(TestDatabaseFeatures.Feature.SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES)
void changeReferencedEntity() { void changeReferencedEntity() {
template.save(legoSet); template.save(legoSet);
@ -509,7 +496,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-266 @Test // DATAJDBC-266
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void oneToOneChildWithoutId() { void oneToOneChildWithoutId() {
OneToOneParent parent = new OneToOneParent(); OneToOneParent parent = new OneToOneParent();
@ -526,7 +512,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-266 @Test // DATAJDBC-266
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void oneToOneNullChildWithoutId() { void oneToOneNullChildWithoutId() {
OneToOneParent parent = new OneToOneParent(); OneToOneParent parent = new OneToOneParent();
@ -542,7 +527,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-266 @Test // DATAJDBC-266
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void oneToOneNullAttributes() { void oneToOneNullAttributes() {
OneToOneParent parent = new OneToOneParent(); OneToOneParent parent = new OneToOneParent();
@ -558,7 +542,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-125 @Test // DATAJDBC-125
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithSecondaryReferenceNull() { void saveAndLoadAnEntityWithSecondaryReferenceNull() {
template.save(legoSet); template.save(legoSet);
@ -571,7 +554,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-125 @Test // DATAJDBC-125
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithSecondaryReferenceNotNull() { void saveAndLoadAnEntityWithSecondaryReferenceNotNull() {
legoSet.alternativeInstructions = new Manual(); legoSet.alternativeInstructions = new Manual();
@ -593,7 +575,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-276 @Test // DATAJDBC-276
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithListOfElementsWithoutId() { void saveAndLoadAnEntityWithListOfElementsWithoutId() {
ListParent entity = new ListParent(); ListParent entity = new ListParent();
@ -612,7 +593,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // GH-498 DATAJDBC-273 @Test // GH-498 DATAJDBC-273
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithListOfElementsInConstructor() { void saveAndLoadAnEntityWithListOfElementsInConstructor() {
ElementNoId element = new ElementNoId(); ElementNoId element = new ElementNoId();
@ -752,7 +732,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-340 @Test // DATAJDBC-340
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadLongChain() { void saveAndLoadLongChain() {
Chain4 chain4 = new Chain4(); Chain4 chain4 = new Chain4();
@ -781,7 +760,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-359 @Test // DATAJDBC-359
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadLongChainWithoutIds() { void saveAndLoadLongChainWithoutIds() {
NoIdChain4 chain4 = new NoIdChain4(); NoIdChain4 chain4 = new NoIdChain4();
@ -1012,7 +990,6 @@ class JdbcAggregateTemplateIntegrationTests {
} }
@Test // DATAJDBC-462 @Test // DATAJDBC-462
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void resavingAnUnversionedEntity() { void resavingAnUnversionedEntity() {
LegoSet legoSet = new LegoSet(); LegoSet legoSet = new LegoSet();

1
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateSchemaIntegrationTests.java

@ -55,7 +55,6 @@ public class JdbcAggregateTemplateSchemaIntegrationTests {
@Autowired NamedParameterJdbcOperations jdbcTemplate; @Autowired NamedParameterJdbcOperations jdbcTemplate;
@Test @Test
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
public void insertFindUpdateDelete() { public void insertFindUpdateDelete() {
DummyEntity entity = new DummyEntity(); DummyEntity entity = new DummyEntity();

12
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestDatabaseFeatures.java

@ -40,7 +40,7 @@ public class TestDatabaseFeatures {
String productName = jdbcTemplate.execute( String productName = jdbcTemplate.execute(
(ConnectionCallback<String>) c -> c.getMetaData().getDatabaseProductName().toLowerCase(Locale.ENGLISH)); (ConnectionCallback<String>) c -> c.getMetaData().getDatabaseProductName().toLowerCase(Locale.ENGLISH));
database = Arrays.stream(Database.values()).filter(db -> db.matches(productName)).findFirst().get(); database = Arrays.stream(Database.values()).filter(db -> db.matches(productName)).findFirst().orElseThrow();
} }
/** /**
@ -50,15 +50,6 @@ public class TestDatabaseFeatures {
assumeThat(database).isNotIn(Database.Oracle, Database.SqlServer); assumeThat(database).isNotIn(Database.Oracle, Database.SqlServer);
} }
/**
* Oracles JDBC driver seems to have a bug that makes it impossible to acquire generated keys when the column is
* quoted. See
* https://stackoverflow.com/questions/62263576/how-to-get-the-generated-key-for-a-column-with-lowercase-characters-from-oracle
*/
private void supportsQuotedIds() {
assumeThat(database).isNotEqualTo(Database.Oracle);
}
/** /**
* Microsoft SqlServer does not allow explicitly setting ids in columns where the value gets generated by the * Microsoft SqlServer does not allow explicitly setting ids in columns where the value gets generated by the
* database. Such columns therefore must not be used in referenced entities, since we do a delete and insert, which * database. Such columns therefore must not be used in referenced entities, since we do a delete and insert, which
@ -115,7 +106,6 @@ public class TestDatabaseFeatures {
public enum Feature { public enum Feature {
SUPPORTS_MULTIDIMENSIONAL_ARRAYS(TestDatabaseFeatures::supportsMultiDimensionalArrays), // SUPPORTS_MULTIDIMENSIONAL_ARRAYS(TestDatabaseFeatures::supportsMultiDimensionalArrays), //
SUPPORTS_QUOTED_IDS(TestDatabaseFeatures::supportsQuotedIds), //
SUPPORTS_HUGE_NUMBERS(TestDatabaseFeatures::supportsHugeNumbers), // SUPPORTS_HUGE_NUMBERS(TestDatabaseFeatures::supportsHugeNumbers), //
SUPPORTS_ARRAYS(TestDatabaseFeatures::supportsArrays), // SUPPORTS_ARRAYS(TestDatabaseFeatures::supportsArrays), //
SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES(TestDatabaseFeatures::supportsGeneratedIdsInReferencedEntities), // SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES(TestDatabaseFeatures::supportsGeneratedIdsInReferencedEntities), //

4
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql

@ -43,7 +43,7 @@ CREATE TABLE MANUAL
( (
"id2" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, "id2" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
LEGO_SET NUMBER, LEGO_SET NUMBER,
ALTERNATIVE NUMBER, "alternative" NUMBER,
CONTENT VARCHAR(2000) CONTENT VARCHAR(2000)
); );
@ -59,7 +59,7 @@ CREATE TABLE ONE_TO_ONE_PARENT
CREATE TABLE Child_No_Id CREATE TABLE Child_No_Id
( (
ONE_TO_ONE_PARENT INTEGER PRIMARY KEY, ONE_TO_ONE_PARENT INTEGER PRIMARY KEY,
"content" VARCHAR(30) CONTENT VARCHAR(30)
); );
CREATE TABLE LIST_PARENT CREATE TABLE LIST_PARENT

3
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateSchemaIntegrationTests-oracle.sql

@ -1,8 +1,9 @@
DROP USER OTHER CASCADE; DROP USER OTHER CASCADE;
CREATE USER OTHER; CREATE USER OTHER;
ALTER USER OTHER QUOTA UNLIMITED ON USERS;
CREATE TABLE OTHER.DUMMY_ENTITY CREATE TABLE OTHER.DUMMY_ENTITY
( (
ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY, ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,

14
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/IdGeneration.java

@ -18,6 +18,8 @@ package org.springframework.data.relational.core.dialect;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import org.springframework.data.relational.core.sql.SqlIdentifier;
/** /**
* Describes how obtaining generated ids after an insert works for a given JDBC driver. * Describes how obtaining generated ids after an insert works for a given JDBC driver.
* *
@ -45,6 +47,18 @@ public interface IdGeneration {
return false; return false;
} }
/**
* Provides for a given id {@link SqlIdentifier} the String that is to be used for registering interest in the
* generated value of that column.
*
* @param id {@link SqlIdentifier} representing a column for which a generated value is to be obtained.
* @return a String representing that column in the way expected by the JDBC driver.
* @since 3.3
*/
default String getKeyColumnName(SqlIdentifier id) {
return id.getReference();
}
/** /**
* Does the driver support id generation for batch operations. * Does the driver support id generation for batch operations.
* <p> * <p>

6
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java

@ -18,6 +18,7 @@ package org.springframework.data.relational.core.dialect;
import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter; import org.springframework.data.convert.WritingConverter;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
@ -42,6 +43,11 @@ public class OracleDialect extends AnsiDialect {
public boolean driverRequiresKeyColumnNames() { public boolean driverRequiresKeyColumnNames() {
return true; return true;
} }
@Override
public String getKeyColumnName(SqlIdentifier id) {
return id.toSql(INSTANCE.getIdentifierProcessing());
}
}; };
protected OracleDialect() {} protected OracleDialect() {}

Loading…
Cancel
Save