Skip to content

Commit 64fca82

Browse files
committed
GH-2003 ID generation via Sequence should be omitted in UPDATE queries
Signed-off-by: mipo256 <[email protected]>
1 parent 8c017fc commit 64fca82

14 files changed

+641
-99
lines changed

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

-1
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,6 @@ private <T> RootAggregateChange<T> createInsertChange(T instance) {
557557
}
558558

559559
private <T> RootAggregateChange<T> createUpdateChange(EntityAndPreviousVersion<T> entityAndVersion) {
560-
561560
RootAggregateChange<T> aggregateChange = MutableAggregateChange.forSave(entityAndVersion.entity,
562561
entityAndVersion.version);
563562
new RelationalEntityUpdateWriter<T>(context).write(entityAndVersion.entity, aggregateChange);

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

+65-50
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.Map;
44
import java.util.Optional;
5+
56
import org.apache.commons.logging.Log;
67
import org.apache.commons.logging.LogFactory;
78
import org.springframework.data.jdbc.repository.config.AbstractJdbcConfiguration;
@@ -16,59 +17,73 @@
1617
import org.springframework.util.Assert;
1718

1819
/**
19-
* Callback for generating ID via the database sequence. By default, it is registered as a
20-
* bean in {@link AbstractJdbcConfiguration}
20+
* Callback for generating ID via the database sequence. By default, it is registered as a bean in
21+
* {@link AbstractJdbcConfiguration}
2122
*
2223
* @author Mikhail Polivakha
2324
*/
2425
public class IdGeneratingBeforeSaveCallback implements BeforeSaveCallback<Object> {
2526

26-
private static final Log LOG = LogFactory.getLog(IdGeneratingBeforeSaveCallback.class);
27-
28-
private final RelationalMappingContext relationalMappingContext;
29-
private final Dialect dialect;
30-
private final NamedParameterJdbcOperations operations;
31-
32-
public IdGeneratingBeforeSaveCallback(
33-
RelationalMappingContext relationalMappingContext,
34-
Dialect dialect,
35-
NamedParameterJdbcOperations namedParameterJdbcOperations
36-
) {
37-
this.relationalMappingContext = relationalMappingContext;
38-
this.dialect = dialect;
39-
this.operations = namedParameterJdbcOperations;
40-
}
41-
42-
@Override
43-
public Object onBeforeSave(Object aggregate, MutableAggregateChange<Object> aggregateChange) {
44-
45-
Assert.notNull(aggregate, "The aggregate cannot be null at this point");
46-
47-
RelationalPersistentEntity<?> persistentEntity = relationalMappingContext.getPersistentEntity(aggregate.getClass());
48-
Optional<SqlIdentifier> idSequence = persistentEntity.getIdSequence();
49-
50-
if (dialect.getIdGeneration().sequencesSupported()) {
51-
52-
if (persistentEntity.getIdProperty() != null) {
53-
idSequence
54-
.map(s -> dialect.getIdGeneration().createSequenceQuery(s))
55-
.ifPresent(sql -> {
56-
Long idValue = operations.queryForObject(sql, Map.of(), (rs, rowNum) -> rs.getLong(1));
57-
PersistentPropertyAccessor<Object> propertyAccessor = persistentEntity.getPropertyAccessor(aggregate);
58-
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), idValue);
59-
});
60-
}
61-
} else {
62-
if (idSequence.isPresent()) {
63-
LOG.warn("""
64-
It seems you're trying to insert an aggregate of type '%s' annotated with @TargetSequence, but the problem is RDBMS you're
65-
working with does not support sequences as such. Falling back to identity columns
66-
"""
67-
.formatted(aggregate.getClass().getName())
68-
);
69-
}
70-
}
71-
72-
return aggregate;
73-
}
27+
private static final Log LOG = LogFactory.getLog(IdGeneratingBeforeSaveCallback.class);
28+
29+
private final RelationalMappingContext relationalMappingContext;
30+
private final Dialect dialect;
31+
private final NamedParameterJdbcOperations operations;
32+
33+
public IdGeneratingBeforeSaveCallback(RelationalMappingContext relationalMappingContext, Dialect dialect,
34+
NamedParameterJdbcOperations namedParameterJdbcOperations) {
35+
this.relationalMappingContext = relationalMappingContext;
36+
this.dialect = dialect;
37+
this.operations = namedParameterJdbcOperations;
38+
}
39+
40+
@Override
41+
public Object onBeforeSave(Object aggregate, MutableAggregateChange<Object> aggregateChange) {
42+
43+
Assert.notNull(aggregate, "The aggregate cannot be null at this point");
44+
45+
RelationalPersistentEntity<?> persistentEntity = relationalMappingContext.getPersistentEntity(aggregate.getClass());
46+
47+
if (persistentEntity.hasIdProperty()) {
48+
return aggregate;
49+
}
50+
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 {
55+
return aggregate;
56+
}
57+
}
58+
59+
private boolean hasIdentifierValue(Object aggregate, RelationalPersistentEntity<?> persistentEntity) {
60+
Object identifier = persistentEntity.getIdentifierAccessor(aggregate).getIdentifier();
61+
62+
if (persistentEntity.getIdProperty().getType().isPrimitive()) {
63+
return identifier instanceof Number num && num.longValue() != 0L;
64+
} else {
65+
return identifier != null;
66+
}
67+
}
68+
69+
private Object potentiallyFetchIdFromSequence(Object aggregate, RelationalPersistentEntity<?> persistentEntity) {
70+
Optional<SqlIdentifier> idSequence = persistentEntity.getIdSequence();
71+
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+
}
85+
}
86+
87+
return aggregate;
88+
}
7489
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.springframework.data.jdbc.testing.EnabledOnDatabase;
2929
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3030
import org.springframework.data.repository.CrudRepository;
31+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
3132
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
3233

3334
/**
@@ -40,7 +41,7 @@
4041
abstract class AbstractJdbcRepositoryLookUpStrategyTests {
4142

4243
@Autowired protected OnesRepository onesRepository;
43-
@Autowired NamedParameterJdbcTemplate template;
44+
@Autowired NamedParameterJdbcOperations template;
4445
@Autowired RelationalMappingContext context;
4546

4647
void insertTestInstances() {

0 commit comments

Comments
 (0)