Browse Source

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.
pull/217/head
Jens Schauder 6 years ago committed by Mark Paluch
parent
commit
3c9290483a
No known key found for this signature in database
GPG Key ID: 51A00FA751B91849
  1. 11
      spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/DefaultDataAccessStrategy.java
  2. 33
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryConcurrencyIntegrationTests.java
  3. 2
      spring-data-jdbc/src/test/java/org/springframework/data/jdbc/testing/Db2DataSourceConfiguration.java
  4. 38
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql
  5. 5
      spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryConcurrencyIntegrationTests-db2.sql
  6. 15
      spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Db2Dialect.java

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

@ -18,9 +18,7 @@ package org.springframework.data.jdbc.core.convert; @@ -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; @@ -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; @@ -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 @@ -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 { @@ -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 { @@ -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);
}
/*

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

@ -25,14 +25,18 @@ import lombok.With; @@ -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 { @@ -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() {

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

@ -55,7 +55,7 @@ class Db2DataSourceConfiguration extends DataSourceConfiguration { @@ -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");

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

@ -1,3 +1,41 @@ @@ -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,

5
spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryConcurrencyIntegrationTests-db2.sql

@ -0,0 +1,5 @@ @@ -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);

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

@ -41,7 +41,7 @@ public class Db2Dialect extends AbstractDialect { @@ -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 { @@ -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;
}
};
}
/*

Loading…
Cancel
Save