Skip to content

Commit 91faf45

Browse files
committedApr 9, 2025
Move Sequence to RelationalPersistentProperty.
Sequence details are now maintained on the property level instead of using the entity level. This is a more accurate representation of the underlying model and that properties are annotated and not entities. It also allows future extension of expanding sequence support to general properties. Extract abstract support class for sequence generation. Move types to org.springframework.data.jdbc.core.convert to resolve package cycles. See #2003 Original pull request: #2005
1 parent d0e43be commit 91faf45

16 files changed

+381
-288
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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.jdbc.core.convert;
17+
18+
import org.springframework.data.mapping.PersistentPropertyAccessor;
19+
import org.springframework.data.mapping.context.MappingContext;
20+
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
21+
import org.springframework.data.relational.core.dialect.Dialect;
22+
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
23+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
24+
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
25+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
26+
import org.springframework.util.Assert;
27+
28+
/**
29+
* Callback for generating identifier values through a database sequence.
30+
*
31+
* @author Mikhail Polivakha
32+
* @author Mark Paluch
33+
* @since 3.5
34+
*/
35+
public class IdGeneratingBeforeSaveCallback extends SequenceEntityCallbackSupport
36+
implements BeforeSaveCallback<Object> {
37+
38+
private final MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context;
39+
40+
public IdGeneratingBeforeSaveCallback(
41+
MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context, Dialect dialect,
42+
NamedParameterJdbcOperations operations) {
43+
44+
super(dialect, operations);
45+
this.context = context;
46+
}
47+
48+
@Override
49+
public Object onBeforeSave(Object aggregate, MutableAggregateChange<Object> aggregateChange) {
50+
51+
Assert.notNull(aggregate, "aggregate must not be null");
52+
53+
RelationalPersistentEntity<?> entity = context.getRequiredPersistentEntity(aggregate.getClass());
54+
55+
if (!entity.hasIdProperty()) {
56+
return aggregate;
57+
}
58+
59+
RelationalPersistentProperty property = entity.getRequiredIdProperty();
60+
PersistentPropertyAccessor<Object> accessor = entity.getPropertyAccessor(aggregate);
61+
62+
if (!entity.isNew(aggregate) || hasValue(property, accessor) || !property.hasSequence()) {
63+
return aggregate;
64+
}
65+
66+
generateSequenceValue(property, accessor);
67+
68+
return accessor.getBean();
69+
}
70+
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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.jdbc.core.convert;
17+
18+
import org.apache.commons.logging.Log;
19+
import org.apache.commons.logging.LogFactory;
20+
21+
import org.springframework.data.mapping.PersistentProperty;
22+
import org.springframework.data.mapping.PersistentPropertyAccessor;
23+
import org.springframework.data.relational.core.dialect.Dialect;
24+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
25+
import org.springframework.data.relational.core.sql.SqlIdentifier;
26+
import org.springframework.data.util.ReflectionUtils;
27+
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
28+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
29+
import org.springframework.lang.Nullable;
30+
import org.springframework.util.ClassUtils;
31+
import org.springframework.util.NumberUtils;
32+
33+
/**
34+
* Support class for generating identifier values through a database sequence.
35+
*
36+
* @author Mikhail Polivakha
37+
* @author Mark Paluch
38+
* @since 3.5
39+
* @see org.springframework.data.relational.core.mapping.Sequence
40+
*/
41+
public abstract class SequenceEntityCallbackSupport {
42+
43+
private static final Log LOG = LogFactory.getLog(SequenceEntityCallbackSupport.class);
44+
private final static MapSqlParameterSource EMPTY_PARAMETERS = new MapSqlParameterSource();
45+
46+
private final Dialect dialect;
47+
private final NamedParameterJdbcOperations operations;
48+
49+
public SequenceEntityCallbackSupport(Dialect dialect, NamedParameterJdbcOperations operations) {
50+
this.dialect = dialect;
51+
this.operations = operations;
52+
}
53+
54+
@SuppressWarnings("unchecked")
55+
protected void generateSequenceValue(RelationalPersistentProperty property,
56+
PersistentPropertyAccessor<Object> accessor) {
57+
58+
Object sequenceValue = getSequenceValue(property);
59+
60+
if (sequenceValue == null) {
61+
return;
62+
}
63+
64+
Class<?> targetType = ClassUtils.resolvePrimitiveIfNecessary(property.getType());
65+
if (sequenceValue instanceof Number && Number.class.isAssignableFrom(targetType)) {
66+
sequenceValue = NumberUtils.convertNumberToTargetClass((Number) sequenceValue,
67+
(Class<? extends Number>) targetType);
68+
}
69+
70+
accessor.setProperty(property, sequenceValue);
71+
}
72+
73+
protected boolean hasValue(PersistentProperty<?> property, PersistentPropertyAccessor<Object> propertyAccessor) {
74+
75+
Object identifier = propertyAccessor.getProperty(property);
76+
77+
if (property.getType().isPrimitive()) {
78+
79+
Object primitiveDefault = ReflectionUtils.getPrimitiveDefault(property.getType());
80+
return !primitiveDefault.equals(identifier);
81+
}
82+
83+
return identifier != null;
84+
}
85+
86+
private @Nullable Object getSequenceValue(RelationalPersistentProperty property) {
87+
88+
SqlIdentifier sequence = property.getSequence();
89+
90+
if (sequence != null && !dialect.getIdGeneration().sequencesSupported()) {
91+
LOG.warn("""
92+
Aggregate type '%s' is marked for sequence usage but configured dialect '%s'
93+
does not support sequences. Falling back to identity columns.
94+
""".formatted(property.getOwner().getType(), ClassUtils.getQualifiedName(dialect.getClass())));
95+
return null;
96+
}
97+
98+
String sql = dialect.getIdGeneration().createSequenceQuery(sequence);
99+
return operations.queryForObject(sql, EMPTY_PARAMETERS, (rs, rowNum) -> rs.getObject(1));
100+
}
101+
102+
}

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

-112
This file was deleted.

‎spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import org.apache.commons.logging.Log;
2727
import org.apache.commons.logging.LogFactory;
28+
2829
import org.springframework.beans.BeansException;
2930
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3031
import org.springframework.context.ApplicationContext;
@@ -38,7 +39,6 @@
3839
import org.springframework.data.jdbc.core.JdbcAggregateTemplate;
3940
import org.springframework.data.jdbc.core.convert.*;
4041
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
41-
import org.springframework.data.jdbc.core.mapping.IdGeneratingBeforeSaveCallback;
4242
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
4343
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
4444
import org.springframework.data.mapping.model.SimpleTypeHolder;
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2024-2025 the original author or authors.
2+
* Copyright 2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
package org.springframework.data.jdbc.core.mapping;
16+
package org.springframework.data.jdbc.core.convert;
1717

1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.mockito.ArgumentMatchers.*;

‎spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import org.springframework.data.annotation.PersistenceCreator;
3636
import org.springframework.data.annotation.Transient;
3737
import org.springframework.data.domain.Persistable;
38-
import org.springframework.data.jdbc.core.mapping.IdGeneratingBeforeSaveCallback;
38+
import org.springframework.data.jdbc.core.convert.IdGeneratingBeforeSaveCallback;
3939
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories;
4040
import org.springframework.data.jdbc.repository.support.SimpleJdbcRepository;
4141
import org.springframework.data.jdbc.testing.IntegrationTest;

‎spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
import org.apache.ibatis.session.SqlSessionFactory;
2626
import org.mockito.Mockito;
27+
2728
import org.springframework.beans.factory.BeanFactory;
2829
import org.springframework.beans.factory.annotation.Autowired;
2930
import org.springframework.beans.factory.annotation.Qualifier;
@@ -37,15 +38,13 @@
3738
import org.springframework.data.convert.CustomConversions;
3839
import org.springframework.data.jdbc.core.convert.*;
3940
import org.springframework.data.jdbc.core.dialect.JdbcDialect;
40-
import org.springframework.data.jdbc.core.mapping.IdGeneratingBeforeSaveCallback;
4141
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
4242
import org.springframework.data.jdbc.core.mapping.JdbcSimpleTypes;
4343
import org.springframework.data.jdbc.repository.config.DialectResolver;
4444
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
4545
import org.springframework.data.mapping.callback.EntityCallback;
4646
import org.springframework.data.mapping.callback.EntityCallbacks;
4747
import org.springframework.data.mapping.model.SimpleTypeHolder;
48-
import org.springframework.data.relational.RelationalManagedTypes;
4948
import org.springframework.data.relational.core.dialect.Dialect;
5049
import org.springframework.data.relational.core.mapping.DefaultNamingStrategy;
5150
import org.springframework.data.relational.core.mapping.NamingStrategy;

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,13 @@ public enum IdValueSource {
4949
*/
5050
public static <T> IdValueSource forInstance(Object instance, RelationalPersistentEntity<T> persistentEntity) {
5151

52-
if (persistentEntity.getIdSequence().isPresent()) {
52+
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
53+
54+
if (idProperty != null && idProperty.hasSequence()) {
5355
return IdValueSource.PROVIDED;
5456
}
5557

5658
Object idValue = persistentEntity.getIdentifierAccessor(instance).getIdentifier();
57-
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
5859
if (idProperty == null) {
5960
return IdValueSource.NONE;
6061
}

0 commit comments

Comments
 (0)
Please sign in to comment.