Skip to content

Commit 7636783

Browse files
committed
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 spring-projects/spring-data-relational#1041 Related tickets spring-projects#643
1 parent 4da260e commit 7636783

File tree

11 files changed

+286
-53
lines changed

11 files changed

+286
-53
lines changed

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2021 the original author or authors.
2+
* Copyright 2019-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -44,6 +44,7 @@
4444
* @author Mark Paluch
4545
* @author Roman Chigvintsev
4646
* @author Mingyuan Wu
47+
* @author Diego Krupitza
4748
*/
4849
class DefaultStatementMapper implements StatementMapper {
4950

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

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

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

+40-20
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2021 the original author or authors.
2+
* Copyright 2019-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -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;
@@ -34,6 +28,7 @@
3428
import org.springframework.data.relational.core.query.Criteria;
3529
import org.springframework.data.relational.core.query.CriteriaDefinition;
3630
import org.springframework.data.relational.core.sql.Expression;
31+
import org.springframework.data.relational.core.sql.LockMode;
3732
import org.springframework.data.relational.core.sql.SqlIdentifier;
3833
import org.springframework.data.relational.core.sql.Table;
3934
import org.springframework.data.relational.core.sql.render.RenderContext;
@@ -54,6 +49,7 @@
5449
* @author Mark Paluch
5550
* @author Roman Chigvintsev
5651
* @author Mingyuan Wu
52+
* @author Diego Krupitza
5753
*/
5854
public interface StatementMapper {
5955

@@ -228,9 +224,10 @@ class SelectSpec {
228224
private final long offset;
229225
private final int limit;
230226
private final boolean distinct;
227+
private final LockMode lockMode;
231228

232229
protected SelectSpec(Table table, List<String> projectedFields, List<Expression> selectList,
233-
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct) {
230+
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct, LockMode lockMode) {
234231
this.table = table;
235232
this.projectedFields = projectedFields;
236233
this.selectList = selectList;
@@ -239,6 +236,7 @@ protected SelectSpec(Table table, List<String> projectedFields, List<Expression>
239236
this.offset = offset;
240237
this.limit = limit;
241238
this.distinct = distinct;
239+
this.lockMode = lockMode;
242240
}
243241

244242
/**
@@ -263,7 +261,7 @@ public static SelectSpec create(SqlIdentifier table) {
263261
List<String> projectedFields = Collections.emptyList();
264262
List<Expression> selectList = Collections.emptyList();
265263
return new SelectSpec(Table.create(table), projectedFields, selectList, Criteria.empty(), Sort.unsorted(), -1, -1,
266-
false);
264+
false, null);
267265
}
268266

269267
public SelectSpec doWithTable(BiFunction<Table, SelectSpec, SelectSpec> function) {
@@ -305,7 +303,7 @@ public SelectSpec withProjection(Expression... expressions) {
305303
selectList.addAll(Arrays.asList(expressions));
306304

307305
return new SelectSpec(this.table, projectedFields, selectList, this.criteria, this.sort, this.limit, this.offset,
308-
this.distinct);
306+
this.distinct, this.lockMode);
309307
}
310308

311309
/**
@@ -321,7 +319,7 @@ public SelectSpec withProjection(Collection<Expression> projectedFields) {
321319
selectList.addAll(projectedFields);
322320

323321
return new SelectSpec(this.table, this.projectedFields, selectList, this.criteria, this.sort, this.limit,
324-
this.offset, this.distinct);
322+
this.offset, this.distinct, this.lockMode);
325323
}
326324

327325
/**
@@ -332,7 +330,7 @@ public SelectSpec withProjection(Collection<Expression> projectedFields) {
332330
*/
333331
public SelectSpec withCriteria(CriteriaDefinition criteria) {
334332
return new SelectSpec(this.table, this.projectedFields, this.selectList, criteria, this.sort, this.limit,
335-
this.offset, this.distinct);
333+
this.offset, this.distinct, this.lockMode);
336334
}
337335

338336
/**
@@ -345,11 +343,11 @@ public SelectSpec withSort(Sort sort) {
345343

346344
if (sort.isSorted()) {
347345
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, sort, this.limit,
348-
this.offset, this.distinct);
346+
this.offset, this.distinct, this.lockMode);
349347
}
350348

351349
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
352-
this.offset, this.distinct);
350+
this.offset, this.distinct, this.lockMode);
353351
}
354352

355353
/**
@@ -365,11 +363,11 @@ public SelectSpec withPage(Pageable page) {
365363
Sort sort = page.getSort();
366364

367365
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria,
368-
sort.isSorted() ? sort : this.sort, page.getPageSize(), page.getOffset(), this.distinct);
366+
sort.isSorted() ? sort : this.sort, page.getPageSize(), page.getOffset(), this.distinct, this.lockMode);
369367
}
370368

371369
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
372-
this.offset, this.distinct);
370+
this.offset, this.distinct, this.lockMode);
373371
}
374372

375373
/**
@@ -380,7 +378,7 @@ public SelectSpec withPage(Pageable page) {
380378
*/
381379
public SelectSpec offset(long offset) {
382380
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
383-
offset, this.distinct);
381+
offset, this.distinct, this.lockMode);
384382
}
385383

386384
/**
@@ -391,7 +389,7 @@ public SelectSpec offset(long offset) {
391389
*/
392390
public SelectSpec limit(int limit) {
393391
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
394-
this.offset, this.distinct);
392+
this.offset, this.distinct, this.lockMode);
395393
}
396394

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

407426
public Table getTable() {
@@ -441,6 +460,7 @@ public int getLimit() {
441460
public boolean isDistinct() {
442461
return this.distinct;
443462
}
463+
444464
}
445465

446466
/**

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
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.r2dbc.repository;
17+
18+
import org.springframework.data.annotation.QueryAnnotation;
19+
import org.springframework.data.relational.core.sql.LockMode;
20+
21+
import java.lang.annotation.*;
22+
23+
/**
24+
* Annotation to provide a lock mode for a given query.
25+
*
26+
* @author Diego Krupitza
27+
*/
28+
@Retention(RetentionPolicy.RUNTIME)
29+
@Target(ElementType.METHOD)
30+
@QueryAnnotation
31+
@Documented
32+
public @interface Lock {
33+
34+
/**
35+
* Defines which type of {@link LockMode} we want to use.
36+
*/
37+
LockMode value();
38+
39+
}

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2020-2021 the original author or authors.
2+
* Copyright 2020-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -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
}

0 commit comments

Comments
 (0)