Browse Source

Polishing.

Reference issues in tests comments.
Removed `DisabledOnDatabase`
IdGeneration default methods related to sequence generation are now internally consistent.
Formatting and naming.
IdGeneration offers simple support by default.
Fix exception in oracle integration test setup
Use SqlIdentifier for sequence names
Remove SEQUENCE id source
Added documentation

See #1923
Original pull request #1955
pull/2001/head
Jens Schauder 11 months ago
parent
commit
8c017fc56b
No known key found for this signature in database
GPG Key ID: 74F6C554AE971567
  1. 3
      pom.xml
  2. 11
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/IdGeneratingBeforeSaveCallback.java
  3. 32
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/IdGeneratingBeforeSaveCallbackTest.java
  4. 10
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java
  5. 27
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DisabledOnDatabase.java
  6. 36
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DisabledOnDatabaseExecutionCondition.java
  7. 2
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql
  8. 6
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql
  9. 2
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql
  10. 14
      spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/IdValueSource.java
  11. 21
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java
  12. 13
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/H2Dialect.java
  13. 11
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/HsqlDbDialect.java
  14. 46
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/IdGeneration.java
  15. 13
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java
  16. 17
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java
  17. 16
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/OracleDialect.java
  18. 20
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java
  19. 11
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java
  20. 50
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentEntity.java
  21. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentEntity.java
  22. 2
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntity.java
  23. 46
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Sequence.java
  24. 43
      spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/TargetSequence.java
  25. 46
      spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentEntityUnitTests.java
  26. 4
      src/main/antora/modules/ROOT/partials/id-generation.adoc

3
pom.xml

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>

11
spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/mapping/IdGeneratingBeforeSaveCallback.java

@ -11,6 +11,7 @@ import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback; import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -40,15 +41,17 @@ public class IdGeneratingBeforeSaveCallback implements BeforeSaveCallback<Object
@Override @Override
public Object onBeforeSave(Object aggregate, MutableAggregateChange<Object> aggregateChange) { public Object onBeforeSave(Object aggregate, MutableAggregateChange<Object> aggregateChange) {
Assert.notNull(aggregate, "The aggregate cannot be null at this point"); Assert.notNull(aggregate, "The aggregate cannot be null at this point");
RelationalPersistentEntity<?> persistentEntity = relationalMappingContext.getPersistentEntity(aggregate.getClass()); RelationalPersistentEntity<?> persistentEntity = relationalMappingContext.getPersistentEntity(aggregate.getClass());
Optional<String> idTargetSequence = persistentEntity.getIdTargetSequence(); Optional<SqlIdentifier> idSequence = persistentEntity.getIdSequence();
if (dialect.getIdGeneration().sequencesSupported()) { if (dialect.getIdGeneration().sequencesSupported()) {
if (persistentEntity.getIdProperty() != null) { if (persistentEntity.getIdProperty() != null) {
idTargetSequence idSequence
.map(s -> dialect.getIdGeneration().nextValueFromSequenceSelect(s)) .map(s -> dialect.getIdGeneration().createSequenceQuery(s))
.ifPresent(sql -> { .ifPresent(sql -> {
Long idValue = operations.queryForObject(sql, Map.of(), (rs, rowNum) -> rs.getLong(1)); Long idValue = operations.queryForObject(sql, Map.of(), (rs, rowNum) -> rs.getLong(1));
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(aggregate); PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(aggregate);
@ -56,7 +59,7 @@ public class IdGeneratingBeforeSaveCallback implements BeforeSaveCallback<Object
}); });
} }
} else { } else {
if (idTargetSequence.isPresent()) { if (idSequence.isPresent()) {
LOG.warn(""" LOG.warn("""
It seems you're trying to insert an aggregate of type '%s' annotated with @TargetSequence, but the problem is RDBMS you're It seems you're trying to insert an aggregate of type '%s' annotated with @TargetSequence, but the problem is RDBMS you're
working with does not support sequences as such. Falling back to identity columns working with does not support sequences as such. Falling back to identity columns

32
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/mapping/IdGeneratingBeforeSaveCallbackTest.java

@ -14,7 +14,7 @@ import org.springframework.data.relational.core.dialect.MySqlDialect;
import org.springframework.data.relational.core.dialect.PostgresDialect; import org.springframework.data.relational.core.dialect.PostgresDialect;
import org.springframework.data.relational.core.mapping.RelationalMappingContext; import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.mapping.TargetSequence; import org.springframework.data.relational.core.mapping.Sequence;
import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
@ -26,68 +26,58 @@ import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
*/ */
class IdGeneratingBeforeSaveCallbackTest { class IdGeneratingBeforeSaveCallbackTest {
@Test @Test // GH-1923
void test_mySqlDialect_sequenceGenerationIsNotSupported() { void mySqlDialectsequenceGenerationIsNotSupported() {
// given
RelationalMappingContext relationalMappingContext = new RelationalMappingContext(); RelationalMappingContext relationalMappingContext = new RelationalMappingContext();
MySqlDialect mySqlDialect = new MySqlDialect(IdentifierProcessing.NONE); MySqlDialect mySqlDialect = new MySqlDialect(IdentifierProcessing.NONE);
NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class); NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class);
// and
IdGeneratingBeforeSaveCallback subject = new IdGeneratingBeforeSaveCallback(relationalMappingContext, mySqlDialect, operations); IdGeneratingBeforeSaveCallback subject = new IdGeneratingBeforeSaveCallback(relationalMappingContext, mySqlDialect, operations);
NoSequenceEntity entity = new NoSequenceEntity(); NoSequenceEntity entity = new NoSequenceEntity();
// when
Object processed = subject.onBeforeSave(entity, MutableAggregateChange.forSave(entity)); Object processed = subject.onBeforeSave(entity, MutableAggregateChange.forSave(entity));
// then
Assertions.assertThat(processed).isSameAs(entity); Assertions.assertThat(processed).isSameAs(entity);
Assertions.assertThat(processed).usingRecursiveComparison().isEqualTo(entity); Assertions.assertThat(processed).usingRecursiveComparison().isEqualTo(entity);
} }
@Test @Test // GH-1923
void test_EntityIsNotMarkedWithTargetSequence() { void entityIsNotMarkedWithTargetSequence() {
// given
RelationalMappingContext relationalMappingContext = new RelationalMappingContext(); RelationalMappingContext relationalMappingContext = new RelationalMappingContext();
PostgresDialect mySqlDialect = PostgresDialect.INSTANCE; PostgresDialect mySqlDialect = PostgresDialect.INSTANCE;
NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class); NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class);
// and
IdGeneratingBeforeSaveCallback subject = new IdGeneratingBeforeSaveCallback(relationalMappingContext, mySqlDialect, operations); IdGeneratingBeforeSaveCallback subject = new IdGeneratingBeforeSaveCallback(relationalMappingContext, mySqlDialect, operations);
NoSequenceEntity entity = new NoSequenceEntity(); NoSequenceEntity entity = new NoSequenceEntity();
// when
Object processed = subject.onBeforeSave(entity, MutableAggregateChange.forSave(entity)); Object processed = subject.onBeforeSave(entity, MutableAggregateChange.forSave(entity));
// then
Assertions.assertThat(processed).isSameAs(entity); Assertions.assertThat(processed).isSameAs(entity);
Assertions.assertThat(processed).usingRecursiveComparison().isEqualTo(entity); Assertions.assertThat(processed).usingRecursiveComparison().isEqualTo(entity);
} }
@Test @Test // GH-1923
void test_EntityIdIsPopulatedFromSequence() { void entityIdIsPopulatedFromSequence() {
// given
RelationalMappingContext relationalMappingContext = new RelationalMappingContext(); RelationalMappingContext relationalMappingContext = new RelationalMappingContext();
relationalMappingContext.getRequiredPersistentEntity(EntityWithSequence.class); relationalMappingContext.getRequiredPersistentEntity(EntityWithSequence.class);
PostgresDialect mySqlDialect = PostgresDialect.INSTANCE; PostgresDialect mySqlDialect = PostgresDialect.INSTANCE;
NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class); NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class);
// and
long generatedId = 112L; long generatedId = 112L;
when(operations.queryForObject(anyString(), anyMap(), any(RowMapper.class))).thenReturn(generatedId); when(operations.queryForObject(anyString(), anyMap(), any(RowMapper.class))).thenReturn(generatedId);
// and
IdGeneratingBeforeSaveCallback subject = new IdGeneratingBeforeSaveCallback(relationalMappingContext, mySqlDialect, operations); IdGeneratingBeforeSaveCallback subject = new IdGeneratingBeforeSaveCallback(relationalMappingContext, mySqlDialect, operations);
EntityWithSequence entity = new EntityWithSequence(); EntityWithSequence entity = new EntityWithSequence();
// when
Object processed = subject.onBeforeSave(entity, MutableAggregateChange.forSave(entity)); Object processed = subject.onBeforeSave(entity, MutableAggregateChange.forSave(entity));
// then
Assertions.assertThat(processed).isSameAs(entity); Assertions.assertThat(processed).isSameAs(entity);
Assertions Assertions
.assertThat(processed) .assertThat(processed)
@ -109,7 +99,7 @@ class IdGeneratingBeforeSaveCallbackTest {
static class EntityWithSequence { static class EntityWithSequence {
@Id @Id
@TargetSequence(value = "id_seq", schema = "public") @Sequence(value = "id_seq", schema = "public")
private Long id; private Long id;
private Long name; private Long name;

10
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java

@ -65,7 +65,7 @@ import org.springframework.data.jdbc.testing.TestDatabaseFeatures;
import org.springframework.data.relational.core.mapping.Column; import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.MappedCollection; import org.springframework.data.relational.core.mapping.MappedCollection;
import org.springframework.data.relational.core.mapping.Table; import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.relational.core.mapping.TargetSequence; import org.springframework.data.relational.core.mapping.Sequence;
import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent; import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent;
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent; import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
import org.springframework.data.relational.core.sql.LockMode; import org.springframework.data.relational.core.sql.LockMode;
@ -126,9 +126,10 @@ public class JdbcRepositoryIntegrationTests {
"id_Prop = " + entity.getIdProp())).isEqualTo(1); "id_Prop = " + entity.getIdProp())).isEqualTo(1);
} }
@Test @Test // GH-1923
@EnabledOnFeature(value = TestDatabaseFeatures.Feature.SUPPORTS_SEQUENCES) @EnabledOnFeature(value = TestDatabaseFeatures.Feature.SUPPORTS_SEQUENCES)
public void saveEntityWithTargetSequenceSpecified() { public void saveEntityWithTargetSequenceSpecified() {
EntityWithSequence first = entityWithSequenceRepository.save(new EntityWithSequence("first")); EntityWithSequence first = entityWithSequenceRepository.save(new EntityWithSequence("first"));
EntityWithSequence second = entityWithSequenceRepository.save(new EntityWithSequence("second")); EntityWithSequence second = entityWithSequenceRepository.save(new EntityWithSequence("second"));
@ -139,9 +140,10 @@ public class JdbcRepositoryIntegrationTests {
assertThat(second.getName()).isEqualTo("second"); assertThat(second.getName()).isEqualTo("second");
} }
@Test @Test // GH-1923
@EnabledOnFeature(value = TestDatabaseFeatures.Feature.SUPPORTS_SEQUENCES) @EnabledOnFeature(value = TestDatabaseFeatures.Feature.SUPPORTS_SEQUENCES)
public void batchInsertEntityWithTargetSequenceSpecified() { public void batchInsertEntityWithTargetSequenceSpecified() {
Iterable<EntityWithSequence> results = entityWithSequenceRepository Iterable<EntityWithSequence> results = entityWithSequenceRepository
.saveAll(List.of(new EntityWithSequence("first"), new EntityWithSequence("second"))); .saveAll(List.of(new EntityWithSequence("first"), new EntityWithSequence("second")));
@ -1862,7 +1864,7 @@ public class JdbcRepositoryIntegrationTests {
static class EntityWithSequence { static class EntityWithSequence {
@Id @Id
@TargetSequence(sequence = "entity_sequence") private Long id; @Sequence(sequence = "ENTITY_SEQUENCE") private Long id;
private String name; private String name;

27
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DisabledOnDatabase.java

@ -1,27 +0,0 @@
package org.springframework.data.jdbc.testing;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.test.context.junit.jupiter.EnabledIf;
/**
* Annotation that allows to disable a particular test to be executed on a particular database
*
* @author Mikhail Polivakha
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@ExtendWith(DisabledOnDatabaseExecutionCondition.class)
public @interface DisabledOnDatabase {
/**
* The database on which the test is not supposed to run on
*/
DatabaseType database();
}

36
spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/DisabledOnDatabaseExecutionCondition.java

@ -1,36 +0,0 @@
package org.springframework.data.jdbc.testing;
import org.apache.commons.lang3.ArrayUtils;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.test.context.junit.jupiter.SpringExtension;
/**
* {@link ExecutionCondition} for the {@link DisabledOnDatabase} annotation
*
* @author Mikhail Polivakha
*/
public class DisabledOnDatabaseExecutionCondition implements ExecutionCondition {
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
ApplicationContext applicationContext = SpringExtension.getApplicationContext(context);
MergedAnnotation<DisabledOnDatabase> disabledOnDatabaseMergedAnnotation = MergedAnnotations
.from(context.getRequiredTestMethod(), MergedAnnotations.SearchStrategy.DIRECT)
.get(DisabledOnDatabase.class);
DatabaseType database = disabledOnDatabaseMergedAnnotation.getEnum("database", DatabaseType.class);
if (ArrayUtils.contains(applicationContext.getEnvironment().getActiveProfiles(), database.getProfile())) {
return ConditionEvaluationResult.disabled(
"The test method '%s' is disabled for '%s' because of the @DisabledOnDatabase annotation".formatted(context.getRequiredTestMethod().getName(), database)
);
}
return ConditionEvaluationResult.enabled("The test method '%s' is enabled".formatted(context.getRequiredTestMethod()));
}
}

2
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mariadb.sql

@ -47,4 +47,4 @@ CREATE TABLE ENTITY_WITH_SEQUENCE
NAME VARCHAR(100) NAME VARCHAR(100)
); );
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE; CREATE SEQUENCE `ENTITY_SEQUENCE` START WITH 1 INCREMENT BY 1 NO MAXVALUE;

6
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-oracle.sql

@ -47,12 +47,12 @@ CREATE TABLE WITH_DELIMITED_COLUMN
ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY, ID NUMBER GENERATED BY DEFAULT ON NULL AS IDENTITY PRIMARY KEY,
"ORG.XTUNIT.IDENTIFIER" VARCHAR(100), "ORG.XTUNIT.IDENTIFIER" VARCHAR(100),
STYPE VARCHAR(100) STYPE VARCHAR(100)
) );
CREATE TABLE ENTITY_WITH_SEQUENCE CREATE TABLE ENTITY_WITH_SEQUENCE
( (
ID BIGINT, ID NUMBER,
NAME VARCHAR(100) NAME VARCHAR(100)
); );
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE; CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1;

2
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql

@ -55,4 +55,4 @@ CREATE TABLE ENTITY_WITH_SEQUENCE
NAME VARCHAR(100) NAME VARCHAR(100)
); );
CREATE SEQUENCE ENTITY_SEQUENCE START WITH 1 INCREMENT BY 1 NO MAXVALUE; CREATE SEQUENCE "ENTITY_SEQUENCE" START WITH 1 INCREMENT BY 1 NO MAXVALUE;

14
spring-data-relational/src/main/java/org/springframework/data/relational/core/conversion/IdValueSource.java

@ -15,8 +15,6 @@
*/ */
package org.springframework.data.relational.core.conversion; package org.springframework.data.relational.core.conversion;
import java.util.Optional;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity; import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
@ -42,12 +40,7 @@ public enum IdValueSource {
/** /**
* There is no id property, and therefore no id value source. * There is no id property, and therefore no id value source.
*/ */
NONE, NONE;
/**
* The id should be dervied from the database sequence
*/
SEQUENCE;
/** /**
* Returns the appropriate {@link IdValueSource} for the instance: {@link IdValueSource#NONE} when the entity has no * Returns the appropriate {@link IdValueSource} for the instance: {@link IdValueSource#NONE} when the entity has no
@ -56,9 +49,8 @@ public enum IdValueSource {
*/ */
public static <T> IdValueSource forInstance(Object instance, RelationalPersistentEntity<T> persistentEntity) { public static <T> IdValueSource forInstance(Object instance, RelationalPersistentEntity<T> persistentEntity) {
Optional<String> idTargetSequence = persistentEntity.getIdTargetSequence(); if (persistentEntity.getIdSequence().isPresent()) {
if (idTargetSequence.isPresent()) { return IdValueSource.PROVIDED;
return IdValueSource.SEQUENCE;
} }
Object idValue = persistentEntity.getIdentifierAccessor(instance).getIdentifier(); Object idValue = persistentEntity.getIdentifierAccessor(instance).getIdentifier();

21
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java

@ -18,8 +18,8 @@ package org.springframework.data.relational.core.dialect;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.relational.core.sql.LockOptions; import org.springframework.data.relational.core.sql.LockOptions;
import org.springframework.data.relational.core.sql.SqlIdentifier;
/** /**
* An SQL dialect for DB2. * An SQL dialect for DB2.
@ -41,14 +41,20 @@ public class Db2Dialect extends AbstractDialect {
return false; return false;
} }
/** @Override
public boolean sequencesSupported() {
return true;
}
@Override
public String createSequenceQuery(SqlIdentifier sequenceName) {
/*
* This workaround (non-ANSI SQL way of querying sequence) exists for the same reasons it exists for {@link HsqlDbDialect} * This workaround (non-ANSI SQL way of querying sequence) exists for the same reasons it exists for {@link HsqlDbDialect}
* *
* @see HsqlDbDialect#getIdGeneration()#nextValueFromSequenceSelect(String) * @see HsqlDbDialect#getIdGeneration()#nextValueFromSequenceSelect(String)
*/ */
@Override return "SELECT NEXT VALUE FOR %s FROM SYSCAT.SEQUENCES LIMIT 1"
public String nextValueFromSequenceSelect(String sequenceName) { .formatted(sequenceName.toSql(INSTANCE.getIdentifierProcessing()));
return "SELECT NEXT VALUE FOR %s FROM SYSCAT.SEQUENCES LIMIT 1".formatted(sequenceName);
} }
}; };
@ -104,11 +110,6 @@ public class Db2Dialect extends AbstractDialect {
}; };
} }
@Override
public IdentifierProcessing getIdentifierProcessing() {
return IdentifierProcessing.ANSI;
}
@Override @Override
public Collection<Object> getConverters() { public Collection<Object> getConverters() {
return Collections.singletonList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE); return Collections.singletonList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE);

13
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/H2Dialect.java

@ -40,6 +40,9 @@ public class H2Dialect extends AbstractDialect {
* Singleton instance. * Singleton instance.
*/ */
public static final H2Dialect INSTANCE = new H2Dialect(); public static final H2Dialect INSTANCE = new H2Dialect();
private static final IdentifierProcessing IDENTIFIER_PROCESSING = IdentifierProcessing.create(Quoting.ANSI,
LetterCasing.UPPER_CASE);
private static final IdGeneration ID_GENERATION = IdGeneration.create(IDENTIFIER_PROCESSING);
protected H2Dialect() {} protected H2Dialect() {}
@ -101,7 +104,7 @@ public class H2Dialect extends AbstractDialect {
@Override @Override
public IdentifierProcessing getIdentifierProcessing() { public IdentifierProcessing getIdentifierProcessing() {
return IdentifierProcessing.create(Quoting.ANSI, LetterCasing.UPPER_CASE); return IDENTIFIER_PROCESSING;
} }
@Override @Override
@ -117,12 +120,6 @@ public class H2Dialect extends AbstractDialect {
@Override @Override
public IdGeneration getIdGeneration() { public IdGeneration getIdGeneration() {
return new IdGeneration() { return ID_GENERATION;
@Override
public String nextValueFromSequenceSelect(String sequenceName) {
return "SELECT NEXT VALUE FOR %s".formatted(sequenceName);
}
};
} }
} }

11
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/HsqlDbDialect.java

@ -15,6 +15,8 @@
*/ */
package org.springframework.data.relational.core.dialect; package org.springframework.data.relational.core.dialect;
import org.springframework.data.relational.core.sql.SqlIdentifier;
/** /**
* A {@link Dialect} for HsqlDb. * A {@link Dialect} for HsqlDb.
* *
@ -70,16 +72,17 @@ public class HsqlDbDialect extends AbstractDialect {
public IdGeneration getIdGeneration() { public IdGeneration getIdGeneration() {
return new IdGeneration() { return new IdGeneration() {
/** @Override
public String createSequenceQuery(SqlIdentifier sequenceName) {
/*
* One may think that this is an over-complication, but it is actually not. * One may think that this is an over-complication, but it is actually not.
* There is no a direct way to query the next value for the sequence, only to use it as an expression * There is no a direct way to query the next value for the sequence, only to use it as an expression
* inside other queries (SELECT/INSERT). Therefore, such a workaround is required * inside other queries (SELECT/INSERT). Therefore, such a workaround is required
* *
* @see <a href="https://github.com/jOOQ/jOOQ/issues/3762">The way JOOQ solves this problem</a> * @see <a href="https://github.com/jOOQ/jOOQ/issues/3762">The way JOOQ solves this problem</a>
*/ */
@Override return "SELECT NEXT VALUE FOR %s AS msq FROM INFORMATION_SCHEMA.SEQUENCES LIMIT 1"
public String nextValueFromSequenceSelect(String sequenceName) { .formatted(sequenceName.toSql(getIdentifierProcessing()));
return "SELECT NEXT VALUE FOR %s AS msq FROM INFORMATION_SCHEMA.SEQUENCES LIMIT 1".formatted(sequenceName);
} }
}; };
} }

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

@ -18,20 +18,30 @@ 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.IdentifierProcessing;
import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.SqlIdentifier;
/** /**
* Encapsulates various properties that are related to ID generation process and specific to * Encapsulates various properties that are related to ID generation process and specific to given {@link Dialect}
* given {@link Dialect}
* *
* @author Jens Schauder * @author Jens Schauder
* @author Chirag Tailor * @author Chirag Tailor
* @author Mikhail Polivakha * @author Mikhail Polivakha
*
* @since 2.1 * @since 2.1
*/ */
public interface IdGeneration { public interface IdGeneration {
static IdGeneration create(IdentifierProcessing identifierProcessing) {
return new IdGeneration() {
@Override
public String createSequenceQuery(SqlIdentifier sequenceName) {
return IdGeneration.createSequenceQuery(sequenceName.toSql(identifierProcessing));
}
};
}
/** /**
* A default instance working for many databases and equivalent to Spring Data JDBCs behavior before version 2.1. * A default instance working for many databases and equivalent to Spring Data JDBCs behavior before version 2.1.
*/ */
@ -62,13 +72,6 @@ public interface IdGeneration {
return id.getReference(); return id.getReference();
} }
/**
* @return {@literal true} in case the sequences are supported by the underlying database, {@literal false} otherwise
*/
default boolean sequencesSupported() {
return true;
}
/** /**
* Does the driver support id generation for batch operations. * Does the driver support id generation for batch operations.
* <p> * <p>
@ -82,15 +85,28 @@ public interface IdGeneration {
return true; return true;
} }
/**
* @return {@literal true} in case the sequences are supported by the underlying database, {@literal false} otherwise
* @since 3.5
*/
default boolean sequencesSupported() {
return true;
}
/** /**
* The SQL statement that allows retrieving the next value from the passed sequence * The SQL statement that allows retrieving the next value from the passed sequence
* *
* @param sequenceName the sequence name to get the enxt value for * @param sequenceName the sequence name to get the next value for
* @return SQL string * @return SQL string
* @since 3.5
*/ */
default String nextValueFromSequenceSelect(String sequenceName) { default String createSequenceQuery(SqlIdentifier sequenceName) {
throw new UnsupportedOperationException(
"Currently, there is no support for sequence generation for %s dialect. If you need it, please, submit a ticket".formatted(this.getClass().getSimpleName()) String nameString = sequenceName.toString();
); return createSequenceQuery(nameString);
}
static String createSequenceQuery(String nameString) {
return "SELECT NEXT VALUE FOR" + nameString;
} }
} }

13
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MariaDbDialect.java

@ -18,7 +18,6 @@ package org.springframework.data.relational.core.dialect;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.IdentifierProcessing;
/** /**
@ -36,19 +35,11 @@ public class MariaDbDialect extends MySqlDialect {
@Override @Override
public Collection<Object> getConverters() { public Collection<Object> getConverters() {
return Arrays.asList( return Arrays.asList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE, NumberToBooleanConverter.INSTANCE);
TimestampAtUtcToOffsetDateTimeConverter.INSTANCE,
NumberToBooleanConverter.INSTANCE);
} }
@Override @Override
public IdGeneration getIdGeneration() { public IdGeneration getIdGeneration() {
return new IdGeneration() { return IdGeneration.create(getIdentifierProcessing());
@Override
public String nextValueFromSequenceSelect(String sequenceName) {
return "SELECT NEXTVAL(%s)".formatted(sequenceName);
}
};
} }
} }

17
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java

@ -18,10 +18,12 @@ package org.springframework.data.relational.core.dialect;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.relational.core.sql.LockOptions;
import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing; import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing;
import org.springframework.data.relational.core.sql.IdentifierProcessing.Quoting; import org.springframework.data.relational.core.sql.IdentifierProcessing.Quoting;
import org.springframework.data.relational.core.sql.LockOptions;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
@ -131,10 +133,7 @@ public class MySqlDialect extends AbstractDialect {
@Override @Override
public Collection<Object> getConverters() { public Collection<Object> getConverters() {
return Arrays.asList( return Arrays.asList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE, NumberToBooleanConverter.INSTANCE);
TimestampAtUtcToOffsetDateTimeConverter.INSTANCE,
NumberToBooleanConverter.INSTANCE
);
} }
@Override @Override
@ -144,12 +143,20 @@ public class MySqlDialect extends AbstractDialect {
@Override @Override
public IdGeneration getIdGeneration() { public IdGeneration getIdGeneration() {
return new IdGeneration() { return new IdGeneration() {
@Override @Override
public boolean sequencesSupported() { public boolean sequencesSupported() {
return false; return false;
} }
@Override
public String createSequenceQuery(@NotNull SqlIdentifier sequenceName) {
throw new UnsupportedOperationException(
"Currently, there is no support for sequence generation for %s dialect. If you need it, please, submit a ticket"
.formatted(this.getClass().getSimpleName()));
}
}; };
} }
} }

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

@ -15,13 +15,14 @@
*/ */
package org.springframework.data.relational.core.dialect; package org.springframework.data.relational.core.dialect;
import org.springframework.core.convert.converter.Converter; import static java.util.Arrays.*;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import java.util.Collection; import java.util.Collection;
import static java.util.Arrays.*; import org.jetbrains.annotations.NotNull;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.relational.core.sql.SqlIdentifier;
/** /**
* An SQL dialect for Oracle. * An SQL dialect for Oracle.
@ -50,8 +51,8 @@ public class OracleDialect extends AnsiDialect {
} }
@Override @Override
public String nextValueFromSequenceSelect(String sequenceName) { public String createSequenceQuery(@NotNull SqlIdentifier sequenceName) {
return "SELECT %s.nextval FROM DUAL".formatted(sequenceName); return "SELECT %s.nextval FROM DUAL".formatted(sequenceName.toSql(INSTANCE.getIdentifierProcessing()));
} }
}; };
@ -64,7 +65,8 @@ public class OracleDialect extends AnsiDialect {
@Override @Override
public Collection<Object> getConverters() { public Collection<Object> getConverters() {
return asList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE, NumberToBooleanConverter.INSTANCE, BooleanToIntegerConverter.INSTANCE); return asList(TimestampAtUtcToOffsetDateTimeConverter.INSTANCE, NumberToBooleanConverter.INSTANCE,
BooleanToIntegerConverter.INSTANCE);
} }
@WritingConverter @WritingConverter

20
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java

@ -55,6 +55,16 @@ public class PostgresDialect extends AbstractDialect {
private static final Set<Class<?>> POSTGRES_SIMPLE_TYPES = Set.of(UUID.class, URL.class, URI.class, InetAddress.class, private static final Set<Class<?>> POSTGRES_SIMPLE_TYPES = Set.of(UUID.class, URL.class, URI.class, InetAddress.class,
Map.class); Map.class);
private IdentifierProcessing identifierProcessing = IdentifierProcessing.create(Quoting.ANSI,
LetterCasing.LOWER_CASE);
private IdGeneration idGeneration = new IdGeneration() {
@Override
public String createSequenceQuery(SqlIdentifier sequenceName) {
return "SELECT nextval('%s')".formatted(sequenceName.toSql(getIdentifierProcessing()));
}
};
protected PostgresDialect() {} protected PostgresDialect() {}
private static final LimitClause LIMIT_CLAUSE = new LimitClause() { private static final LimitClause LIMIT_CLAUSE = new LimitClause() {
@ -145,7 +155,7 @@ public class PostgresDialect extends AbstractDialect {
@Override @Override
public IdentifierProcessing getIdentifierProcessing() { public IdentifierProcessing getIdentifierProcessing() {
return IdentifierProcessing.create(Quoting.ANSI, LetterCasing.LOWER_CASE); return identifierProcessing;
} }
@Override @Override
@ -160,12 +170,6 @@ public class PostgresDialect extends AbstractDialect {
@Override @Override
public IdGeneration getIdGeneration() { public IdGeneration getIdGeneration() {
return new IdGeneration() { return idGeneration;
@Override
public String nextValueFromSequenceSelect(String sequenceName) {
return "SELECT nextval('%s')".formatted(sequenceName);
}
};
} }
} }

11
spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java

@ -17,6 +17,7 @@ package org.springframework.data.relational.core.dialect;
import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.relational.core.sql.LockOptions; import org.springframework.data.relational.core.sql.LockOptions;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.core.sql.render.SelectRenderContext; import org.springframework.data.relational.core.sql.render.SelectRenderContext;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
@ -36,6 +37,9 @@ public class SqlServerDialect extends AbstractDialect {
*/ */
public static final SqlServerDialect INSTANCE = new SqlServerDialect(); public static final SqlServerDialect INSTANCE = new SqlServerDialect();
private static final IdentifierProcessing IDENTIFIER_PROCESSING = IdentifierProcessing
.create(IdentifierProcessing.Quoting.ANSI, IdentifierProcessing.LetterCasing.AS_IS);
private static final IdGeneration ID_GENERATION = new IdGeneration() { private static final IdGeneration ID_GENERATION = new IdGeneration() {
@Override @Override
@ -44,14 +48,11 @@ public class SqlServerDialect extends AbstractDialect {
} }
@Override @Override
public String nextValueFromSequenceSelect(String sequenceName) { public String createSequenceQuery(SqlIdentifier sequenceName) {
return "SELECT NEXT VALUE FOR %s".formatted(sequenceName); return IdGeneration.createSequenceQuery(sequenceName.toSql(IDENTIFIER_PROCESSING));
} }
}; };
private static final IdentifierProcessing IDENTIFIER_PROCESSING = IdentifierProcessing
.create(IdentifierProcessing.Quoting.ANSI, IdentifierProcessing.LetterCasing.AS_IS);
protected SqlServerDialect() {} protected SqlServerDialect() {}
@Override @Override

50
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentEntity.java

@ -17,7 +17,8 @@ package org.springframework.data.relational.core.mapping;
import java.util.Optional; import java.util.Optional;
import org.jetbrains.annotations.NotNull; import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.data.mapping.model.BasicPersistentEntity; import org.springframework.data.mapping.model.BasicPersistentEntity;
import org.springframework.data.relational.core.sql.SqlIdentifier; import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.util.Lazy; import org.springframework.data.util.Lazy;
@ -47,7 +48,7 @@ class BasicRelationalPersistentEntity<T> extends BasicPersistentEntity<T, Relati
private final Lazy<SqlIdentifier> tableName; private final Lazy<SqlIdentifier> tableName;
private final @Nullable Expression tableNameExpression; private final @Nullable Expression tableNameExpression;
private final Lazy<String> idTargetSequenceName; private final Lazy<SqlIdentifier> idSequenceName;
private final Lazy<Optional<SqlIdentifier>> schemaName; private final Lazy<Optional<SqlIdentifier>> schemaName;
private final @Nullable Expression schemaNameExpression; private final @Nullable Expression schemaNameExpression;
@ -91,7 +92,7 @@ class BasicRelationalPersistentEntity<T> extends BasicPersistentEntity<T, Relati
this.schemaNameExpression = null; this.schemaNameExpression = null;
} }
this.idTargetSequenceName = Lazy.of(this::determineTargetSequenceName); this.idSequenceName = Lazy.of(this::determineSequenceName);
} }
/** /**
@ -165,8 +166,8 @@ class BasicRelationalPersistentEntity<T> extends BasicPersistentEntity<T, Relati
} }
@Override @Override
public Optional<String> getIdTargetSequence() { public Optional<SqlIdentifier> getIdSequence() {
return idTargetSequenceName.getOptional(); return idSequenceName.getOptional();
} }
@Override @Override
@ -174,33 +175,28 @@ class BasicRelationalPersistentEntity<T> extends BasicPersistentEntity<T, Relati
return String.format("BasicRelationalPersistentEntity<%s>", getType()); return String.format("BasicRelationalPersistentEntity<%s>", getType());
} }
private @Nullable String determineTargetSequenceName() { private @Nullable SqlIdentifier determineSequenceName() {
RelationalPersistentProperty idProperty = getIdProperty(); RelationalPersistentProperty idProperty = getIdProperty();
if (idProperty != null && idProperty.isAnnotationPresent(TargetSequence.class)) { if (idProperty != null && idProperty.isAnnotationPresent(Sequence.class)) {
TargetSequence requiredAnnotation = idProperty.getRequiredAnnotation(TargetSequence.class);
if (!StringUtils.hasText(requiredAnnotation.sequence()) && !StringUtils.hasText(requiredAnnotation.value())) { Sequence requiredAnnotation = idProperty.getRequiredAnnotation(Sequence.class);
throw new IllegalStateException("""
For the persistent entity '%s' the @TargetSequence annotation was specified for the @Id, however, neither MergedAnnotation<Sequence> targetSequence = MergedAnnotations.from(requiredAnnotation)
the value() nor the sequence() attributes are specified .get(Sequence.class);
"""
); String sequence = targetSequence.getString("sequence");
} else { String schema = targetSequence.getString("schema");
String sequenceFullyQualifiedName = getSequenceName(requiredAnnotation);
if (StringUtils.hasText(requiredAnnotation.schema())) { SqlIdentifier sequenceIdentifier = SqlIdentifier.quoted(sequence);
return String.join(".", requiredAnnotation.schema(), sequenceFullyQualifiedName); if (StringUtils.hasText(schema)) {
} sequenceIdentifier = SqlIdentifier.from(SqlIdentifier.quoted(schema), sequenceIdentifier);
return sequenceFullyQualifiedName;
} }
return sequenceIdentifier;
} else { } else {
return null; return null;
} }
} }
@NotNull
private static String getSequenceName(TargetSequence requiredAnnotation) {
return Optional.of(requiredAnnotation.sequence())
.filter(s -> !s.isBlank())
.orElse(requiredAnnotation.value());
}
} }

2
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/EmbeddedRelationalPersistentEntity.java

@ -57,7 +57,7 @@ class EmbeddedRelationalPersistentEntity<T> implements RelationalPersistentEntit
} }
@Override @Override
public Optional<String> getIdTargetSequence() { public Optional<SqlIdentifier> getIdSequence() {
return Optional.empty(); return Optional.empty();
} }

2
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/RelationalPersistentEntity.java

@ -58,5 +58,5 @@ public interface RelationalPersistentEntity<T> extends MutablePersistentEntity<T
/** /**
* @return the target sequence that should be used for id generation * @return the target sequence that should be used for id generation
*/ */
Optional<String> getIdTargetSequence(); Optional<SqlIdentifier> getIdSequence();
} }

46
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/Sequence.java

@ -0,0 +1,46 @@
package org.springframework.data.relational.core.mapping;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Specify the sequence from which the value for the {@link org.springframework.data.annotation.Id} should be fetched
*
* @author Mikhail Polivakha
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface Sequence {
/**
* The name of the sequence from which the id should be fetched
*/
@AliasFor("sequence")
String value() default "";
/**
* Alias for {@link #value()}
*/
@AliasFor("value")
String sequence() default "";
/**
* Schema where the sequence reside. Technically, this attribute is not necessarily the schema. It just represents the
* location/namespace, where the sequence resides. For instance, in Oracle databases the schema and user are often
* used interchangeably, so {@link #schema() schema} attribute may represent an Oracle user as well.
* <p>
* The final name of the sequence to be queried for the next value will be constructed by the concatenation of schema
* and sequence :
*
* <pre>
* schema().sequence()
* </pre>
*/
String schema() default "";
}

43
spring-data-relational/src/main/java/org/springframework/data/relational/core/mapping/TargetSequence.java

@ -1,43 +0,0 @@
package org.springframework.data.relational.core.mapping;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
/**
* Specify the sequence from which the value for the {@link org.springframework.data.annotation.Id}
* should be fetched
*
* @author Mikhail Polivakha
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface TargetSequence {
/**
* The name of the sequence from which the id should be fetched
*/
String value() default "";
/**
* Alias for {@link #value()}
*/
@AliasFor("value")
String sequence() default "";
/**
* Schema where the sequence reside.
* Technically, this attribute is not necessarily the schema. It just represents the location/namespace,
* where the sequence resides. For instance, in Oracle databases the schema and user are often used
* interchangeably, so {@link #schema() schema} attribute may represent an Oracle user as well.
* <p>
* The final name of the sequence to be queried for the next value will be constructed by the concatenation
* of schema and sequence : <pre>schema().sequence()</pre>
*/
String schema() default "";
}

46
spring-data-relational/src/test/java/org/springframework/data/relational/core/mapping/BasicRelationalPersistentEntityUnitTests.java

@ -58,32 +58,40 @@ class BasicRelationalPersistentEntityUnitTests {
assertThat(entity.getTableName()).isEqualTo(quoted("dummy_sub_entity")); assertThat(entity.getTableName()).isEqualTo(quoted("dummy_sub_entity"));
} }
@Test @Test // GH-1923
void entityWithNotargetSequence() { void entityWithNoSequence() {
RelationalPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(DummySubEntity.class); RelationalPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(DummySubEntity.class);
assertThat(entity.getIdTargetSequence()).isEmpty(); assertThat(entity.getIdSequence()).isEmpty();
} }
@Test @Test // GH-1923
void determineSequenceName() { void determineSequenceName() {
RelationalPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(EntityWithSequence.class);
assertThat(persistentEntity.getIdTargetSequence()).isPresent().hasValue("my_seq"); RelationalPersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(EntityWithSequence.class);
assertThat(persistentEntity.getIdSequence()).contains(SqlIdentifier.quoted("my_seq"));
} }
@Test @Test // GH-1923
void determineSequenceNameFromValue() { void determineSequenceNameFromValue() {
RelationalPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(EntityWithSequenceValueAlias.class);
assertThat(persistentEntity.getIdTargetSequence()).isPresent().hasValue("my_seq"); RelationalPersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(EntityWithSequenceValueAlias.class);
assertThat(persistentEntity.getIdSequence()).contains(SqlIdentifier.quoted("my_seq"));
} }
@Test @Test // GH-1923
void determineSequenceNameWithSchemaSpecified() { void determineSequenceNameWithSchemaSpecified() {
RelationalPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(EntityWithSequenceAndSchema.class);
assertThat(persistentEntity.getIdTargetSequence()).isPresent().hasValue("public.my_seq"); RelationalPersistentEntity<?> persistentEntity = mappingContext
.getRequiredPersistentEntity(EntityWithSequenceAndSchema.class);
assertThat(persistentEntity.getIdSequence())
.contains(SqlIdentifier.from(SqlIdentifier.quoted("public"), SqlIdentifier.quoted("my_seq")));
} }
@Test // DATAJDBC-294 @Test // DATAJDBC-294
@ -203,7 +211,8 @@ class BasicRelationalPersistentEntityUnitTests {
@Id private Long id; @Id private Long id;
} }
@Table(schema = "#{T(org.springframework.data.relational.core.mapping." @Table(
schema = "#{T(org.springframework.data.relational.core.mapping."
+ "BasicRelationalPersistentEntityUnitTests$EntityWithSchemaAndTableSpelExpression).desiredSchemaName}", + "BasicRelationalPersistentEntityUnitTests$EntityWithSchemaAndTableSpelExpression).desiredSchemaName}",
name = "#{T(org.springframework.data.relational.core.mapping." name = "#{T(org.springframework.data.relational.core.mapping."
+ "BasicRelationalPersistentEntityUnitTests$EntityWithSchemaAndTableSpelExpression).desiredTableName}") + "BasicRelationalPersistentEntityUnitTests$EntityWithSchemaAndTableSpelExpression).desiredTableName}")
@ -213,7 +222,8 @@ class BasicRelationalPersistentEntityUnitTests {
public static String desiredSchemaName = "HELP_ME_OBI_WON"; public static String desiredSchemaName = "HELP_ME_OBI_WON";
} }
@Table(schema = "#{T(org.springframework.data.relational.core.mapping." @Table(
schema = "#{T(org.springframework.data.relational.core.mapping."
+ "BasicRelationalPersistentEntityUnitTests$LittleBobbyTables).desiredSchemaName}", + "BasicRelationalPersistentEntityUnitTests$LittleBobbyTables).desiredSchemaName}",
name = "#{T(org.springframework.data.relational.core.mapping." name = "#{T(org.springframework.data.relational.core.mapping."
+ "BasicRelationalPersistentEntityUnitTests$LittleBobbyTables).desiredTableName}") + "BasicRelationalPersistentEntityUnitTests$LittleBobbyTables).desiredTableName}")
@ -232,19 +242,21 @@ class BasicRelationalPersistentEntityUnitTests {
@Table("entity_with_sequence") @Table("entity_with_sequence")
static class EntityWithSequence { static class EntityWithSequence {
@Id @Id
@TargetSequence(sequence = "my_seq") Long id; @Sequence(sequence = "my_seq") Long id;
} }
@Table("entity_with_sequence_value_alias") @Table("entity_with_sequence_value_alias")
static class EntityWithSequenceValueAlias { static class EntityWithSequenceValueAlias {
@Id @Id
@Column("myId") @TargetSequence(value = "my_seq") Long id; @Column("myId")
@Sequence(value = "my_seq") Long id;
} }
@Table("entity_with_sequence_and_schema") @Table("entity_with_sequence_and_schema")
static class EntityWithSequenceAndSchema { static class EntityWithSequenceAndSchema {
@Id @Id
@Column("myId") @TargetSequence(sequence = "my_seq", schema = "public") Long id; @Column("myId")
@Sequence(sequence = "my_seq", schema = "public") Long id;
} }
@Table() @Table()

4
src/main/antora/modules/ROOT/partials/id-generation.adoc

@ -6,7 +6,9 @@ The ID of an entity must be annotated with Spring Data's https://docs.spring.io/
When your database has an auto-increment column for the ID column, the generated value gets set in the entity after inserting it into the database. When your database has an auto-increment column for the ID column, the generated value gets set in the entity after inserting it into the database.
Spring Data does not attempt to insert values of identifier columns when the entity is new and the identifier value defaults to its initial value. If you annotate the id additionally with `@Sequence` a database sequence will be used to obtain values for the id.
Otherwise Spring Data does not attempt to insert values of identifier columns when the entity is new and the identifier value defaults to its initial value.
That is `0` for primitive types and `null` if the identifier property uses a numeric wrapper type such as `Long`. That is `0` for primitive types and `null` if the identifier property uses a numeric wrapper type such as `Long`.
xref:repositories/core-concepts.adoc#is-new-state-detection[Entity State Detection] explains in detail the strategies to detect whether an entity is new or whether it is expected to exist in your database. xref:repositories/core-concepts.adoc#is-new-state-detection[Entity State Detection] explains in detail the strategies to detect whether an entity is new or whether it is expected to exist in your database.

Loading…
Cancel
Save