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 Original pull request: #216.
This commit is contained in:
committed by
Mark Paluch
parent
0448148bec
commit
3c9290483a
@@ -18,9 +18,7 @@ package org.springframework.data.jdbc.core.convert;
|
||||
import static org.springframework.data.jdbc.core.convert.SqlGenerator.*;
|
||||
|
||||
import java.sql.JDBCType;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
@@ -28,7 +26,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.DataRetrievalFailureException;
|
||||
import org.springframework.dao.EmptyResultDataAccessException;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
@@ -40,6 +37,7 @@ import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyHandler;
|
||||
import org.springframework.data.relational.core.dialect.LockClause;
|
||||
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
@@ -47,8 +45,6 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
|
||||
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
||||
import org.springframework.data.relational.core.sql.LockMode;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.jdbc.core.PreparedStatementCallback;
|
||||
import org.springframework.jdbc.core.ResultSetExtractor;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
|
||||
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
|
||||
@@ -254,7 +250,8 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
|
||||
String acquireLockByIdSql = sql(domainType).getAcquireLockById(lockMode);
|
||||
SqlIdentifierParameterSource parameter = createIdParameterSource(id, domainType);
|
||||
operations.execute(acquireLockByIdSql, parameter, ps -> {ps.execute(); return null;});
|
||||
|
||||
operations.query(acquireLockByIdSql, parameter, ResultSet::next);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -265,7 +262,7 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
|
||||
public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
|
||||
|
||||
String acquireLockAllSql = sql(domainType).getAcquireLockAll(lockMode);
|
||||
operations.getJdbcOperations().execute(acquireLockAllSql);
|
||||
operations.getJdbcOperations().query(acquireLockAllSql, ResultSet::next);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -25,14 +25,18 @@ import lombok.With;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.platform.commons.util.ExceptionUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -86,6 +90,35 @@ public class JdbcRepositoryConcurrencyIntegrationTests {
|
||||
TransactionTemplate transactionTemplate;
|
||||
List<Exception> exceptions;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() {
|
||||
|
||||
Assertions.registerFormatterForType(CopyOnWriteArrayList.class, l -> {
|
||||
|
||||
StringJoiner joiner = new StringJoiner(", ", "List(", ")");
|
||||
l.forEach(e -> {
|
||||
|
||||
if (e instanceof Throwable) {
|
||||
printThrowable(joiner,(Throwable) e);
|
||||
} else {
|
||||
joiner.add(e.toString());
|
||||
}
|
||||
});
|
||||
|
||||
return joiner.toString();
|
||||
});
|
||||
}
|
||||
|
||||
private static void printThrowable(StringJoiner joiner, Throwable t) {
|
||||
|
||||
joiner.add(t.toString() + ExceptionUtils.readStackTrace(t));
|
||||
if (t.getCause() != null) {
|
||||
|
||||
joiner.add("\ncaused by:\n");
|
||||
printThrowable(joiner, t.getCause());
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class Db2DataSourceConfiguration extends DataSourceConfiguration {
|
||||
if (DB_2_CONTAINER == null) {
|
||||
|
||||
LOG.info("DB2 starting...");
|
||||
Db2Container container = new Db2Container();
|
||||
Db2Container container = new Db2Container().withReuse(true);
|
||||
container.start();
|
||||
LOG.info("DB2 started");
|
||||
|
||||
|
||||
@@ -1,3 +1,41 @@
|
||||
DROP TABLE MANUAL;
|
||||
DROP TABLE LEGO_SET;
|
||||
|
||||
DROP TABLE Child_No_Id;
|
||||
DROP TABLE ONE_TO_ONE_PARENT;
|
||||
|
||||
DROP TABLE ELEMENT_NO_ID;
|
||||
DROP TABLE LIST_PARENT;
|
||||
|
||||
DROP TABLE BYTE_ARRAY_OWNER;
|
||||
|
||||
DROP TABLE CHAIN0;
|
||||
DROP TABLE CHAIN1;
|
||||
DROP TABLE CHAIN2;
|
||||
DROP TABLE CHAIN3;
|
||||
DROP TABLE CHAIN4;
|
||||
|
||||
DROP TABLE NO_ID_CHAIN0;
|
||||
DROP TABLE NO_ID_CHAIN1;
|
||||
DROP TABLE NO_ID_CHAIN2;
|
||||
DROP TABLE NO_ID_CHAIN3;
|
||||
DROP TABLE NO_ID_CHAIN4;
|
||||
|
||||
DROP TABLE NO_ID_MAP_CHAIN0;
|
||||
DROP TABLE NO_ID_MAP_CHAIN1;
|
||||
DROP TABLE NO_ID_MAP_CHAIN2;
|
||||
DROP TABLE NO_ID_MAP_CHAIN3;
|
||||
DROP TABLE NO_ID_MAP_CHAIN4;
|
||||
|
||||
DROP TABLE NO_ID_LIST_CHAIN0;
|
||||
DROP TABLE NO_ID_LIST_CHAIN1;
|
||||
DROP TABLE NO_ID_LIST_CHAIN2;
|
||||
DROP TABLE NO_ID_LIST_CHAIN3;
|
||||
DROP TABLE NO_ID_LIST_CHAIN4;
|
||||
|
||||
DROP TABLE WITH_READ_ONLY;
|
||||
DROP TABLE VERSIONED_AGGREGATE;
|
||||
|
||||
CREATE TABLE LEGO_SET
|
||||
(
|
||||
"id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
DROP TABLE element;
|
||||
DROP TABLE dummy_entity;
|
||||
|
||||
CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) NOT NULL PRIMARY KEY, NAME VARCHAR(100));
|
||||
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);
|
||||
@@ -41,7 +41,7 @@ public class Db2Dialect extends AbstractDialect {
|
||||
*/
|
||||
@Override
|
||||
public String getLimit(long limit) {
|
||||
return "FIRST " + limit + " ROWS ONLY";
|
||||
return "FETCH FIRST " + limit + " ROWS ONLY";
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -83,7 +83,18 @@ public class Db2Dialect extends AbstractDialect {
|
||||
|
||||
@Override
|
||||
public LockClause lock() {
|
||||
return AnsiDialect.LOCK_CLAUSE;
|
||||
|
||||
return new LockClause() {
|
||||
@Override
|
||||
public String getLock(LockOptions lockOptions) {
|
||||
return "FOR UPDATE WITH RS USE AND KEEP EXCLUSIVE LOCKS";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Position getClausePosition() {
|
||||
return Position.AFTER_ORDER_BY;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user