Skip to content

Commit d3a35e4

Browse files
committed
Added Sequence generation support
1 parent 52fdadd commit d3a35e4

File tree

37 files changed

+590
-51
lines changed

37 files changed

+590
-51
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ private <S> Object setIdAndCascadingProperties(DbAction.WithEntity<S> action, @N
298298
PersistentPropertyPathAccessor<S> propertyAccessor = converter.getPropertyAccessor(persistentEntity,
299299
originalEntity);
300300

301-
if (IdValueSource.GENERATED.equals(action.getIdValueSource())) {
301+
if (IdValueSource.isGeneratedByDatabased(action.getIdValueSource())) {
302302
propertyAccessor.setProperty(persistentEntity.getRequiredIdProperty(), generatedId);
303303
}
304304

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

+94-15
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,16 @@
1919

2020
import java.sql.ResultSet;
2121
import java.sql.SQLException;
22+
import java.util.Arrays;
2223
import java.util.Collections;
2324
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Objects;
2427
import java.util.Optional;
28+
import java.util.Set;
29+
import java.util.stream.Collectors;
30+
import java.util.stream.IntStream;
31+
import java.util.stream.LongStream;
2532

2633
import org.springframework.dao.EmptyResultDataAccessException;
2734
import org.springframework.dao.OptimisticLockingFailureException;
@@ -37,6 +44,7 @@
3744
import org.springframework.data.relational.core.query.Query;
3845
import org.springframework.data.relational.core.sql.LockMode;
3946
import org.springframework.data.relational.core.sql.SqlIdentifier;
47+
import org.springframework.data.util.Pair;
4048
import org.springframework.jdbc.core.RowMapper;
4149
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
4250
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
@@ -60,6 +68,7 @@
6068
* @author Radim Tlusty
6169
* @author Chirag Tailor
6270
* @author Diego Krupitza
71+
* @author Mikhail Polivakha
6372
* @since 1.1
6473
*/
6574
public class DefaultDataAccessStrategy implements DataAccessStrategy {
@@ -102,31 +111,35 @@ public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, Relation
102111
@Override
103112
public <T> Object insert(T instance, Class<T> domainType, Identifier identifier, IdValueSource idValueSource) {
104113

105-
SqlIdentifierParameterSource parameterSource = sqlParametersFactory.forInsert(instance, domainType, identifier,
106-
idValueSource);
114+
RelationalPersistentEntity<?> persistentEntity = context.getRequiredPersistentEntity(domainType);
115+
116+
Optional<Long> idFromSequence = getIdFromSequenceIfAnyDefined(idValueSource, persistentEntity);
117+
118+
SqlIdentifierParameterSource parameterSource = idFromSequence
119+
.map(it -> sqlParametersFactory.forInsert(instance, domainType, identifier, it))
120+
.orElseGet(() -> sqlParametersFactory.forInsert(instance, domainType, identifier, idValueSource));
107121

108122
String insertSql = sql(domainType).getInsert(parameterSource.getIdentifiers());
109123

110-
return insertStrategyFactory.insertStrategy(idValueSource, getIdColumn(domainType)).execute(insertSql,
111-
parameterSource);
112-
}
124+
Object idAfterExecute = insertStrategyFactory.insertStrategy(idValueSource, getIdColumn(domainType))
125+
.execute(insertSql, parameterSource);
126+
127+
return idFromSequence.map(it -> (Object) it).orElse(idAfterExecute);
128+
}
113129

114130
@Override
115131
public <T> Object[] insert(List<InsertSubject<T>> insertSubjects, Class<T> domainType, IdValueSource idValueSource) {
116132

117133
Assert.notEmpty(insertSubjects, "Batch insert must contain at least one InsertSubject");
118-
SqlIdentifierParameterSource[] sqlParameterSources = insertSubjects.stream()
119-
.map(insertSubject -> sqlParametersFactory.forInsert(insertSubject.getInstance(), domainType,
120-
insertSubject.getIdentifier(), idValueSource))
121-
.toArray(SqlIdentifierParameterSource[]::new);
122134

123-
String insertSql = sql(domainType).getInsert(sqlParameterSources[0].getIdentifiers());
135+
if (IdValueSource.SEQUENCE.equals(idValueSource)) {
136+
return executeBatchInsertWithSequenceAsIdSource(insertSubjects, domainType, idValueSource);
137+
} else {
138+
return executeBatchInsert(insertSubjects, domainType, idValueSource);
139+
}
140+
}
124141

125-
return insertStrategyFactory.batchInsertStrategy(idValueSource, getIdColumn(domainType)).execute(insertSql,
126-
sqlParameterSources);
127-
}
128-
129-
@Override
142+
@Override
130143
public <S> boolean update(S instance, Class<S> domainType) {
131144

132145
SqlIdentifierParameterSource parameterSource = sqlParametersFactory.forUpdate(instance, domainType);
@@ -446,4 +459,70 @@ private Class<?> getBaseType(PersistentPropertyPath<RelationalPersistentProperty
446459
return baseProperty.getOwner().getType();
447460
}
448461

462+
private <T> Object[] executeBatchInsert(List<InsertSubject<T>> insertSubjects, Class<T> domainType, IdValueSource idValueSource) {
463+
SqlIdentifierParameterSource[] sqlParameterSources = insertSubjects
464+
.stream()
465+
.map(insertSubject -> sqlParametersFactory.forInsert(
466+
insertSubject.getInstance(), domainType,
467+
insertSubject.getIdentifier(), idValueSource)
468+
)
469+
.toArray(SqlIdentifierParameterSource[]::new);
470+
471+
String insertSql = sql(domainType).getInsert(sqlParameterSources[0].getIdentifiers());
472+
473+
return insertStrategyFactory.batchInsertStrategy(idValueSource, getIdColumn(domainType))
474+
.execute(insertSql, sqlParameterSources);
475+
}
476+
477+
private <T> Object[] executeBatchInsertWithSequenceAsIdSource(List<InsertSubject<T>> insertSubjects, Class<T> domainType, IdValueSource idValueSource) {
478+
List<Pair<Long, SqlIdentifierParameterSource>> sqlParameterSources = createBatchParameterSourcesWithSequence(insertSubjects, domainType,
479+
context.getPersistentEntity(domainType).getIdTargetSequence()
480+
);
481+
482+
String insertSql = sql(domainType).getInsert(sqlParameterSources.get(0).getSecond().getIdentifiers());
483+
484+
insertStrategyFactory.batchInsertStrategy(idValueSource, getIdColumn(domainType))
485+
.execute(insertSql, sqlParameterSources.stream()
486+
.map(Pair::getSecond)
487+
.toArray(SqlIdentifierParameterSource[]::new));
488+
489+
return sqlParameterSources.stream().map(Pair::getFirst).toArray(Object[]::new);
490+
}
491+
492+
private <T> List<Pair<Long, SqlIdentifierParameterSource>> createBatchParameterSourcesWithSequence(List<InsertSubject<T>> insertSubjects, Class<T> domainType, Optional<String> idTargetSequence) {
493+
List<Pair<Long, SqlIdentifierParameterSource>> sqlParameterSources;
494+
int subjectsSize = insertSubjects.size();
495+
496+
List<Long> generatedIds = getMultipleIdsFromSequence(idTargetSequence.get(), subjectsSize);
497+
498+
sqlParameterSources = IntStream
499+
.range(0, subjectsSize)
500+
.mapToObj(index -> {
501+
InsertSubject<T> subject = insertSubjects.get(index);
502+
Long generatedId = generatedIds.get(index);
503+
return Pair.of(generatedId, sqlParametersFactory.forInsert(
504+
subject.getInstance(), domainType,
505+
subject.getIdentifier(), generatedId
506+
));
507+
})
508+
.collect(Collectors.toList());
509+
return sqlParameterSources;
510+
}
511+
512+
private Optional<Long> getIdFromSequenceIfAnyDefined(IdValueSource idValueSource, RelationalPersistentEntity<?> persistentEntity) {
513+
if (IdValueSource.SEQUENCE.equals(idValueSource) && persistentEntity.getIdTargetSequence().isPresent()) {
514+
String nextSequenceValueSelect = insertStrategyFactory.getDialect().nextValueFromSequenceSelect(persistentEntity.getIdTargetSequence().get());
515+
return Optional.of(operations.queryForObject(nextSequenceValueSelect, Map.of(), (rs, rowNum) -> rs.getLong(1)));
516+
}
517+
return Optional.empty();
518+
}
519+
520+
private List<Long> getMultipleIdsFromSequence(String sequenceName, Integer requiredIds) {
521+
String nextSequenceValueSelect = insertStrategyFactory.getDialect().nextValueFromSequenceSelect(sequenceName);
522+
523+
return IntStream.range(0, requiredIds)
524+
.mapToObj(operand -> operations.queryForObject(nextSequenceValueSelect, Map.of(), (rs, rowNum) -> rs.getLong(1)))
525+
.collect(Collectors.toList());
526+
}
527+
449528
}

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

+4
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
*
2929
* @author Chirag Tailor
3030
* @author Jens Schauder
31+
* @author Mikhail Polivakha
3132
* @since 2.4
3233
*/
3334
public class InsertStrategyFactory {
@@ -102,4 +103,7 @@ public Object[] execute(String sql, SqlParameterSource[] sqlParameterSources) {
102103
}
103104
}
104105

106+
public Dialect getDialect() {
107+
return this.dialect;
108+
}
105109
}

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

+14-7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.Set;
2323

2424
import org.springframework.data.relational.core.sql.SqlIdentifier;
25+
import org.springframework.data.util.Pair;
2526
import org.springframework.jdbc.core.namedparam.AbstractSqlParameterSource;
2627

2728
/**
@@ -35,9 +36,11 @@
3536
*/
3637
class SqlIdentifierParameterSource extends AbstractSqlParameterSource {
3738

38-
private final Set<SqlIdentifier> identifiers = new HashSet<>();
39+
private final Set<SqlIdentifier> sqlIdentifiers = new HashSet<>();
3940
private final Map<String, Object> namesToValues = new HashMap<>();
4041

42+
private Pair<SqlIdentifier, Object> idToValue;
43+
4144
@Override
4245
public boolean hasValue(String paramName) {
4346
return namesToValues.containsKey(paramName);
@@ -54,30 +57,34 @@ public String[] getParameterNames() {
5457
}
5558

5659
Set<SqlIdentifier> getIdentifiers() {
57-
return Collections.unmodifiableSet(identifiers);
60+
return Collections.unmodifiableSet(sqlIdentifiers);
5861
}
5962

6063
void addValue(SqlIdentifier name, Object value) {
6164
addValue(name, value, Integer.MIN_VALUE);
6265
}
6366

64-
void addValue(SqlIdentifier identifier, Object value, int sqlType) {
67+
void addValue(SqlIdentifier sqlIdentifier, Object value, int sqlType) {
6568

66-
identifiers.add(identifier);
67-
String name = BindParameterNameSanitizer.sanitize(identifier.getReference());
69+
sqlIdentifiers.add(sqlIdentifier);
70+
String name = prepareSqlIdentifierName(sqlIdentifier);
6871
namesToValues.put(name, value);
6972
registerSqlType(name, sqlType);
7073
}
7174

72-
void addAll(SqlIdentifierParameterSource others) {
75+
void addAll(SqlIdentifierParameterSource others) {
7376

7477
for (SqlIdentifier identifier : others.getIdentifiers()) {
7578

76-
String name = BindParameterNameSanitizer.sanitize( identifier.getReference());
79+
String name = prepareSqlIdentifierName(identifier);
7780
addValue(identifier, others.getValue(name), others.getSqlType(name));
7881
}
7982
}
8083

84+
private static String prepareSqlIdentifierName(SqlIdentifier sqlIdentifier) {
85+
return BindParameterNameSanitizer.sanitize(sqlIdentifier.getReference());
86+
}
87+
8188
int size() {
8289
return namesToValues.size();
8390
}

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

+46-10
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,28 @@
1515
*/
1616
package org.springframework.data.jdbc.core.convert;
1717

18+
import java.sql.ResultSet;
19+
import java.sql.SQLException;
1820
import java.sql.SQLType;
1921
import java.util.ArrayList;
2022
import java.util.List;
2123
import java.util.Map;
24+
import java.util.Optional;
2225
import java.util.function.Predicate;
2326

2427
import org.springframework.data.jdbc.core.mapping.JdbcValue;
2528
import org.springframework.data.jdbc.support.JdbcUtil;
2629
import org.springframework.data.mapping.PersistentProperty;
2730
import org.springframework.data.mapping.PersistentPropertyAccessor;
2831
import org.springframework.data.relational.core.conversion.IdValueSource;
32+
import org.springframework.data.relational.core.dialect.Dialect;
2933
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
3034
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
3135
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
3236
import org.springframework.data.relational.core.sql.SqlIdentifier;
37+
import org.springframework.jdbc.core.RowMapper;
38+
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
39+
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
3340
import org.springframework.jdbc.support.JdbcUtils;
3441
import org.springframework.lang.Nullable;
3542
import org.springframework.util.Assert;
@@ -46,13 +53,15 @@
4653
public class SqlParametersFactory {
4754
private final RelationalMappingContext context;
4855
private final JdbcConverter converter;
56+
private final Dialect dialect;
4957

50-
/**
51-
* @since 3.1
52-
*/
53-
public SqlParametersFactory(RelationalMappingContext context, JdbcConverter converter) {
58+
private final NamedParameterJdbcOperations operations;
59+
60+
public SqlParametersFactory(RelationalMappingContext context, JdbcConverter converter, Dialect dialect, NamedParameterJdbcOperations operations) {
5461
this.context = context;
5562
this.converter = converter;
63+
this.dialect = dialect;
64+
this.operations = operations;
5665
}
5766

5867
/**
@@ -70,18 +79,38 @@ public SqlParametersFactory(RelationalMappingContext context, JdbcConverter conv
7079
<T> SqlIdentifierParameterSource forInsert(T instance, Class<T> domainType, Identifier identifier,
7180
IdValueSource idValueSource) {
7281

82+
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
83+
84+
Object idValue = null;
85+
86+
if (IdValueSource.PROVIDED.equals(idValueSource)) {
87+
idValue = persistentEntity.getIdentifierAccessor(instance).getRequiredIdentifier();
88+
}
89+
return forInsert(instance, domainType, identifier, idValue);
90+
}
91+
92+
/**
93+
* Creates the parameters for a SQL insert operation. That method is different from its sibling
94+
* {@link #forInsert(Object, Class, Identifier, IdValueSource) forInsert method} in the sense, that
95+
* this method is invoked when we actually know the id to be added to the {@link SqlParameterSource paarameter source}.
96+
* It might be null, meaning, that we know for sure the id should be coming from the database, or
97+
* it could be not null, meaning, that we've got the id from some source (user provided by himself,
98+
* or we have queried the sequence for instance)
99+
*/
100+
<T> SqlIdentifierParameterSource forInsert(T instance, Class<T> domainType, Identifier identifier,
101+
@Nullable Object id) {
102+
73103
RelationalPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType);
74104
SqlIdentifierParameterSource parameterSource = getParameterSource(instance, persistentEntity, "",
75105
PersistentProperty::isIdProperty);
76106

77107
identifier.forEach((name, value, type) -> addConvertedPropertyValue(parameterSource, name, value, type));
78108

79-
if (IdValueSource.PROVIDED.equals(idValueSource)) {
80-
81-
RelationalPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
82-
Object idValue = persistentEntity.getIdentifierAccessor(instance).getRequiredIdentifier();
83-
addConvertedPropertyValue(parameterSource, idProperty, idValue, idProperty.getColumnName());
84-
}
109+
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
110+
Optional
111+
.ofNullable(id)
112+
.filter(it -> idProperty != null)
113+
.ifPresent(it -> addConvertedPropertyValue(parameterSource, idProperty, it, idProperty.getColumnName()));
85114
return parameterSource;
86115
}
87116

@@ -178,6 +207,13 @@ private void addConvertedPropertyValue(SqlIdentifierParameterSource parameterSou
178207
converter.getTargetSqlType(property));
179208
}
180209

210+
private void addConvertedIdPropertyValue(SqlIdentifierParameterSource parameterSource,
211+
RelationalPersistentProperty property, @Nullable Object value, SqlIdentifier name) {
212+
213+
addConvertedValue(parameterSource, value, name, converter.getColumnType(property),
214+
converter.getTargetSqlType(property));
215+
}
216+
181217
private void addConvertedPropertyValue(SqlIdentifierParameterSource parameterSource, SqlIdentifier name, Object value,
182218
Class<?> javaType) {
183219

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/mybatis/MyBatisDataAccessStrategy.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public static DataAccessStrategy createCombinedAccessStrategy(RelationalMappingC
8686
NamespaceStrategy namespaceStrategy, Dialect dialect) {
8787

8888
SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(context, converter, dialect);
89-
SqlParametersFactory sqlParametersFactory = new SqlParametersFactory(context, converter);
89+
SqlParametersFactory sqlParametersFactory = new SqlParametersFactory(context, converter, dialect, operations);
9090
InsertStrategyFactory insertStrategyFactory = new InsertStrategyFactory(operations, dialect);
9191

9292
DataAccessStrategy defaultDataAccessStrategy = new DataAccessStrategyFactory( //

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public DataAccessStrategy dataAccessStrategyBean(NamedParameterJdbcOperations op
207207

208208
SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(context, jdbcConverter, dialect);
209209
DataAccessStrategyFactory factory = new DataAccessStrategyFactory(sqlGeneratorSource, jdbcConverter, operations,
210-
new SqlParametersFactory(context, jdbcConverter),
210+
new SqlParametersFactory(context, jdbcConverter, dialect, operations),
211211
new InsertStrategyFactory(operations, dialect));
212212

213213
return factory.create();

spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactoryBean.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ public void afterPropertiesSet() {
177177

178178
SqlGeneratorSource sqlGeneratorSource = new SqlGeneratorSource(this.mappingContext, this.converter,
179179
this.dialect);
180-
SqlParametersFactory sqlParametersFactory = new SqlParametersFactory(this.mappingContext, this.converter);
180+
SqlParametersFactory sqlParametersFactory = new SqlParametersFactory(this.mappingContext, this.converter, this.dialect, this.operations);
181181
InsertStrategyFactory insertStrategyFactory = new InsertStrategyFactory(this.operations, this.dialect);
182182

183183
DataAccessStrategyFactory factory = new DataAccessStrategyFactory(sqlGeneratorSource, this.converter,

0 commit comments

Comments
 (0)