Skip to content

Commit 249b75c

Browse files
committed
Polishing.
Extract SequenceEntityCallbackDelegate from IdGeneratingBeforeSaveCallback. Renameto IdGeneratingEntityCallback and move callback to convert package. Align return values and associate generated sequence value with the entity. Fix test. Add ticket references to tests. Extract documentation partials. See #1955 Original pull request: #2028
1 parent 000b859 commit 249b75c

File tree

11 files changed

+316
-210
lines changed

11 files changed

+316
-210
lines changed

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -48,22 +48,22 @@ public IdGeneratingEntityCallback(
4848
@Override
4949
public Object onBeforeSave(Object aggregate, MutableAggregateChange<Object> aggregateChange) {
5050

51-
Assert.notNull(aggregate, "aggregate must not be null");
51+
Assert.notNull(aggregate, "Aggregate must not be null");
5252

5353
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(aggregate.getClass());
5454

5555
if (!entity.hasIdProperty()) {
5656
return aggregate;
5757
}
5858

59-
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
59+
RelationalPersistentProperty property = entity.getRequiredIdProperty();
6060
PersistentPropertyAccessor<Object> accessor = entity.getPropertyAccessor(aggregate);
6161

62-
if (!entity.isNew(aggregate) || delegate.hasValue(idProperty, accessor) || !idProperty.hasSequence()) {
62+
if (!entity.isNew(aggregate) || delegate.hasValue(property, accessor) || !property.hasSequence()) {
6363
return aggregate;
6464
}
6565

66-
delegate.generateSequenceValue(idProperty, accessor);
66+
delegate.generateSequenceValue(property, accessor);
6767

6868
return accessor.getBean();
6969
}

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

+7-5
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,13 @@
3333
import org.springframework.core.convert.converter.Converter;
3434
import org.springframework.data.convert.CustomConversions;
3535
import org.springframework.data.convert.CustomConversions.StoreConversions;
36+
import org.springframework.data.r2dbc.convert.IdGeneratingEntityCallback;
3637
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
3738
import org.springframework.data.r2dbc.convert.R2dbcConverter;
3839
import org.springframework.data.r2dbc.convert.R2dbcCustomConversions;
3940
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
4041
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
4142
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
42-
import org.springframework.data.r2dbc.core.mapping.IdGeneratingBeforeSaveCallback;
4343
import org.springframework.data.r2dbc.dialect.DialectResolver;
4444
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
4545
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
@@ -185,14 +185,16 @@ public R2dbcMappingContext r2dbcMappingContext(Optional<NamingStrategy> namingSt
185185
}
186186

187187
/**
188-
* Register a {@link IdGeneratingBeforeSaveCallback} using
188+
* Register a {@link IdGeneratingEntityCallback} using
189189
* {@link #r2dbcMappingContext(Optional, R2dbcCustomConversions, RelationalManagedTypes)} and
190-
* {@link #databaseClient()}
190+
* {@link #databaseClient()}.
191+
*
192+
* @since 3.5
191193
*/
192194
@Bean
193-
public IdGeneratingBeforeSaveCallback idGeneratingBeforeSaveCallback(
195+
public IdGeneratingEntityCallback idGeneratingBeforeSaveCallback(
194196
RelationalMappingContext relationalMappingContext, DatabaseClient databaseClient) {
195-
return new IdGeneratingBeforeSaveCallback(relationalMappingContext, getDialect(lookupConnectionFactory()),
197+
return new IdGeneratingEntityCallback(relationalMappingContext, getDialect(lookupConnectionFactory()),
196198
databaseClient);
197199
}
198200

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.r2dbc.convert;
17+
18+
import reactor.core.publisher.Mono;
19+
20+
import org.springframework.data.mapping.PersistentPropertyAccessor;
21+
import org.springframework.data.mapping.context.MappingContext;
22+
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
23+
import org.springframework.data.r2dbc.mapping.OutboundRow;
24+
import org.springframework.data.r2dbc.mapping.event.BeforeSaveCallback;
25+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
26+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
27+
import org.springframework.data.relational.core.sql.SqlIdentifier;
28+
import org.springframework.r2dbc.core.DatabaseClient;
29+
import org.springframework.util.Assert;
30+
31+
/**
32+
* Callback for generating identifier values through a database sequence.
33+
*
34+
* @author Mikhail Polivakha
35+
* @author Mark Paluch
36+
* @since 3.5
37+
*/
38+
public class IdGeneratingEntityCallback implements BeforeSaveCallback<Object> {
39+
40+
private final MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context;
41+
private final SequenceEntityCallbackDelegate delegate;
42+
43+
public IdGeneratingEntityCallback(
44+
MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
45+
R2dbcDialect dialect,
46+
DatabaseClient databaseClient) {
47+
48+
this.context = context;
49+
this.delegate = new SequenceEntityCallbackDelegate(dialect, databaseClient);
50+
}
51+
52+
@Override
53+
public Mono<Object> onBeforeSave(Object entity, OutboundRow row, SqlIdentifier table) {
54+
55+
Assert.notNull(entity, "Entity must not be null");
56+
57+
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(entity.getClass());
58+
59+
if (!persistentEntity.hasIdProperty()) {
60+
return Mono.just(entity);
61+
}
62+
63+
RelationalPersistentProperty property = persistentEntity.getRequiredIdProperty();
64+
PersistentPropertyAccessor<Object> accessor = persistentEntity.getPropertyAccessor(entity);
65+
66+
if (!persistentEntity.isNew(entity) || delegate.hasValue(property, accessor) || !property.hasSequence()) {
67+
return Mono.just(entity);
68+
}
69+
70+
Mono<Object> idGenerator = delegate.generateSequenceValue(property, row, accessor);
71+
72+
return idGenerator.defaultIfEmpty(entity);
73+
}
74+
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.r2dbc.convert;
17+
18+
import reactor.core.publisher.Mono;
19+
20+
import org.apache.commons.logging.Log;
21+
import org.apache.commons.logging.LogFactory;
22+
23+
import org.springframework.data.mapping.PersistentProperty;
24+
import org.springframework.data.mapping.PersistentPropertyAccessor;
25+
import org.springframework.data.r2dbc.mapping.OutboundRow;
26+
import org.springframework.data.relational.core.dialect.Dialect;
27+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
28+
import org.springframework.data.relational.core.sql.SqlIdentifier;
29+
import org.springframework.data.util.ReflectionUtils;
30+
import org.springframework.r2dbc.core.DatabaseClient;
31+
import org.springframework.r2dbc.core.Parameter;
32+
import org.springframework.util.ClassUtils;
33+
import org.springframework.util.NumberUtils;
34+
35+
/**
36+
* Support class for generating identifier values through a database sequence.
37+
*
38+
* @author Mikhail Polivakha
39+
* @author Mark Paluch
40+
* @since 3.5
41+
* @see org.springframework.data.relational.core.mapping.Sequence
42+
*/
43+
class SequenceEntityCallbackDelegate {
44+
45+
private static final Log LOG = LogFactory.getLog(SequenceEntityCallbackDelegate.class);
46+
47+
private final Dialect dialect;
48+
private final DatabaseClient databaseClient;
49+
50+
public SequenceEntityCallbackDelegate(Dialect dialect, DatabaseClient databaseClient) {
51+
this.dialect = dialect;
52+
this.databaseClient = databaseClient;
53+
}
54+
55+
@SuppressWarnings("unchecked")
56+
protected Mono<Object> generateSequenceValue(RelationalPersistentProperty property, OutboundRow row,
57+
PersistentPropertyAccessor<Object> accessor) {
58+
59+
Class<?> targetType = ClassUtils.resolvePrimitiveIfNecessary(property.getType());
60+
61+
return getSequenceValue(property).map(it -> {
62+
63+
Object sequenceValue = it;
64+
if (sequenceValue instanceof Number && Number.class.isAssignableFrom(targetType)) {
65+
sequenceValue = NumberUtils.convertNumberToTargetClass((Number) sequenceValue,
66+
(Class<? extends Number>) targetType);
67+
}
68+
69+
row.append(property.getColumnName(), Parameter.from(sequenceValue));
70+
accessor.setProperty(property, sequenceValue);
71+
72+
return accessor.getBean();
73+
});
74+
}
75+
76+
protected boolean hasValue(PersistentProperty<?> property, PersistentPropertyAccessor<Object> propertyAccessor) {
77+
78+
Object identifier = propertyAccessor.getProperty(property);
79+
80+
if (property.getType().isPrimitive()) {
81+
82+
Object primitiveDefault = ReflectionUtils.getPrimitiveDefault(property.getType());
83+
return !primitiveDefault.equals(identifier);
84+
}
85+
86+
return identifier != null;
87+
}
88+
89+
private Mono<Object> getSequenceValue(RelationalPersistentProperty property) {
90+
91+
SqlIdentifier sequence = property.getSequence();
92+
93+
if (sequence != null && !dialect.getIdGeneration().sequencesSupported()) {
94+
LOG.warn("""
95+
Entity type '%s' is marked for sequence usage but configured dialect '%s'
96+
does not support sequences. Falling back to identity columns.
97+
""".formatted(property.getOwner().getType(), ClassUtils.getQualifiedName(dialect.getClass())));
98+
return Mono.empty();
99+
}
100+
101+
String sql = dialect.getIdGeneration().createSequenceQuery(sequence);
102+
return databaseClient //
103+
.sql(sql) //
104+
.map((r, rowMetadata) -> r.get(0)) //
105+
.one();
106+
}
107+
108+
}

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

-104
This file was deleted.

0 commit comments

Comments
 (0)