Skip to content

Commit 910c340

Browse files
DiegoKrupitzaschauder
authored andcommitted
Introduced pessimistic locks for derived queries.
Methods which use the derive query functionality now can be annotated with `@Lock` to used a given `LockMode`. Right now there are two different modes `PESSIMISTIC_READ` and `PESSIMISTIC_WRITE`. Based on the dialect the right select is generated. For example for H2 `Select ... FOR UPDATE`. Closes #1041 See #643, Original pull request /pull/1158
1 parent 4edb398 commit 910c340

File tree

10 files changed

+240
-45
lines changed

10 files changed

+240
-45
lines changed

spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/DefaultStatementMapper.java

+5
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
* @author Mark Paluch
4646
* @author Roman Chigvintsev
4747
* @author Mingyuan Wu
48+
* @author Diego Krupitza
4849
*/
4950
class DefaultStatementMapper implements StatementMapper {
5051

@@ -132,6 +133,10 @@ private PreparedOperation<Select> getMappedObject(SelectSpec selectSpec,
132133
selectBuilder.offset(selectSpec.getOffset());
133134
}
134135

136+
if (selectSpec.getLock() != null) {
137+
selectBuilder.lock(selectSpec.getLock());
138+
}
139+
135140
Select select = selectBuilder.build();
136141
return new DefaultPreparedOperation<>(select, this.renderContext, bindings);
137142
}

spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/core/StatementMapper.java

+39-19
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,7 @@
1515
*/
1616
package org.springframework.data.r2dbc.core;
1717

18-
import java.util.ArrayList;
19-
import java.util.Arrays;
20-
import java.util.Collection;
21-
import java.util.Collections;
22-
import java.util.LinkedHashMap;
23-
import java.util.List;
24-
import java.util.Map;
18+
import java.util.*;
2519
import java.util.function.BiFunction;
2620
import java.util.function.Supplier;
2721
import java.util.stream.Collectors;
@@ -33,6 +27,7 @@
3327
import org.springframework.data.relational.core.query.Criteria;
3428
import org.springframework.data.relational.core.query.CriteriaDefinition;
3529
import org.springframework.data.relational.core.sql.Expression;
30+
import org.springframework.data.relational.core.sql.LockMode;
3631
import org.springframework.data.relational.core.sql.SqlIdentifier;
3732
import org.springframework.data.relational.core.sql.Table;
3833
import org.springframework.data.relational.core.sql.render.RenderContext;
@@ -53,6 +48,7 @@
5348
* @author Mark Paluch
5449
* @author Roman Chigvintsev
5550
* @author Mingyuan Wu
51+
* @author Diego Krupitza
5652
*/
5753
public interface StatementMapper {
5854

@@ -227,9 +223,10 @@ class SelectSpec {
227223
private final long offset;
228224
private final int limit;
229225
private final boolean distinct;
226+
private final LockMode lockMode;
230227

231228
protected SelectSpec(Table table, List<String> projectedFields, List<Expression> selectList,
232-
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct) {
229+
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct, LockMode lockMode) {
233230
this.table = table;
234231
this.projectedFields = projectedFields;
235232
this.selectList = selectList;
@@ -238,6 +235,7 @@ protected SelectSpec(Table table, List<String> projectedFields, List<Expression>
238235
this.offset = offset;
239236
this.limit = limit;
240237
this.distinct = distinct;
238+
this.lockMode = lockMode;
241239
}
242240

243241
/**
@@ -262,7 +260,7 @@ public static SelectSpec create(SqlIdentifier table) {
262260
List<String> projectedFields = Collections.emptyList();
263261
List<Expression> selectList = Collections.emptyList();
264262
return new SelectSpec(Table.create(table), projectedFields, selectList, Criteria.empty(), Sort.unsorted(), -1, -1,
265-
false);
263+
false, null);
266264
}
267265

268266
public SelectSpec doWithTable(BiFunction<Table, SelectSpec, SelectSpec> function) {
@@ -304,7 +302,7 @@ public SelectSpec withProjection(Expression... expressions) {
304302
selectList.addAll(Arrays.asList(expressions));
305303

306304
return new SelectSpec(this.table, projectedFields, selectList, this.criteria, this.sort, this.limit, this.offset,
307-
this.distinct);
305+
this.distinct, this.lockMode);
308306
}
309307

310308
/**
@@ -320,7 +318,7 @@ public SelectSpec withProjection(Collection<Expression> projectedFields) {
320318
selectList.addAll(projectedFields);
321319

322320
return new SelectSpec(this.table, this.projectedFields, selectList, this.criteria, this.sort, this.limit,
323-
this.offset, this.distinct);
321+
this.offset, this.distinct, this.lockMode);
324322
}
325323

326324
/**
@@ -331,7 +329,7 @@ public SelectSpec withProjection(Collection<Expression> projectedFields) {
331329
*/
332330
public SelectSpec withCriteria(CriteriaDefinition criteria) {
333331
return new SelectSpec(this.table, this.projectedFields, this.selectList, criteria, this.sort, this.limit,
334-
this.offset, this.distinct);
332+
this.offset, this.distinct, this.lockMode);
335333
}
336334

337335
/**
@@ -344,11 +342,11 @@ public SelectSpec withSort(Sort sort) {
344342

345343
if (sort.isSorted()) {
346344
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, sort, this.limit,
347-
this.offset, this.distinct);
345+
this.offset, this.distinct, this.lockMode);
348346
}
349347

350348
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
351-
this.offset, this.distinct);
349+
this.offset, this.distinct, this.lockMode);
352350
}
353351

354352
/**
@@ -364,11 +362,11 @@ public SelectSpec withPage(Pageable page) {
364362
Sort sort = page.getSort();
365363

366364
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria,
367-
sort.isSorted() ? sort : this.sort, page.getPageSize(), page.getOffset(), this.distinct);
365+
sort.isSorted() ? sort : this.sort, page.getPageSize(), page.getOffset(), this.distinct, this.lockMode);
368366
}
369367

370368
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
371-
this.offset, this.distinct);
369+
this.offset, this.distinct, this.lockMode);
372370
}
373371

374372
/**
@@ -379,7 +377,7 @@ public SelectSpec withPage(Pageable page) {
379377
*/
380378
public SelectSpec offset(long offset) {
381379
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
382-
offset, this.distinct);
380+
offset, this.distinct, this.lockMode);
383381
}
384382

385383
/**
@@ -390,7 +388,7 @@ public SelectSpec offset(long offset) {
390388
*/
391389
public SelectSpec limit(int limit) {
392390
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
393-
this.offset, this.distinct);
391+
this.offset, this.distinct, this.lockMode);
394392
}
395393

396394
/**
@@ -400,7 +398,28 @@ public SelectSpec limit(int limit) {
400398
*/
401399
public SelectSpec distinct() {
402400
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
403-
this.offset, true);
401+
this.offset, true, this.lockMode);
402+
}
403+
404+
/**
405+
* Associate a lock mode with the select and create a new {@link SelectSpec}.
406+
*
407+
* @param lockMode the {@link LockMode} we want to use. This might be null
408+
* @return the {@link SelectSpec}.
409+
*/
410+
public SelectSpec lock(LockMode lockMode) {
411+
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
412+
this.offset, this.distinct, lockMode);
413+
}
414+
415+
/**
416+
* The used lockmode
417+
*
418+
* @return might be null if no lockmode defined.
419+
*/
420+
@Nullable
421+
public LockMode getLock() {
422+
return this.lockMode;
404423
}
405424

406425
public Table getTable() {
@@ -440,6 +459,7 @@ public int getLimit() {
440459
public boolean isDistinct() {
441460
return this.distinct;
442461
}
462+
443463
}
444464

445465
/**

spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/dialect/H2Dialect.java

+16
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package org.springframework.data.r2dbc.dialect;
22

3+
import org.springframework.data.relational.core.dialect.AnsiDialect;
4+
import org.springframework.data.relational.core.dialect.LockClause;
35
import org.springframework.data.relational.core.sql.SqlIdentifier;
46

57
/**
68
* An SQL dialect for H2 in Postgres Compatibility mode.
79
*
810
* @author Mark Paluch
911
* @author Jens Schauder
12+
* @author Diego Krupitza
1013
*/
1114
public class H2Dialect extends PostgresDialect {
1215

@@ -19,4 +22,17 @@ public class H2Dialect extends PostgresDialect {
1922
public String renderForGeneratedValues(SqlIdentifier identifier) {
2023
return identifier.getReference(getIdentifierProcessing());
2124
}
25+
26+
/*
27+
* (non-Javadoc)
28+
* @see org.springframework.data.relational.core.dialect.Dialect#lock()
29+
*/
30+
@Override
31+
public LockClause lock() {
32+
// H2 Dialect does not support the same lock keywords as PostgreSQL, but it supports the ANSI SQL standard.
33+
// see https://www.h2database.com/html/commands.html
34+
// and https://www.h2database.com/html/features.html#compatibility
35+
return AnsiDialect.INSTANCE.lock();
36+
}
37+
2238
}

spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/PartTreeR2dbcQuery.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
*
4040
* @author Roman Chigvintsev
4141
* @author Mark Paluch
42+
* @author Diego Krupitza
4243
* @since 1.1
4344
*/
4445
public class PartTreeR2dbcQuery extends AbstractR2dbcQuery {
@@ -119,7 +120,7 @@ protected Mono<PreparedOperation<?>> createQuery(RelationalParameterAccessor acc
119120

120121
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
121122
R2dbcQueryCreator queryCreator = new R2dbcQueryCreator(tree, dataAccessStrategy, entityMetadata, accessor,
122-
projectedProperties);
123+
projectedProperties, this.getQueryMethod().getLock());
123124
return queryCreator.createQuery(getDynamicSort(accessor));
124125
});
125126
}

spring-data-r2dbc/src/main/java/org/springframework/data/r2dbc/repository/query/R2dbcQueryCreator.java

+17-14
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,18 @@
1818
import java.util.ArrayList;
1919
import java.util.Collections;
2020
import java.util.List;
21+
import java.util.Optional;
2122
import java.util.stream.Collectors;
2223

2324
import org.springframework.data.domain.Pageable;
2425
import org.springframework.data.domain.Sort;
2526
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
2627
import org.springframework.data.r2dbc.core.StatementMapper;
28+
import org.springframework.data.relational.repository.Lock;
2729
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
2830
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
2931
import org.springframework.data.relational.core.query.Criteria;
30-
import org.springframework.data.relational.core.sql.Column;
31-
import org.springframework.data.relational.core.sql.Expression;
32-
import org.springframework.data.relational.core.sql.Expressions;
33-
import org.springframework.data.relational.core.sql.Functions;
34-
import org.springframework.data.relational.core.sql.SqlIdentifier;
35-
import org.springframework.data.relational.core.sql.Table;
32+
import org.springframework.data.relational.core.sql.*;
3633
import org.springframework.data.relational.repository.query.RelationalEntityMetadata;
3734
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
3835
import org.springframework.data.relational.repository.query.RelationalQueryCreator;
@@ -48,6 +45,7 @@
4845
* @author Mark Paluch
4946
* @author Mingyuan Wu
5047
* @author Myeonghyeon Lee
48+
* @author Diego Krupitza
5149
* @since 1.1
5250
*/
5351
class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<?>> {
@@ -58,6 +56,7 @@ class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<?>> {
5856
private final RelationalEntityMetadata<?> entityMetadata;
5957
private final List<String> projectedProperties;
6058
private final Class<?> entityToRead;
59+
private final Optional<Lock> lock;
6160

6261
/**
6362
* Creates new instance of this class with the given {@link PartTree}, {@link ReactiveDataAccessStrategy},
@@ -71,7 +70,7 @@ class R2dbcQueryCreator extends RelationalQueryCreator<PreparedOperation<?>> {
7170
*/
7271
public R2dbcQueryCreator(PartTree tree, ReactiveDataAccessStrategy dataAccessStrategy,
7372
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor,
74-
List<String> projectedProperties) {
73+
List<String> projectedProperties, Optional<Lock> lock) {
7574
super(tree, accessor);
7675

7776
this.tree = tree;
@@ -81,6 +80,7 @@ public R2dbcQueryCreator(PartTree tree, ReactiveDataAccessStrategy dataAccessStr
8180
this.entityMetadata = entityMetadata;
8281
this.projectedProperties = projectedProperties;
8382
this.entityToRead = entityMetadata.getTableEntity().getType();
83+
this.lock = lock;
8484
}
8585

8686
/**
@@ -138,6 +138,10 @@ private PreparedOperation<?> select(@Nullable Criteria criteria, Sort sort, Stat
138138
selectSpec = selectSpec.distinct();
139139
}
140140

141+
if (this.lock.isPresent()) {
142+
selectSpec = selectSpec.lock(this.lock.get().value());
143+
}
144+
141145
return statementMapper.getMappedObject(selectSpec);
142146
}
143147

@@ -154,15 +158,15 @@ private Expression[] getSelectProjection() {
154158
for (String projectedProperty : projectedProperties) {
155159

156160
RelationalPersistentProperty property = entity.getPersistentProperty(projectedProperty);
157-
Column column = table.column(property != null ? property.getColumnName() : SqlIdentifier.unquoted(projectedProperty));
161+
Column column = table
162+
.column(property != null ? property.getColumnName() : SqlIdentifier.unquoted(projectedProperty));
158163
expressions.add(column);
159164
}
160165

161166
} else if (tree.isExistsProjection()) {
162167

163-
expressions = dataAccessStrategy.getIdentifierColumns(entityToRead).stream()
164-
.map(table::column)
165-
.collect(Collectors.toList());
168+
expressions = dataAccessStrategy.getIdentifierColumns(entityToRead).stream().map(table::column)
169+
.collect(Collectors.toList());
166170
} else if (tree.isCountProjection()) {
167171

168172
Expression countExpression = entityMetadata.getTableEntity().hasIdProperty()
@@ -171,9 +175,8 @@ private Expression[] getSelectProjection() {
171175

172176
expressions = Collections.singletonList(Functions.count(countExpression));
173177
} else {
174-
expressions = dataAccessStrategy.getAllColumns(entityToRead).stream()
175-
.map(table::column)
176-
.collect(Collectors.toList());
178+
expressions = dataAccessStrategy.getAllColumns(entityToRead).stream().map(table::column)
179+
.collect(Collectors.toList());
177180
}
178181

179182
return expressions.toArray(new Expression[0]);

0 commit comments

Comments
 (0)