Skip to content

Commit 2348056

Browse files
committed
DATAJDBC-538 - Fixes lock acquisition for DB2.
The DB2 driver requires one to access the `ResultSet` resulting from a SELECT FOR UPDATE. See also: * https://github.com/schauder/db2-locks * https://stackoverflow.com/questions/61681095/how-to-obtain-a-lock-in-db2-with-select-for-update-without-transferring-data
1 parent d216cf8 commit 2348056

File tree

6 files changed

+94
-10
lines changed

6 files changed

+94
-10
lines changed

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

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,14 @@
1818
import static org.springframework.data.jdbc.core.convert.SqlGenerator.*;
1919

2020
import java.sql.JDBCType;
21-
import java.sql.PreparedStatement;
2221
import java.sql.ResultSet;
23-
import java.sql.SQLException;
2422
import java.util.ArrayList;
2523
import java.util.Collections;
2624
import java.util.HashSet;
2725
import java.util.List;
2826
import java.util.Map;
2927
import java.util.function.Predicate;
3028

31-
import org.springframework.dao.DataAccessException;
3229
import org.springframework.dao.DataRetrievalFailureException;
3330
import org.springframework.dao.EmptyResultDataAccessException;
3431
import org.springframework.dao.InvalidDataAccessApiUsageException;
@@ -40,15 +37,14 @@
4037
import org.springframework.data.mapping.PersistentPropertyAccessor;
4138
import org.springframework.data.mapping.PersistentPropertyPath;
4239
import org.springframework.data.mapping.PropertyHandler;
40+
import org.springframework.data.relational.core.dialect.LockClause;
4341
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
4442
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
4543
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
4644
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
4745
import org.springframework.data.relational.core.sql.IdentifierProcessing;
4846
import org.springframework.data.relational.core.sql.LockMode;
4947
import org.springframework.data.relational.core.sql.SqlIdentifier;
50-
import org.springframework.jdbc.core.PreparedStatementCallback;
51-
import org.springframework.jdbc.core.ResultSetExtractor;
5248
import org.springframework.jdbc.core.RowMapper;
5349
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
5450
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
@@ -254,7 +250,8 @@ public <T> void acquireLockById(Object id, LockMode lockMode, Class<T> domainTyp
254250

255251
String acquireLockByIdSql = sql(domainType).getAcquireLockById(lockMode);
256252
SqlIdentifierParameterSource parameter = createIdParameterSource(id, domainType);
257-
operations.execute(acquireLockByIdSql, parameter, ps -> {ps.execute(); return null;});
253+
254+
operations.query(acquireLockByIdSql, parameter, ResultSet::next);
258255
}
259256

260257
/*
@@ -265,7 +262,7 @@ public <T> void acquireLockById(Object id, LockMode lockMode, Class<T> domainTyp
265262
public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
266263

267264
String acquireLockAllSql = sql(domainType).getAcquireLockAll(lockMode);
268-
operations.getJdbcOperations().execute(acquireLockAllSql);
265+
operations.getJdbcOperations().query(acquireLockAllSql, ResultSet::next);
269266
}
270267

271268
/*

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,18 @@
2525
import java.util.ArrayList;
2626
import java.util.Arrays;
2727
import java.util.List;
28+
import java.util.StringJoiner;
2829
import java.util.concurrent.CopyOnWriteArrayList;
2930
import java.util.concurrent.CountDownLatch;
3031
import java.util.function.UnaryOperator;
3132

33+
import org.assertj.core.api.Assertions;
3234
import org.junit.Before;
35+
import org.junit.BeforeClass;
3336
import org.junit.ClassRule;
3437
import org.junit.Rule;
3538
import org.junit.Test;
39+
import org.junit.platform.commons.util.ExceptionUtils;
3640
import org.springframework.beans.factory.annotation.Autowired;
3741
import org.springframework.context.annotation.Bean;
3842
import org.springframework.context.annotation.Configuration;
@@ -86,6 +90,35 @@ DummyEntityRepository dummyEntityRepository() {
8690
TransactionTemplate transactionTemplate;
8791
List<Exception> exceptions;
8892

93+
@BeforeClass
94+
public static void beforeClass() {
95+
96+
Assertions.registerFormatterForType(CopyOnWriteArrayList.class, l -> {
97+
98+
StringJoiner joiner = new StringJoiner(", ", "List(", ")");
99+
l.forEach(e -> {
100+
101+
if (e instanceof Throwable) {
102+
printThrowable(joiner,(Throwable) e);
103+
} else {
104+
joiner.add(e.toString());
105+
}
106+
});
107+
108+
return joiner.toString();
109+
});
110+
}
111+
112+
private static void printThrowable(StringJoiner joiner, Throwable t) {
113+
114+
joiner.add(t.toString() + ExceptionUtils.readStackTrace(t));
115+
if (t.getCause() != null) {
116+
117+
joiner.add("\ncaused by:\n");
118+
printThrowable(joiner, t.getCause());
119+
}
120+
}
121+
89122
@Before
90123
public void before() {
91124

spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/Db2DataSourceConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ protected DataSource createDataSource() {
5555
if (DB_2_CONTAINER == null) {
5656

5757
LOG.info("DB2 starting...");
58-
Db2Container container = new Db2Container();
58+
Db2Container container = new Db2Container().withReuse(true);
5959
container.start();
6060
LOG.info("DB2 started");
6161

spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,41 @@
1+
DROP TABLE MANUAL;
2+
DROP TABLE LEGO_SET;
3+
4+
DROP TABLE Child_No_Id;
5+
DROP TABLE ONE_TO_ONE_PARENT;
6+
7+
DROP TABLE ELEMENT_NO_ID;
8+
DROP TABLE LIST_PARENT;
9+
10+
DROP TABLE BYTE_ARRAY_OWNER;
11+
12+
DROP TABLE CHAIN0;
13+
DROP TABLE CHAIN1;
14+
DROP TABLE CHAIN2;
15+
DROP TABLE CHAIN3;
16+
DROP TABLE CHAIN4;
17+
18+
DROP TABLE NO_ID_CHAIN0;
19+
DROP TABLE NO_ID_CHAIN1;
20+
DROP TABLE NO_ID_CHAIN2;
21+
DROP TABLE NO_ID_CHAIN3;
22+
DROP TABLE NO_ID_CHAIN4;
23+
24+
DROP TABLE NO_ID_MAP_CHAIN0;
25+
DROP TABLE NO_ID_MAP_CHAIN1;
26+
DROP TABLE NO_ID_MAP_CHAIN2;
27+
DROP TABLE NO_ID_MAP_CHAIN3;
28+
DROP TABLE NO_ID_MAP_CHAIN4;
29+
30+
DROP TABLE NO_ID_LIST_CHAIN0;
31+
DROP TABLE NO_ID_LIST_CHAIN1;
32+
DROP TABLE NO_ID_LIST_CHAIN2;
33+
DROP TABLE NO_ID_LIST_CHAIN3;
34+
DROP TABLE NO_ID_LIST_CHAIN4;
35+
36+
DROP TABLE WITH_READ_ONLY;
37+
DROP TABLE VERSIONED_AGGREGATE;
38+
139
CREATE TABLE LEGO_SET
240
(
341
"id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
DROP TABLE element;
2+
DROP TABLE dummy_entity;
3+
4+
CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) NOT NULL PRIMARY KEY, NAME VARCHAR(100));
5+
CREATE TABLE element (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) NOT NULL PRIMARY KEY, content BIGINT, Dummy_Entity_key BIGINT,dummy_entity BIGINT);

spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ protected Db2Dialect() {}
4141
*/
4242
@Override
4343
public String getLimit(long limit) {
44-
return "FIRST " + limit + " ROWS ONLY";
44+
return "FETCH FIRST " + limit + " ROWS ONLY";
4545
}
4646

4747
/*
@@ -83,7 +83,18 @@ public LimitClause limit() {
8383

8484
@Override
8585
public LockClause lock() {
86-
return AnsiDialect.LOCK_CLAUSE;
86+
87+
return new LockClause() {
88+
@Override
89+
public String getLock(LockOptions lockOptions) {
90+
return "FOR UPDATE WITH RS USE AND KEEP EXCLUSIVE LOCKS";
91+
}
92+
93+
@Override
94+
public Position getClausePosition() {
95+
return Position.AFTER_ORDER_BY;
96+
}
97+
};
8798
}
8899

89100
/*

0 commit comments

Comments
 (0)