Skip to content

Commit d0e43be

Browse files
committed
Polishing.
Refine assignment flow and use early returns where possible. Cache empty MapSqlParameterSource. Reduce dependency on RelationalMappingContext using a lower-level abstraction signature. Simplify names. Use default value check from Commons. Fix log warning message. Add missing since tags. Remove superfluous annotations and redundant code. Tweak documentation wording. Closes #2003 Original pull request: #2005
1 parent 0fc3187 commit d0e43be

File tree

9 files changed

+252
-200
lines changed

9 files changed

+252
-200
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,112 @@
11
package org.springframework.data.jdbc.core.mapping;
22

3-
import java.util.Map;
43
import java.util.Optional;
54

65
import org.apache.commons.logging.Log;
76
import org.apache.commons.logging.LogFactory;
8-
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
7+
8+
import org.springframework.data.mapping.PersistentProperty;
99
import org.springframework.data.mapping.PersistentPropertyAccessor;
10+
import org.springframework.data.mapping.context.MappingContext;
1011
import org.springframework.data.relational.core.conversion.MutableAggregateChange;
1112
import org.springframework.data.relational.core.dialect.Dialect;
12-
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
1313
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
14+
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
1415
import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
1516
import org.springframework.data.relational.core.sql.SqlIdentifier;
17+
import org.springframework.data.util.ReflectionUtils;
18+
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
1619
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
1720
import org.springframework.util.Assert;
21+
import org.springframework.util.ClassUtils;
22+
import org.springframework.util.NumberUtils;
1823

1924
/**
20-
* Callback for generating ID via the database sequence. By default, it is registered as a bean in
21-
* {@link AbstractJdbcConfiguration}
25+
* Callback for generating identifier values through a database sequence.
2226
*
2327
* @author Mikhail Polivakha
28+
* @author Mark Paluch
29+
* @since 3.5
30+
* @see org.springframework.data.relational.core.mapping.Sequence
2431
*/
2532
public class IdGeneratingBeforeSaveCallback implements BeforeSaveCallback<Object> {
2633

2734
private static final Log LOG = LogFactory.getLog(IdGeneratingBeforeSaveCallback.class);
35+
private final static MapSqlParameterSource EMPTY_PARAMETERS = new MapSqlParameterSource();
2836

29-
private final RelationalMappingContext relationalMappingContext;
37+
private final MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext;
3038
private final Dialect dialect;
3139
private final NamedParameterJdbcOperations operations;
3240

33-
public IdGeneratingBeforeSaveCallback(RelationalMappingContext relationalMappingContext, Dialect dialect,
34-
NamedParameterJdbcOperations namedParameterJdbcOperations) {
35-
this.relationalMappingContext = relationalMappingContext;
41+
public IdGeneratingBeforeSaveCallback(
42+
MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext,
43+
Dialect dialect, NamedParameterJdbcOperations operations) {
44+
this.mappingContext = mappingContext;
3645
this.dialect = dialect;
37-
this.operations = namedParameterJdbcOperations;
46+
this.operations = operations;
3847
}
3948

4049
@Override
4150
public Object onBeforeSave(Object aggregate, MutableAggregateChange<Object> aggregateChange) {
4251

43-
Assert.notNull(aggregate, "The aggregate cannot be null at this point");
52+
Assert.notNull(aggregate, "aggregate must not be null");
4453

45-
RelationalPersistentEntity<?> persistentEntity = relationalMappingContext.getPersistentEntity(aggregate.getClass());
54+
RelationalPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(aggregate.getClass());
4655

47-
if (!persistentEntity.hasIdProperty()) {
56+
if (!entity.hasIdProperty()) {
4857
return aggregate;
4958
}
5059

51-
// we're doing INSERT and ID property value is not set explicitly by client
52-
if (persistentEntity.isNew(aggregate) && !hasIdentifierValue(aggregate, persistentEntity)) {
53-
return potentiallyFetchIdFromSequence(aggregate, persistentEntity);
54-
} else {
60+
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
61+
PersistentPropertyAccessor<Object> accessor = entity.getPropertyAccessor(aggregate);
62+
63+
if (!entity.isNew(aggregate) || hasIdentifierValue(idProperty, accessor)) {
5564
return aggregate;
5665
}
66+
67+
potentiallyFetchIdFromSequence(idProperty, entity, accessor);
68+
return accessor.getBean();
5769
}
5870

59-
private boolean hasIdentifierValue(Object aggregate, RelationalPersistentEntity<?> persistentEntity) {
60-
Object identifier = persistentEntity.getIdentifierAccessor(aggregate).getIdentifier();
71+
private boolean hasIdentifierValue(PersistentProperty<?> idProperty,
72+
PersistentPropertyAccessor<Object> propertyAccessor) {
6173

62-
if (persistentEntity.getIdProperty().getType().isPrimitive()) {
63-
return identifier instanceof Number num && num.longValue() != 0L;
64-
} else {
65-
return identifier != null;
74+
Object identifier = propertyAccessor.getProperty(idProperty);
75+
76+
if (idProperty.getType().isPrimitive()) {
77+
78+
Object primitiveDefault = ReflectionUtils.getPrimitiveDefault(idProperty.getType());
79+
return !primitiveDefault.equals(identifier);
6680
}
81+
82+
return identifier != null;
6783
}
6884

69-
private Object potentiallyFetchIdFromSequence(Object aggregate, RelationalPersistentEntity<?> persistentEntity) {
85+
@SuppressWarnings("unchecked")
86+
private void potentiallyFetchIdFromSequence(PersistentProperty<?> idProperty,
87+
RelationalPersistentEntity<?> persistentEntity, PersistentPropertyAccessor<Object> accessor) {
88+
7089
Optional<SqlIdentifier> idSequence = persistentEntity.getIdSequence();
7190

72-
if (dialect.getIdGeneration().sequencesSupported()) {
73-
idSequence.map(s -> dialect.getIdGeneration().createSequenceQuery(s)).ifPresent(sql -> {
74-
Long idValue = operations.queryForObject(sql, Map.of(), (rs, rowNum) -> rs.getLong(1));
75-
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(aggregate);
76-
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), idValue);
77-
});
78-
} else {
79-
if (idSequence.isPresent()) {
80-
LOG.warn("""
81-
It seems you're trying to insert an aggregate of type '%s' annotated with @TargetSequence, but the problem is RDBMS you're
82-
working with does not support sequences as such. Falling back to identity columns
83-
""".formatted(aggregate.getClass().getName()));
84-
}
91+
if (idSequence.isPresent() && !dialect.getIdGeneration().sequencesSupported()) {
92+
LOG.warn("""
93+
Aggregate type '%s' is marked for sequence usage but configured dialect '%s'
94+
does not support sequences. Falling back to identity columns.
95+
""".formatted(persistentEntity.getType(), ClassUtils.getQualifiedName(dialect.getClass())));
96+
return;
8597
}
8698

87-
return aggregate;
99+
idSequence.map(s -> dialect.getIdGeneration().createSequenceQuery(s)).ifPresent(sql -> {
100+
101+
Object idValue = operations.queryForObject(sql, EMPTY_PARAMETERS, (rs, rowNum) -> rs.getObject(1));
102+
103+
Class<?> targetType = ClassUtils.resolvePrimitiveIfNecessary(idProperty.getType());
104+
if (idValue instanceof Number && Number.class.isAssignableFrom(targetType)) {
105+
accessor.setProperty(idProperty,
106+
NumberUtils.convertNumberToTargetClass((Number) idValue, (Class<? extends Number>) targetType));
107+
} else {
108+
accessor.setProperty(idProperty, idValue);
109+
}
110+
});
88111
}
89112
}

Diff for: spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/AbstractJdbcConfiguration.java

+4-7
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,11 @@ public JdbcMappingContext jdbcMappingContext(Optional<NamingStrategy> namingStra
126126
* {@link #jdbcDialect(NamedParameterJdbcOperations)}.
127127
*
128128
* @return must not be {@literal null}.
129+
* @since 3.5
129130
*/
130131
@Bean
131-
public IdGeneratingBeforeSaveCallback idGeneratingBeforeSaveCallback(
132-
JdbcMappingContext mappingContext,
133-
NamedParameterJdbcOperations operations,
134-
Dialect dialect
135-
) {
132+
public IdGeneratingBeforeSaveCallback idGeneratingBeforeSaveCallback(JdbcMappingContext mappingContext,
133+
NamedParameterJdbcOperations operations, Dialect dialect) {
136134
return new IdGeneratingBeforeSaveCallback(mappingContext, dialect, operations);
137135
}
138136

@@ -224,8 +222,7 @@ public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations op
224222

225223
SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(context, jdbcConverter, dialect);
226224
DataAccessStrategyFactory factory = new DataAccessStrategyFactory(sqlGeneratorSource, jdbcConverter, operations,
227-
new SqlParametersFactory(context, jdbcConverter),
228-
new InsertStrategyFactory(operations, dialect));
225+
new SqlParametersFactory(context, jdbcConverter), new InsertStrategyFactory(operations, dialect));
229226

230227
return factory.create();
231228
}

0 commit comments

Comments
 (0)