|
1 | 1 | package org.springframework.data.jdbc.core.mapping;
|
2 | 2 |
|
3 |
| -import java.util.Map; |
4 | 3 | import java.util.Optional;
|
5 | 4 |
|
6 | 5 | import org.apache.commons.logging.Log;
|
7 | 6 | import org.apache.commons.logging.LogFactory;
|
8 |
| -import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration; |
| 7 | + |
| 8 | +import org.springframework.data.mapping.PersistentProperty; |
9 | 9 | import org.springframework.data.mapping.PersistentPropertyAccessor;
|
| 10 | +import org.springframework.data.mapping.context.MappingContext; |
10 | 11 | import org.springframework.data.relational.core.conversion.MutableAggregateChange;
|
11 | 12 | import org.springframework.data.relational.core.dialect.Dialect;
|
12 |
| -import org.springframework.data.relational.core.mapping.RelationalMappingContext; |
13 | 13 | import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
| 14 | +import org.springframework.data.relational.core.mapping.RelationalPersistentProperty; |
14 | 15 | import org.springframework.data.relational.core.mapping.event.BeforeSaveCallback;
|
15 | 16 | import org.springframework.data.relational.core.sql.SqlIdentifier;
|
| 17 | +import org.springframework.data.util.ReflectionUtils; |
| 18 | +import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; |
16 | 19 | import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
17 | 20 | import org.springframework.util.Assert;
|
| 21 | +import org.springframework.util.ClassUtils; |
| 22 | +import org.springframework.util.NumberUtils; |
18 | 23 |
|
19 | 24 | /**
|
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. |
22 | 26 | *
|
23 | 27 | * @author Mikhail Polivakha
|
| 28 | + * @author Mark Paluch |
| 29 | + * @since 3.5 |
| 30 | + * @see org.springframework.data.relational.core.mapping.Sequence |
24 | 31 | */
|
25 | 32 | public class IdGeneratingBeforeSaveCallback implements BeforeSaveCallback<Object> {
|
26 | 33 |
|
27 | 34 | private static final Log LOG = LogFactory.getLog(IdGeneratingBeforeSaveCallback.class);
|
| 35 | + private final static MapSqlParameterSource EMPTY_PARAMETERS = new MapSqlParameterSource(); |
28 | 36 |
|
29 |
| - private final RelationalMappingContext relationalMappingContext; |
| 37 | + private final MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext; |
30 | 38 | private final Dialect dialect;
|
31 | 39 | private final NamedParameterJdbcOperations operations;
|
32 | 40 |
|
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; |
36 | 45 | this.dialect = dialect;
|
37 |
| - this.operations = namedParameterJdbcOperations; |
| 46 | + this.operations = operations; |
38 | 47 | }
|
39 | 48 |
|
40 | 49 | @Override
|
41 | 50 | public Object onBeforeSave(Object aggregate, MutableAggregateChange<Object> aggregateChange) {
|
42 | 51 |
|
43 |
| - Assert.notNull(aggregate, "The aggregate cannot be null at this point"); |
| 52 | + Assert.notNull(aggregate, "aggregate must not be null"); |
44 | 53 |
|
45 |
| - RelationalPersistentEntity<?> persistentEntity = relationalMappingContext.getPersistentEntity(aggregate.getClass()); |
| 54 | + RelationalPersistentEntity<?> entity = mappingContext.getRequiredPersistentEntity(aggregate.getClass()); |
46 | 55 |
|
47 |
| - if (!persistentEntity.hasIdProperty()) { |
| 56 | + if (!entity.hasIdProperty()) { |
48 | 57 | return aggregate;
|
49 | 58 | }
|
50 | 59 |
|
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)) { |
55 | 64 | return aggregate;
|
56 | 65 | }
|
| 66 | + |
| 67 | + potentiallyFetchIdFromSequence(idProperty, entity, accessor); |
| 68 | + return accessor.getBean(); |
57 | 69 | }
|
58 | 70 |
|
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) { |
61 | 73 |
|
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); |
66 | 80 | }
|
| 81 | + |
| 82 | + return identifier != null; |
67 | 83 | }
|
68 | 84 |
|
69 |
| - private Object potentiallyFetchIdFromSequence(Object aggregate, RelationalPersistentEntity<?> persistentEntity) { |
| 85 | + @SuppressWarnings("unchecked") |
| 86 | + private void potentiallyFetchIdFromSequence(PersistentProperty<?> idProperty, |
| 87 | + RelationalPersistentEntity<?> persistentEntity, PersistentPropertyAccessor<Object> accessor) { |
| 88 | + |
70 | 89 | Optional<SqlIdentifier> idSequence = persistentEntity.getIdSequence();
|
71 | 90 |
|
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; |
85 | 97 | }
|
86 | 98 |
|
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 | + }); |
88 | 111 | }
|
89 | 112 | }
|
0 commit comments