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 { @@ -64,7 +64,7 @@ class IdGeneratingBatchInsertStrategy implements BatchInsertStrategy {
IdGeneration idGeneration = dialect.getIdGeneration();
if (idGeneration.driverRequiresKeyColumnNames()) {
String[] keyColumnNames = getKeyColumnNames();
String[] keyColumnNames = getKeyColumnNames(idGeneration);
if (keyColumnNames.length == 0) {
batchJdbcOperations.batchUpdate(sql, sqlParameterSources, holder);
} else {
@ -91,10 +91,10 @@ class IdGeneratingBatchInsertStrategy implements BatchInsertStrategy { @@ -91,10 +91,10 @@ class IdGeneratingBatchInsertStrategy implements BatchInsertStrategy {
return ids;
}
private String[] getKeyColumnNames() {
private String[] getKeyColumnNames(IdGeneration idGeneration) {
return Optional.ofNullable(idColumn)
.map(idColumn -> new String[] { idColumn.getReference() })
.map(idColumn -> new String[] {idGeneration.getKeyColumnName( idColumn) })
.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 { @@ -58,7 +58,7 @@ class IdGeneratingInsertStrategy implements InsertStrategy {
if (idGeneration.driverRequiresKeyColumnNames()) {
String[] keyColumnNames = getKeyColumnNames();
String[] keyColumnNames = getKeyColumnNames(idGeneration);
if (keyColumnNames.length == 0) {
jdbcOperations.update(sql, sqlParameterSource, holder);
} else {
@ -84,8 +84,8 @@ class IdGeneratingInsertStrategy implements InsertStrategy { @@ -84,8 +84,8 @@ class IdGeneratingInsertStrategy implements InsertStrategy {
}
}
private String[] getKeyColumnNames() {
return Optional.ofNullable(idColumn).map(idColumn -> new String[] { idColumn.getReference() })
private String[] getKeyColumnNames(IdGeneration idGeneration) {
return Optional.ofNullable(idColumn).map(idColumn -> new String[] { idGeneration.getKeyColumnName(idColumn) })
.orElse(new String[0]);
}
}

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

@ -198,7 +198,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -198,7 +198,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithReferencedEntityById() {
template.save(legoSet);
@ -219,7 +218,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -219,7 +218,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadManyEntitiesWithReferencedEntity() {
template.save(legoSet);
@ -232,7 +230,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -232,7 +230,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-101
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadManyEntitiesWithReferencedEntitySorted() {
template.save(createLegoSet("Lava"));
@ -247,7 +244,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -247,7 +244,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-101
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadManyEntitiesWithReferencedEntitySortedAndPaged() {
template.save(createLegoSet("Lava"));
@ -262,7 +258,7 @@ class JdbcAggregateTemplateIntegrationTests { @@ -262,7 +258,7 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // GH-821
@EnabledOnFeature({ SUPPORTS_QUOTED_IDS, SUPPORTS_NULL_PRECEDENCE })
@EnabledOnFeature(SUPPORTS_NULL_PRECEDENCE)
void saveAndLoadManyEntitiesWithReferencedEntitySortedWithNullPrecedence() {
template.save(createLegoSet(null));
@ -279,7 +275,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -279,7 +275,6 @@ class JdbcAggregateTemplateIntegrationTests {
@Test //
@EnabledOnFeature({ SUPPORTS_QUOTED_IDS})
void findByNonPropertySortFails() {
assertThatThrownBy(() -> template.findAll(LegoSet.class,
@ -289,7 +284,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -289,7 +284,6 @@ class JdbcAggregateTemplateIntegrationTests {
@Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadManyEntitiesByIdWithReferencedEntity() {
template.save(legoSet);
@ -301,7 +295,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -301,7 +295,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithReferencedNullEntity() {
legoSet.setManual(null);
@ -314,7 +307,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -314,7 +307,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndDeleteAnEntityWithReferencedEntity() {
template.save(legoSet);
@ -329,7 +321,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -329,7 +321,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndDeleteAllWithReferencedEntity() {
template.save(legoSet);
@ -345,7 +336,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -345,7 +336,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // GH-537
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndDeleteAllByAggregateRootsWithReferencedEntity() {
LegoSet legoSet1 = template.save(legoSet);
@ -362,7 +352,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -362,7 +352,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // GH-537
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndDeleteAllByIdsWithReferencedEntity() {
LegoSet legoSet1 = template.save(legoSet);
@ -423,7 +412,7 @@ class JdbcAggregateTemplateIntegrationTests { @@ -423,7 +412,7 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-112
@EnabledOnFeature({ SUPPORTS_QUOTED_IDS, SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES })
@EnabledOnFeature(SUPPORTS_GENERATED_IDS_IN_REFERENCED_ENTITIES)
void updateReferencedEntityFromNull() {
legoSet.setManual(null);
@ -442,7 +431,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -442,7 +431,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void updateReferencedEntityToNull() {
template.save(legoSet);
@ -473,7 +461,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -473,7 +461,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-112
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void replaceReferencedEntity() {
template.save(legoSet);
@ -494,7 +481,7 @@ class JdbcAggregateTemplateIntegrationTests { @@ -494,7 +481,7 @@ class JdbcAggregateTemplateIntegrationTests {
}
@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() {
template.save(legoSet);
@ -509,7 +496,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -509,7 +496,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-266
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void oneToOneChildWithoutId() {
OneToOneParent parent = new OneToOneParent();
@ -526,7 +512,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -526,7 +512,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-266
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void oneToOneNullChildWithoutId() {
OneToOneParent parent = new OneToOneParent();
@ -542,7 +527,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -542,7 +527,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-266
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void oneToOneNullAttributes() {
OneToOneParent parent = new OneToOneParent();
@ -558,7 +542,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -558,7 +542,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-125
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithSecondaryReferenceNull() {
template.save(legoSet);
@ -571,7 +554,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -571,7 +554,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-125
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithSecondaryReferenceNotNull() {
legoSet.alternativeInstructions = new Manual();
@ -593,7 +575,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -593,7 +575,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-276
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithListOfElementsWithoutId() {
ListParent entity = new ListParent();
@ -612,7 +593,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -612,7 +593,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // GH-498 DATAJDBC-273
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadAnEntityWithListOfElementsInConstructor() {
ElementNoId element = new ElementNoId();
@ -752,7 +732,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -752,7 +732,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-340
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadLongChain() {
Chain4 chain4 = new Chain4();
@ -781,7 +760,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -781,7 +760,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-359
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void saveAndLoadLongChainWithoutIds() {
NoIdChain4 chain4 = new NoIdChain4();
@ -1012,7 +990,6 @@ class JdbcAggregateTemplateIntegrationTests { @@ -1012,7 +990,6 @@ class JdbcAggregateTemplateIntegrationTests {
}
@Test // DATAJDBC-462
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
void resavingAnUnversionedEntity() {
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 { @@ -55,7 +55,6 @@ public class JdbcAggregateTemplateSchemaIntegrationTests {
@Autowired NamedParameterJdbcOperations jdbcTemplate;
@Test
@EnabledOnFeature(SUPPORTS_QUOTED_IDS)
public void insertFindUpdateDelete() {
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 { @@ -40,7 +40,7 @@ public class TestDatabaseFeatures {
String productName = jdbcTemplate.execute(
(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 { @@ -50,15 +50,6 @@ public class TestDatabaseFeatures {
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
* 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 { @@ -115,7 +106,6 @@ public class TestDatabaseFeatures {
public enum Feature {
SUPPORTS_MULTIDIMENSIONAL_ARRAYS(TestDatabaseFeatures::supportsMultiDimensionalArrays), //
SUPPORTS_QUOTED_IDS(TestDatabaseFeatures::supportsQuotedIds), //
SUPPORTS_HUGE_NUMBERS(TestDatabaseFeatures::supportsHugeNumbers), //
SUPPORTS_ARRAYS(TestDatabaseFeatures::supportsArrays), //
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 @@ -43,7 +43,7 @@ CREATE TABLE MANUAL
(
"id2" NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY,
LEGO_SET NUMBER,
ALTERNATIVE NUMBER,
"alternative" NUMBER,
CONTENT VARCHAR(2000)
);
@ -59,7 +59,7 @@ CREATE TABLE ONE_TO_ONE_PARENT @@ -59,7 +59,7 @@ CREATE TABLE ONE_TO_ONE_PARENT
CREATE TABLE Child_No_Id
(
ONE_TO_ONE_PARENT INTEGER PRIMARY KEY,
"content" VARCHAR(30)
CONTENT VARCHAR(30)
);
CREATE TABLE LIST_PARENT

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

@ -1,8 +1,9 @@ @@ -1,8 +1,9 @@
DROP USER OTHER CASCADE;
CREATE USER OTHER;
ALTER USER OTHER QUOTA UNLIMITED ON USERS;
CREATE TABLE OTHER.DUMMY_ENTITY
(
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; @@ -18,6 +18,8 @@ package org.springframework.data.relational.core.dialect;
import java.sql.Connection;
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.
*
@ -45,6 +47,18 @@ public interface IdGeneration { @@ -45,6 +47,18 @@ public interface IdGeneration {
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.
* <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; @@ -18,6 +18,7 @@ package org.springframework.data.relational.core.dialect;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import java.util.Collection;
import java.util.Collections;
@ -42,6 +43,11 @@ public class OracleDialect extends AnsiDialect { @@ -42,6 +43,11 @@ public class OracleDialect extends AnsiDialect {
public boolean driverRequiresKeyColumnNames() {
return true;
}
@Override
public String getKeyColumnName(SqlIdentifier id) {
return id.toSql(INSTANCE.getIdentifierProcessing());
}
};
protected OracleDialect() {}

Loading…
Cancel
Save