Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 11a2364

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 HSQLDB `Select ... FOR UPDATE`. See #1041 Original pull request #1158
1 parent c0cd813 commit 11a2364

File tree

8 files changed

+184
-21
lines changed

8 files changed

+184
-21
lines changed

‎spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcCountQueryCreator.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 the original author or authors.
2+
* Copyright 2021-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.
@@ -30,18 +30,21 @@
3030
import org.springframework.data.repository.query.ReturnedType;
3131
import org.springframework.data.repository.query.parser.PartTree;
3232

33+
import java.util.Optional;
34+
3335
/**
3436
* {@link JdbcQueryCreator} that creates {@code COUNT(*)} queries without applying limit/offset and {@link Sort}.
3537
*
3638
* @author Mark Paluch
39+
* @author Diego Krupitza
3740
* @since 2.2
3841
*/
3942
class JdbcCountQueryCreator extends JdbcQueryCreator {
4043

4144
JdbcCountQueryCreator(RelationalMappingContext context, PartTree tree, JdbcConverter converter, Dialect dialect,
4245
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor, boolean isSliceQuery,
43-
ReturnedType returnedType) {
44-
super(context, tree, converter, dialect, entityMetadata, accessor, isSliceQuery, returnedType);
46+
ReturnedType returnedType, Optional<Lock> lockMode) {
47+
super(context, tree, converter, dialect, entityMetadata, accessor, isSliceQuery, returnedType, lockMode);
4548
}
4649

4750
@Override

‎spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryCreator.java

Lines changed: 12 additions & 3 deletions
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.
@@ -18,6 +18,7 @@
1818
import java.util.ArrayList;
1919
import java.util.List;
2020
import java.util.Objects;
21+
import java.util.Optional;
2122

2223
import org.springframework.data.domain.Pageable;
2324
import org.springframework.data.domain.Sort;
@@ -57,6 +58,7 @@
5758
* @author Mark Paluch
5859
* @author Jens Schauder
5960
* @author Myeonghyeon Lee
61+
* @author Diego Krupitza
6062
* @since 2.0
6163
*/
6264
class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
@@ -69,6 +71,7 @@ class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
6971
private final RenderContextFactory renderContextFactory;
7072
private final boolean isSliceQuery;
7173
private final ReturnedType returnedType;
74+
private final Optional<Lock> lockMode;
7275

7376
/**
7477
* Creates new instance of this class with the given {@link PartTree}, {@link JdbcConverter}, {@link Dialect},
@@ -85,7 +88,7 @@ class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
8588
*/
8689
JdbcQueryCreator(RelationalMappingContext context, PartTree tree, JdbcConverter converter, Dialect dialect,
8790
RelationalEntityMetadata<?> entityMetadata, RelationalParameterAccessor accessor, boolean isSliceQuery,
88-
ReturnedType returnedType) {
91+
ReturnedType returnedType, Optional<Lock> lockMode) {
8992
super(tree, accessor);
9093

9194
Assert.notNull(converter, "JdbcConverter must not be null");
@@ -102,6 +105,7 @@ class JdbcQueryCreator extends RelationalQueryCreator<ParametrizedQuery> {
102105
this.renderContextFactory = new RenderContextFactory(dialect);
103106
this.isSliceQuery = isSliceQuery;
104107
this.returnedType = returnedType;
108+
this.lockMode = lockMode;
105109
}
106110

107111
/**
@@ -168,7 +172,12 @@ protected ParametrizedQuery complete(@Nullable Criteria criteria, Sort sort) {
168172
whereBuilder);
169173
selectOrderBuilder = applyOrderBy(sort, entity, table, selectOrderBuilder);
170174

171-
Select select = selectOrderBuilder.build();
175+
SelectBuilder.BuildSelect completedBuildSelect = selectOrderBuilder;
176+
if (this.lockMode.isPresent()) {
177+
completedBuildSelect = selectOrderBuilder.lock(this.lockMode.get().value());
178+
}
179+
180+
Select select = completedBuildSelect.build();
172181

173182
String sql = SqlRenderer.create(renderContextFactory.createRenderContext()).render(select);
174183

‎spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethod.java

Lines changed: 18 additions & 2 deletions
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.
@@ -47,6 +47,7 @@
4747
* @author Kazuki Shimizu
4848
* @author Moises Cisneros
4949
* @author Hebert Coelho
50+
* @author Diego Krupitza
5051
*/
5152
public class JdbcQueryMethod extends QueryMethod {
5253

@@ -159,7 +160,6 @@ public String getNamedQueryName() {
159160
return StringUtils.hasText(annotatedName) ? annotatedName : super.getNamedQueryName();
160161
}
161162

162-
163163
/**
164164
* Returns the class to be used as {@link org.springframework.jdbc.core.RowMapper}
165165
*
@@ -236,6 +236,22 @@ Optional<Query> lookupQueryAnnotation() {
236236
return doFindAnnotation(Query.class);
237237
}
238238

239+
/**
240+
* @return is a {@link Lock} annotation present or not.
241+
*/
242+
public boolean hasLockMode() {
243+
return lookupLockAnnotation().isPresent();
244+
}
245+
246+
/**
247+
* Looks up the {@link Lock} annotation from the query method.
248+
*
249+
* @return the {@link Optional} wrapped {@link Lock} annotation.
250+
*/
251+
Optional<Lock> lookupLockAnnotation() {
252+
return doFindAnnotation(Lock.class);
253+
}
254+
239255
@SuppressWarnings("unchecked")
240256
private <A extends Annotation> Optional<A> doFindAnnotation(Class<A> annotationType) {
241257

Lines changed: 39 additions & 0 deletions
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.jdbc.repository.query;
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() default LockMode.PESSIMISTIC_READ;
38+
39+
}

‎spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/query/PartTreeJdbcQuery.java

Lines changed: 5 additions & 4 deletions
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.
@@ -50,6 +50,7 @@
5050
*
5151
* @author Mark Paluch
5252
* @author Jens Schauder
53+
* @author Diego Krupitza
5354
* @since 2.0
5455
*/
5556
public class PartTreeJdbcQuery extends AbstractJdbcQuery {
@@ -159,7 +160,7 @@ private JdbcQueryExecution<?> getQueryExecution(ResultProcessor processor,
159160
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
160161

161162
JdbcCountQueryCreator queryCreator = new JdbcCountQueryCreator(context, tree, converter, dialect,
162-
entityMetadata, accessor, false, processor.getReturnedType());
163+
entityMetadata, accessor, false, processor.getReturnedType(), getQueryMethod().lookupLockAnnotation());
163164

164165
ParametrizedQuery countQuery = queryCreator.createQuery(Sort.unsorted());
165166
Object count = singleObjectQuery((rs, i) -> rs.getLong(1)).execute(countQuery.getQuery(),
@@ -177,7 +178,7 @@ protected ParametrizedQuery createQuery(RelationalParametersParameterAccessor ac
177178
RelationalEntityMetadata<?> entityMetadata = getQueryMethod().getEntityInformation();
178179

179180
JdbcQueryCreator queryCreator = new JdbcQueryCreator(context, tree, converter, dialect, entityMetadata, accessor,
180-
getQueryMethod().isSliceQuery(), returnedType);
181+
getQueryMethod().isSliceQuery(), returnedType, this.getQueryMethod().lookupLockAnnotation());
181182
return queryCreator.createQuery(getDynamicSort(accessor));
182183
}
183184

@@ -227,7 +228,7 @@ static class PageQueryExecution<T> implements JdbcQueryExecution<Slice<T>> {
227228
private final LongSupplier countSupplier;
228229

229230
PageQueryExecution(JdbcQueryExecution<? extends Collection<T>> delegate, Pageable pageable,
230-
LongSupplier countSupplier) {
231+
LongSupplier countSupplier) {
231232
this.delegate = delegate;
232233
this.pageable = pageable;
233234
this.countSupplier = countSupplier;

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2017-2021 the original author or authors.
2+
* Copyright 2017-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.
@@ -51,6 +51,7 @@
5151
import org.springframework.data.domain.Pageable;
5252
import org.springframework.data.domain.Slice;
5353
import org.springframework.data.jdbc.core.mapping.AggregateReference;
54+
import org.springframework.data.jdbc.repository.query.Lock;
5455
import org.springframework.data.jdbc.repository.query.Modifying;
5556
import org.springframework.data.jdbc.repository.query.Query;
5657
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
@@ -61,6 +62,7 @@
6162
import org.springframework.data.relational.core.mapping.event.AbstractRelationalEvent;
6263
import org.springframework.data.relational.core.mapping.event.AfterConvertEvent;
6364
import org.springframework.data.relational.core.mapping.event.AfterLoadEvent;
65+
import org.springframework.data.relational.core.sql.LockMode;
6466
import org.springframework.data.repository.CrudRepository;
6567
import org.springframework.data.repository.core.NamedQueries;
6668
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
@@ -331,6 +333,13 @@ public void findAllByQueryName() {
331333
assertThat(repository.findAllByNamedQuery()).hasSize(1);
332334
}
333335

336+
@Test
337+
void findAllByFirstnameWithLock() {
338+
DummyEntity dummyEntity = createDummyEntity();
339+
repository.save(dummyEntity);
340+
assertThat(repository.findAllByName(dummyEntity.getName())).hasSize(1);
341+
}
342+
334343
@Test // GH-1022
335344
public void findAllByCustomQueryName() {
336345

@@ -574,7 +583,11 @@ private Instant createDummyBeforeAndAfterNow() {
574583

575584
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
576585

586+
@Lock(LockMode.PESSIMISTIC_WRITE)
587+
List<DummyEntity> findAllByName(String name);
588+
577589
List<DummyEntity> findAllByNamedQuery();
590+
578591
@Query(name = "DummyEntity.customQuery")
579592
List<DummyEntity> findAllByCustomNamedQuery();
580593

@@ -624,6 +637,7 @@ interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> {
624637
List<DummyEntity> findByFlagTrue();
625638

626639
List<DummyEntity> findByRef(int ref);
640+
627641
List<DummyEntity> findByRef(AggregateReference<DummyEntity, Long> ref);
628642
}
629643

‎spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/query/JdbcQueryMethodUnitTests.java

Lines changed: 34 additions & 1 deletion
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.
@@ -28,6 +28,7 @@
2828

2929
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
3030
import org.springframework.data.projection.ProjectionFactory;
31+
import org.springframework.data.relational.core.sql.LockMode;
3132
import org.springframework.data.repository.core.NamedQueries;
3233
import org.springframework.data.repository.core.RepositoryMetadata;
3334
import org.springframework.data.repository.core.support.PropertiesBasedNamedQueries;
@@ -41,6 +42,7 @@
4142
* @author Oliver Gierke
4243
* @author Moises Cisneros
4344
* @author Mark Paluch
45+
* @author Diego Krupitza
4446
*/
4547
public class JdbcQueryMethodUnitTests {
4648

@@ -120,6 +122,37 @@ public void returnsNullIfNoQueryIsFound() throws NoSuchMethodException {
120122
assertThat(queryMethod.getDeclaredQuery()).isEqualTo(null);
121123
}
122124

125+
@Test // GH-1041
126+
void returnsQueryMethodWithLock() throws NoSuchMethodException {
127+
128+
JdbcQueryMethod queryMethodWithWriteLock = createJdbcQueryMethod("queryMethodWithWriteLock");
129+
JdbcQueryMethod queryMethodWithReadLock = createJdbcQueryMethod("queryMethodWithReadLock");
130+
131+
assertThat(queryMethodWithWriteLock.hasLockMode()).isTrue();
132+
assertThat(queryMethodWithReadLock.hasLockMode()).isTrue();
133+
}
134+
135+
@Test // GH-1041
136+
void returnsQueryMethodWithCorrectLockType() throws NoSuchMethodException {
137+
138+
JdbcQueryMethod queryMethodWithWriteLock = createJdbcQueryMethod("queryMethodWithWriteLock");
139+
JdbcQueryMethod queryMethodWithReadLock = createJdbcQueryMethod("queryMethodWithReadLock");
140+
141+
assertThat(queryMethodWithWriteLock.lookupLockAnnotation()).isPresent();
142+
assertThat(queryMethodWithReadLock.lookupLockAnnotation()).isPresent();
143+
144+
assertThat(queryMethodWithWriteLock.lookupLockAnnotation().get().value()).isEqualTo(LockMode.PESSIMISTIC_WRITE);
145+
assertThat(queryMethodWithReadLock.lookupLockAnnotation().get().value()).isEqualTo(LockMode.PESSIMISTIC_READ);
146+
}
147+
148+
@Lock(LockMode.PESSIMISTIC_WRITE)
149+
@Query
150+
private void queryMethodWithWriteLock() {}
151+
152+
@Lock(LockMode.PESSIMISTIC_READ)
153+
@Query
154+
private void queryMethodWithReadLock() {}
155+
123156
@Query(value = QUERY, rowMapperClass = CustomRowMapper.class)
124157
private void queryMethod() {}
125158

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /