Browse Source

R2DBC `@Sequence` annotation support.

Signed-off-by: mipo256 <mikhailpolivakha@gmail.com>

See #1955
Original pull request: #2028
pull/2049/head
mipo256 8 months ago committed by Mark Paluch
parent
commit
d6121cbfe2
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 6
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/IdGeneratingEntityCallback.java
  2. 14
      spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java
  3. 17
      spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java
  4. 10
      spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java
  5. 104
      spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/mapping/IdGeneratingBeforeSaveCallback.java
  6. 2
      spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/UpdateMapper.java
  7. 124
      spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/mapping/IdGeneratingBeforeSaveCallbackTest.java
  8. 86
      spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/PostgresR2dbcRepositoryIntegrationTests.java
  9. 1
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/PostgresDialect.java
  10. 46
      src/main/antora/modules/ROOT/pages/r2dbc/sequences.adoc

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

@ -56,14 +56,14 @@ public class IdGeneratingEntityCallback implements BeforeSaveCallback<Object> { @@ -56,14 +56,14 @@ public class IdGeneratingEntityCallback implements BeforeSaveCallback<Object> {
return aggregate;
}
RelationalPersistentProperty property = entity.getRequiredIdProperty();
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
PersistentPropertyAccessor<Object> accessor = entity.getPropertyAccessor(aggregate);
if (!entity.isNew(aggregate) || delegate.hasValue(property, accessor) || !property.hasSequence()) {
if (!entity.isNew(aggregate) || delegate.hasValue(idProperty, accessor) || !idProperty.hasSequence()) {
return aggregate;
}
delegate.generateSequenceValue(property, accessor);
delegate.generateSequenceValue(idProperty, accessor);
return accessor.getBean();
}

14
spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/config/AbstractR2dbcConfiguration.java

@ -39,12 +39,14 @@ import org.springframework.data.r2dbc.convert.R2dbcCustomConversions; @@ -39,12 +39,14 @@ import org.springframework.data.r2dbc.convert.R2dbcCustomConversions;
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
import org.springframework.data.r2dbc.core.mapping.IdGeneratingBeforeSaveCallback;
import org.springframework.data.r2dbc.dialect.DialectResolver;
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
import org.springframework.data.relational.RelationalManagedTypes;
import org.springframework.data.relational.core.mapping.DefaultNamingStrategy;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.util.TypeScanner;
import org.springframework.lang.Nullable;
@ -182,6 +184,18 @@ public abstract class AbstractR2dbcConfiguration implements ApplicationContextAw @@ -182,6 +184,18 @@ public abstract class AbstractR2dbcConfiguration implements ApplicationContextAw
return context;
}
/**
* Register a {@link IdGeneratingBeforeSaveCallback} using
* {@link #r2dbcMappingContext(Optional, R2dbcCustomConversions, RelationalManagedTypes)} and
* {@link #databaseClient()}
*/
@Bean
public IdGeneratingBeforeSaveCallback idGeneratingBeforeSaveCallback(
RelationalMappingContext relationalMappingContext, DatabaseClient databaseClient) {
return new IdGeneratingBeforeSaveCallback(relationalMappingContext, getDialect(lookupConnectionFactory()),
databaseClient);
}
/**
* Creates a {@link ReactiveDataAccessStrategy} using the configured
* {@link #r2dbcConverter(R2dbcMappingContext, R2dbcCustomConversions) R2dbcConverter}.

17
spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/convert/MappingR2dbcConverter.java

@ -186,11 +186,11 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements @@ -186,11 +186,11 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements
RelationalPersistentEntity<?> entity = getRequiredPersistentEntity(userClass);
PersistentPropertyAccessor<?> propertyAccessor = entity.getPropertyAccessor(source);
writeProperties(sink, entity, propertyAccessor, entity.isNew(source));
writeProperties(sink, entity, propertyAccessor);
}
private void writeProperties(OutboundRow sink, RelationalPersistentEntity<?> entity,
PersistentPropertyAccessor<?> accessor, boolean isNew) {
PersistentPropertyAccessor<?> accessor) {
for (RelationalPersistentProperty property : entity) {
@ -213,15 +213,14 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements @@ -213,15 +213,14 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements
}
if (getConversions().isSimpleType(value.getClass())) {
writeSimpleInternal(sink, value, isNew, property);
writeSimpleInternal(sink, value, property);
} else {
writePropertyInternal(sink, value, isNew, property);
writePropertyInternal(sink, value, property);
}
}
}
private void writeSimpleInternal(OutboundRow sink, Object value, boolean isNew,
RelationalPersistentProperty property) {
private void writeSimpleInternal(OutboundRow sink, Object value, RelationalPersistentProperty property) {
Object result = getPotentiallyConvertedSimpleWrite(value);
@ -229,8 +228,7 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements @@ -229,8 +228,7 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements
Parameter.fromOrEmpty(result, getPotentiallyConvertedSimpleNullType(property.getType())));
}
private void writePropertyInternal(OutboundRow sink, Object value, boolean isNew,
RelationalPersistentProperty property) {
private void writePropertyInternal(OutboundRow sink, Object value, RelationalPersistentProperty property) {
TypeInformation<?> valueType = TypeInformation.of(value.getClass());
@ -239,7 +237,7 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements @@ -239,7 +237,7 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements
if (valueType.getActualType() != null && valueType.getRequiredActualType().isCollectionLike()) {
// pass-thru nested collections
writeSimpleInternal(sink, value, isNew, property);
writeSimpleInternal(sink, value, property);
return;
}
@ -310,7 +308,6 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements @@ -310,7 +308,6 @@ public class MappingR2dbcConverter extends MappingRelationalConverter implements
if (customTarget.isPresent()) {
return customTarget.get();
}
if (type.isEnum()) {

10
spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/R2dbcEntityTemplate.java

@ -521,15 +521,15 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw @@ -521,15 +521,15 @@ public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAw
return;
}
SqlIdentifier columnName = idProperty.getColumnName();
Parameter parameter = outboundRow.get(columnName);
SqlIdentifier idColumnName = idProperty.getColumnName();
Parameter parameter = outboundRow.get(idColumnName);
if (shouldSkipIdValue(parameter, idProperty)) {
outboundRow.remove(columnName);
if (shouldSkipIdValue(parameter)) {
outboundRow.remove(idColumnName);
}
}
private boolean shouldSkipIdValue(@Nullable Parameter value, RelationalPersistentProperty property) {
private boolean shouldSkipIdValue(@Nullable Parameter value) {
if (value == null || value.getValue() == null) {
return true;

104
spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/mapping/IdGeneratingBeforeSaveCallback.java

@ -0,0 +1,104 @@ @@ -0,0 +1,104 @@
/*
* Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.r2dbc.core.mapping;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.reactivestreams.Publisher;
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.r2dbc.mapping.event.BeforeSaveCallback;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.r2dbc.core.Parameter;
import org.springframework.util.Assert;
import reactor.core.publisher.Mono;
/**
* R2DBC Callback for generating ID via the database sequence.
*
* @author Mikhail Polivakha
*/
public class IdGeneratingBeforeSaveCallback implements BeforeSaveCallback<Object> {
private static final Log LOG = LogFactory.getLog(IdGeneratingBeforeSaveCallback.class);
private final RelationalMappingContext relationalMappingContext;
private final R2dbcDialect dialect;
private final DatabaseClient databaseClient;
public IdGeneratingBeforeSaveCallback(RelationalMappingContext relationalMappingContext, R2dbcDialect dialect,
DatabaseClient databaseClient) {
this.relationalMappingContext = relationalMappingContext;
this.dialect = dialect;
this.databaseClient = databaseClient;
}
@Override
public Publisher<Object> onBeforeSave(Object entity, OutboundRow row, SqlIdentifier table) {
Assert.notNull(entity, "The aggregate cannot be null at this point");
RelationalPersistentEntity<?> persistentEntity = relationalMappingContext.getPersistentEntity(entity.getClass());
if (!persistentEntity.hasIdProperty() || //
!persistentEntity.getIdProperty().hasSequence() || //
!persistentEntity.isNew(entity) //
) {
return Mono.just(entity);
}
RelationalPersistentProperty property = persistentEntity.getIdProperty();
SqlIdentifier idSequence = property.getSequence();
if (dialect.getIdGeneration().sequencesSupported()) {
return fetchIdFromSeq(entity, row, persistentEntity, idSequence);
} else {
illegalSequenceUsageWarning(entity);
}
return Mono.just(entity);
}
private Mono<Object> fetchIdFromSeq(Object entity, OutboundRow row, RelationalPersistentEntity<?> persistentEntity,
SqlIdentifier idSequence) {
String sequenceQuery = dialect.getIdGeneration().createSequenceQuery(idSequence);
return databaseClient //
.sql(sequenceQuery) //
.map((r, rowMetadata) -> r.get(0)) //
.one() //
.map(fetchedId -> { //
row.put( //
persistentEntity.getIdColumn().toSql(dialect.getIdentifierProcessing()), //
Parameter.from(fetchedId) //
);
return entity;
});
}
private static void illegalSequenceUsageWarning(Object entity) {
LOG.warn("""
It seems you're trying to insert an aggregate of type '%s' annotated with @Sequence, but the problem is RDBMS you're
working with does not support sequences as such. Falling back to identity columns
""".stripIndent().formatted(entity.getClass().getName()));
}
}

2
spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/query/UpdateMapper.java

@ -83,7 +83,7 @@ public class UpdateMapper extends QueryMapper { @@ -83,7 +83,7 @@ public class UpdateMapper extends QueryMapper {
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
* @return the mapped {@link BoundAssignments}.
*/
public BoundAssignments getMappedObject(BindMarkers markers, Map<SqlIdentifier, ? extends Object> assignments,
public BoundAssignments getMappedObject(BindMarkers markers, Map<SqlIdentifier, ?> assignments,
Table table, @Nullable RelationalPersistentEntity<?> entity) {
Assert.notNull(markers, "BindMarkers must not be null");

124
spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/core/mapping/IdGeneratingBeforeSaveCallbackTest.java

@ -0,0 +1,124 @@ @@ -0,0 +1,124 @@
/*
* Copyright 2020-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.r2dbc.core.mapping;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.function.BiFunction;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.reactivestreams.Publisher;
import org.springframework.data.annotation.Id;
import org.springframework.data.r2dbc.dialect.MySqlDialect;
import org.springframework.data.r2dbc.dialect.PostgresDialect;
import org.springframework.data.r2dbc.mapping.OutboundRow;
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
import org.springframework.data.relational.core.mapping.Sequence;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.r2dbc.core.Parameter;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
/**
* Unit tests for {@link IdGeneratingBeforeSaveCallback}.
*
* @author Mikhail Polivakha
*/
class IdGeneratingBeforeSaveCallbackTest {
@Test
void testIdGenerationIsNotSupported() {
R2dbcMappingContext r2dbcMappingContext = new R2dbcMappingContext();
r2dbcMappingContext.getPersistentEntity(SimpleEntity.class);
MySqlDialect dialect = MySqlDialect.INSTANCE;
DatabaseClient databaseClient = mock(DatabaseClient.class);
IdGeneratingBeforeSaveCallback callback = new IdGeneratingBeforeSaveCallback(r2dbcMappingContext, dialect,
databaseClient);
OutboundRow row = new OutboundRow("name", Parameter.from("my_name"));
SimpleEntity entity = new SimpleEntity();
Publisher<Object> publisher = callback.onBeforeSave(entity, row, SqlIdentifier.unquoted("simple_entity"));
StepVerifier.create(publisher).expectNext(entity).expectComplete().verify();
assertThat(row).hasSize(1); // id is not added
}
@Test
void testEntityIsNotAnnotatedWithSequence() {
R2dbcMappingContext r2dbcMappingContext = new R2dbcMappingContext();
r2dbcMappingContext.getPersistentEntity(SimpleEntity.class);
PostgresDialect dialect = PostgresDialect.INSTANCE;
DatabaseClient databaseClient = mock(DatabaseClient.class);
IdGeneratingBeforeSaveCallback callback = new IdGeneratingBeforeSaveCallback(r2dbcMappingContext, dialect,
databaseClient);
OutboundRow row = new OutboundRow("name", Parameter.from("my_name"));
SimpleEntity entity = new SimpleEntity();
Publisher<Object> publisher = callback.onBeforeSave(entity, row, SqlIdentifier.unquoted("simple_entity"));
StepVerifier.create(publisher).expectNext(entity).expectComplete().verify();
assertThat(row).hasSize(1); // id is not added
}
@Test
void testIdGeneratedFromSequenceHappyPath() {
R2dbcMappingContext r2dbcMappingContext = new R2dbcMappingContext();
r2dbcMappingContext.getPersistentEntity(WithSequence.class);
PostgresDialect dialect = PostgresDialect.INSTANCE;
DatabaseClient databaseClient = mock(DatabaseClient.class, RETURNS_DEEP_STUBS);
long generatedId = 1L;
when(databaseClient.sql(Mockito.anyString()).map(Mockito.any(BiFunction.class)).one()).thenReturn(
Mono.just(generatedId));
IdGeneratingBeforeSaveCallback callback = new IdGeneratingBeforeSaveCallback(r2dbcMappingContext, dialect,
databaseClient);
OutboundRow row = new OutboundRow("name", Parameter.from("my_name"));
WithSequence entity = new WithSequence();
Publisher<Object> publisher = callback.onBeforeSave(entity, row, SqlIdentifier.unquoted("simple_entity"));
StepVerifier.create(publisher).expectNext(entity).expectComplete().verify();
assertThat(row).hasSize(2)
.containsEntry(SqlIdentifier.unquoted("id"), Parameter.from(generatedId));
}
static class SimpleEntity {
@Id
private Long id;
private String name;
}
static class WithSequence {
@Id
@Sequence(sequence = "seq_name")
private Long id;
private String name;
}
}

86
spring-data-r2dbc/src/test/java/org/springframework/data/r2dbc/repository/PostgresR2dbcRepositoryIntegrationTests.java

@ -15,8 +15,13 @@ @@ -15,8 +15,13 @@
*/
package org.springframework.data.r2dbc.repository;
import io.r2dbc.postgresql.codec.Json;
import io.r2dbc.spi.ConnectionFactory;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import java.util.Map;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.RegisterExtension;
@ -32,22 +37,20 @@ import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; @@ -32,22 +37,20 @@ import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories;
import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactory;
import org.springframework.data.r2dbc.testing.ExternalDatabase;
import org.springframework.data.r2dbc.testing.PostgresTestSupport;
import org.springframework.data.relational.core.mapping.Sequence;
import org.springframework.data.relational.core.mapping.Table;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.r2dbc.core.DatabaseClient;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import io.r2dbc.postgresql.codec.Json;
import io.r2dbc.spi.ConnectionFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import javax.sql.DataSource;
import java.util.Collections;
import java.util.Map;
import static org.assertj.core.api.Assertions.*;
/**
* Integration tests for {@link LegoSetRepository} using {@link R2dbcRepositoryFactory} against Postgres.
*
@ -62,12 +65,14 @@ public class PostgresR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposi @@ -62,12 +65,14 @@ public class PostgresR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposi
@Autowired WithJsonRepository withJsonRepository;
@Autowired WithIdFromSequenceRepository withIdFromSequenceRepository;
@Autowired WithHStoreRepository hstoreRepositoryWith;
@Configuration
@EnableR2dbcRepositories(considerNestedRepositories = true,
includeFilters = @Filter(
classes = { PostgresLegoSetRepository.class, WithJsonRepository.class, WithHStoreRepository.class },
classes = { PostgresLegoSetRepository.class, WithJsonRepository.class, WithHStoreRepository.class, WithIdFromSequenceRepository.class },
type = FilterType.ASSIGNABLE_TYPE))
static class IntegrationTestConfiguration extends AbstractR2dbcConfiguration {
@ -151,6 +156,51 @@ public class PostgresR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposi @@ -151,6 +156,51 @@ public class PostgresR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposi
}).verifyComplete();
}
@Test
void shouldInsertWithAutoGeneratedId() {
JdbcTemplate template = new JdbcTemplate(createDataSource());
template.execute("DROP TABLE IF EXISTS with_id_from_sequence");
template.execute("CREATE SEQUENCE IF NOT EXISTS target_sequence START WITH 15");
template.execute("CREATE TABLE with_id_from_sequence(\n" //
+ " id BIGINT PRIMARY KEY,\n" //
+ " name TEXT NOT NULL" //
+ ");");
WithIdFromSequence entity = new WithIdFromSequence(null, "Jordane");
withIdFromSequenceRepository.save(entity).as(StepVerifier::create).expectNextCount(1).verifyComplete();
withIdFromSequenceRepository.findAll().as(StepVerifier::create).consumeNextWith(actual -> {
assertThat(actual.id).isNotNull().isEqualTo(15);
assertThat(actual.name).isEqualTo("Jordane");
}).verifyComplete();
}
@Test
void shouldUpdateNoIdGenerationHappens() {
JdbcTemplate template = new JdbcTemplate(createDataSource());
template.execute("DROP TABLE IF EXISTS with_id_from_sequence");
template.execute("CREATE SEQUENCE IF NOT EXISTS target_sequence");
template.execute("CREATE TABLE with_id_from_sequence(\n" //
+ " id BIGINT PRIMARY KEY,\n" //
+ " name TEXT NOT NULL" //
+ ");");
template.execute("INSERT INTO with_id_from_sequence VALUES(4, 'Alex');");
WithIdFromSequence entity = new WithIdFromSequence(4L, "NewName");
withIdFromSequenceRepository.save(entity).as(StepVerifier::create).expectNextCount(1).verifyComplete();
withJsonRepository.findAll().as(StepVerifier::create).consumeNextWith(actual -> {
assertThat(actual.jsonValue).isNotNull().isEqualTo(4);
assertThat(actual.jsonValue.asString()).isEqualTo("NewName");
}).verifyComplete();
}
@Test // gh-492
void shouldSaveAndLoadHStore() {
@ -188,6 +238,24 @@ public class PostgresR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposi @@ -188,6 +238,24 @@ public class PostgresR2dbcRepositoryIntegrationTests extends AbstractR2dbcReposi
}
static class WithIdFromSequence {
@Id
@Sequence(sequence = "target_sequence")
Long id;
String name;
public WithIdFromSequence(Long id, String name) {
this.id = id;
this.name = name;
}
}
interface WithIdFromSequenceRepository extends ReactiveCrudRepository<WithIdFromSequence, Long> {
}
@Table("with_hstore")
static class WithHStore {

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

@ -57,6 +57,7 @@ public class PostgresDialect extends AbstractDialect { @@ -57,6 +57,7 @@ public class PostgresDialect extends AbstractDialect {
private IdentifierProcessing identifierProcessing = IdentifierProcessing.create(Quoting.ANSI,
LetterCasing.LOWER_CASE);
private IdGeneration idGeneration = new IdGeneration() {
@Override

46
src/main/antora/modules/ROOT/pages/r2dbc/sequences.adoc

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
[[r2dbc.sequences]]
= Sequences Support
Since Spring Data R2DBC 3.5, properties that are annotated with `@Id` and thus represent
an Id property can additionally be annotated with `@Sequence`. This signals, that the Id property
value would be fetched from the configured sequence during an `INSERT` statement. By default,
without `@Sequence`, the identity column is assumed. Consider the following entity.
.Entity with Id generation from sequence
[source,java]
----
@Table
class MyEntity {
@Id
@Sequence(
sequence = "my_seq",
schema = "public"
)
private Long id;
private String name;
}
----
When persisting this entity, before the SQL `INSERT` Spring Data will issue an additional `SELECT`
statement to fetch the next value from the sequence. For instance, for PostgreSQL the query, issued by
Spring Data, would look like this:
.Select for next sequence value in PostgreSQL
[source,sql]
----
SELECT nextval('public.my_seq');
----
The fetched Id would later be included in the `VALUES` list during an insert:
.Insert statement enriched with Id value
[source,sql]
----
INSERT INTO "my_entity"("id", "name") VALUES(?, ?);
----
For now, the sequence support is provided for almost every dialect supported by Spring Data R2DBC.
The only exception is MySQL, since MySQL does not have sequences as such.
Loading…
Cancel
Save