DATAJDBC-493 - Polishing.

Using `execute` instead of `query` since we are not interested in the results.
Refactoring of the concurrency tests.
Make the concurrency tests run with all databases.
Added support for DB2.
Moved AnsiDialect to the main sources so it can be referenced in other Dialects.

Original pull request: #196.
This commit is contained in:
Jens Schauder
2020-05-06 13:54:55 +02:00
parent 04c29f4004
commit aa05b792c3
24 changed files with 177 additions and 264 deletions

View File

@@ -50,7 +50,6 @@ import org.springframework.util.Assert;
* @author Thomas Lang
* @author Christoph Strobl
* @author Milan Milanov
* @author Myeonghyeon Lee
*/
public class JdbcAggregateTemplate implements JdbcAggregateOperations {

View File

@@ -132,7 +132,7 @@ public interface DataAccessStrategy extends RelationResolver {
void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath);
/**
* Acquire Lock
* Acquire a lock on the aggregate specified by id.
*
* @param id the id of the entity to load. Must not be {@code null}.
* @param lockMode the lock mode for select. Must not be {@code null}.
@@ -141,7 +141,7 @@ public interface DataAccessStrategy extends RelationResolver {
<T> void acquireLockById(Object id, LockMode lockMode, Class<T> domainType);
/**
* Acquire Lock entities of the given domain type.
* Acquire a lock on all aggregates of the given domain type.
*
* @param lockMode the lock mode for select. Must not be {@code null}.
* @param domainType the domain type of the entity. Must not be {@code null}.

View File

@@ -18,6 +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;
@@ -27,7 +28,11 @@ import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.springframework.dao.*;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.support.JdbcUtil;
@@ -42,6 +47,7 @@ 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;
@@ -245,9 +251,10 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
*/
@Override
public <T> void acquireLockById(Object id, LockMode lockMode, Class<T> domainType) {
String acquireLockByIdSql = sql(domainType).getAcquireLockById(lockMode);
SqlIdentifierParameterSource parameter = createIdParameterSource(id, domainType);
operations.queryForObject(acquireLockByIdSql, parameter, Object.class);
operations.execute(acquireLockByIdSql, parameter, ps -> {ps.execute(); return null;});
}
/*
@@ -256,8 +263,9 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
*/
@Override
public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
String acquireLockAllSql = sql(domainType).getAcquireLockAll(lockMode);
operations.query(acquireLockAllSql, Collections.emptyMap(), new NoMappingResultSetExtractor());
operations.getJdbcOperations().execute(acquireLockAllSql);
}
/*
@@ -605,14 +613,4 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
return null;
}
}
/**
* The type No mapping result set extractor.
*/
static class NoMappingResultSetExtractor implements ResultSetExtractor<Object> {
@Override
public Object extractData(ResultSet resultSet) throws SQLException, DataAccessException {
return null;
}
}
}

View File

@@ -257,13 +257,15 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
*/
@Override
public <T> void acquireLockById(Object id, LockMode lockMode, Class<T> domainType) {
String statement = namespace(domainType) + ".acquireLockById";
MyBatisContext parameter = new MyBatisContext(id, null, domainType, Collections.emptyMap());
long result = sqlSession().selectOne(statement, parameter);
if (result < 1) {
throw new EmptyResultDataAccessException(
String.format("The lock target does not exist. id: %s, statement: %s", id, statement), 1);
String message = String.format("The lock target does not exist. id: %s, statement: %s", id, statement);
throw new EmptyResultDataAccessException(message, 1);
}
}
@@ -273,6 +275,7 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
*/
@Override
public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
String statement = namespace(domainType) + ".acquireLockAll";
MyBatisContext parameter = new MyBatisContext(null, null, domainType, Collections.emptyMap());

View File

@@ -23,9 +23,8 @@ import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils;
import org.springframework.data.jdbc.testing.AnsiDialect;
import org.springframework.data.relational.core.dialect.AnsiDialect;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.dialect.HsqlDbDialect;
import org.springframework.data.relational.core.mapping.NamingStrategy;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;

View File

@@ -25,7 +25,6 @@ import java.util.Set;
import org.assertj.core.api.SoftAssertions;
import org.junit.Before;
import org.junit.Test;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.ReadOnlyProperty;
import org.springframework.data.annotation.Version;
@@ -36,7 +35,7 @@ import org.springframework.data.jdbc.core.PropertyPathTestingUtils;
import org.springframework.data.jdbc.core.mapping.AggregateReference;
import org.springframework.data.jdbc.core.mapping.JdbcMappingContext;
import org.springframework.data.jdbc.core.mapping.PersistentPropertyPathTestUtils;
import org.springframework.data.jdbc.testing.AnsiDialect;
import org.springframework.data.relational.core.dialect.AnsiDialect;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.Column;
@@ -95,19 +94,18 @@ public class SqlGeneratorUnitTests {
String sql = sqlGenerator.getFindOne();
SoftAssertions softAssertions = new SoftAssertions();
softAssertions.assertThat(sql) //
.startsWith("SELECT") //
.contains("dummy_entity.id1 AS id1,") //
.contains("dummy_entity.x_name AS x_name,") //
.contains("dummy_entity.x_other AS x_other,") //
.contains("ref.x_l1id AS ref_x_l1id") //
.contains("ref.x_content AS ref_x_content").contains(" FROM dummy_entity") //
.contains("ON ref.dummy_entity = dummy_entity.id1") //
.contains("WHERE dummy_entity.id1 = :id") //
// 1-N relationships do not get loaded via join
.doesNotContain("Element AS elements");
softAssertions.assertAll();
SoftAssertions.assertSoftly(softly -> softly //
.assertThat(sql) //
.startsWith("SELECT") //
.contains("dummy_entity.id1 AS id1,") //
.contains("dummy_entity.x_name AS x_name,") //
.contains("dummy_entity.x_other AS x_other,") //
.contains("ref.x_l1id AS ref_x_l1id") //
.contains("ref.x_content AS ref_x_content").contains(" FROM dummy_entity") //
.contains("ON ref.dummy_entity = dummy_entity.id1") //
.contains("WHERE dummy_entity.id1 = :id") //
// 1-N relationships do not get loaded via join
.doesNotContain("Element AS elements"));
}
@Test // DATAJDBC-493
@@ -115,14 +113,13 @@ public class SqlGeneratorUnitTests {
String sql = sqlGenerator.getAcquireLockById(LockMode.PESSIMISTIC_WRITE);
SoftAssertions softAssertions = new SoftAssertions();
softAssertions.assertThat(sql) //
.startsWith("SELECT") //
.contains("dummy_entity.id1") //
.contains("WHERE dummy_entity.id1 = :id") //
.contains("FOR UPDATE") //
.doesNotContain("Element AS elements");
softAssertions.assertAll();
SoftAssertions.assertSoftly(softly -> softly //
.assertThat(sql) //
.startsWith("SELECT") //
.contains("dummy_entity.id1") //
.contains("WHERE dummy_entity.id1 = :id") //
.contains("FOR UPDATE") //
.doesNotContain("Element AS elements"));
}
@Test // DATAJDBC-493
@@ -130,13 +127,12 @@ public class SqlGeneratorUnitTests {
String sql = sqlGenerator.getAcquireLockAll(LockMode.PESSIMISTIC_WRITE);
SoftAssertions softAssertions = new SoftAssertions();
softAssertions.assertThat(sql) //
.startsWith("SELECT") //
.contains("dummy_entity.id1") //
.contains("FOR UPDATE") //
.doesNotContain("Element AS elements");
softAssertions.assertAll();
SoftAssertions.assertSoftly(softly -> softly //
.assertThat(sql) //
.startsWith("SELECT") //
.contains("dummy_entity.id1") //
.contains("FOR UPDATE") //
.doesNotContain("Element AS elements"));
}
@Test // DATAJDBC-112

View File

@@ -15,10 +15,21 @@
*/
package org.springframework.data.jdbc.repository;
import static org.assertj.core.api.Assertions.*;
import junit.framework.AssertionFailedError;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.With;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.function.UnaryOperator;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
@@ -29,34 +40,20 @@ import org.springframework.context.annotation.Import;
import org.springframework.dao.IncorrectUpdateSemanticsDataAccessException;
import org.springframework.data.annotation.Id;
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
import org.springframework.data.jdbc.testing.DatabaseProfileValueSource;
import org.springframework.data.jdbc.testing.TestConfiguration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.test.annotation.IfProfileValue;
import org.springframework.test.annotation.ProfileValueSourceConfiguration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import static org.assertj.core.api.Assertions.assertThat;
/** Tests that highly concurrent update operations of an entity don't cause deadlocks.
/**
* Tests that highly concurrent update operations of an entity don't cause deadlocks.
*
* @author Myeonghyeon Lee
* @author Jens Schauder
*/
@ContextConfiguration
@ProfileValueSourceConfiguration(DatabaseProfileValueSource.class)
@IfProfileValue(name = "current.database.is.not.mysql", value = "false")
public class JdbcRepositoryConcurrencyIntegrationTests {
@Configuration
@@ -83,38 +80,37 @@ public class JdbcRepositoryConcurrencyIntegrationTests {
@Autowired DummyEntityRepository repository;
@Autowired PlatformTransactionManager transactionManager;
@Test // DATAJDBC-488
public void updateConcurrencyWithEmptyReferences() throws Exception {
List<DummyEntity> concurrencyEntities;
DummyEntity entity;
DummyEntity entity = createDummyEntity();
entity = repository.save(entity);
TransactionTemplate transactionTemplate;
List<Exception> exceptions;
@Before
public void before() {
entity = repository.save(createDummyEntity());
assertThat(entity.getId()).isNotNull();
List<DummyEntity> concurrencyEntities = createEntityStates(entity);
concurrencyEntities = createEntityStates(entity);
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
transactionTemplate = new TransactionTemplate(this.transactionManager);
List<Exception> exceptions = new CopyOnWriteArrayList<>();
CountDownLatch startLatch = new CountDownLatch(concurrencyEntities.size()); // latch for all threads to wait on.
CountDownLatch doneLatch = new CountDownLatch(concurrencyEntities.size()); // latch for main thread to wait on until all threads are done.
exceptions = new CopyOnWriteArrayList<>();
}
concurrencyEntities.stream() //
.map(e -> new Thread(() -> {
@Test // DATAJDBC-488
public void updateConcurrencyWithEmptyReferences() throws Exception {
try {
// latch for all threads to wait on.
CountDownLatch startLatch = new CountDownLatch(concurrencyEntities.size());
// latch for main thread to wait on until all threads are done.
CountDownLatch doneLatch = new CountDownLatch(concurrencyEntities.size());
startLatch.countDown();
startLatch.await();
UnaryOperator<DummyEntity> action = e -> repository.save(e);
transactionTemplate.execute(status -> repository.save(e));
} catch (Exception ex) {
exceptions.add(ex);
} finally {
doneLatch.countDown();
}
})) //
.forEach(Thread::start);
concurrencyEntities.forEach(e -> executeInParallel(startLatch, doneLatch, action, e));
doneLatch.await();
@@ -124,62 +120,31 @@ public class JdbcRepositoryConcurrencyIntegrationTests {
}
@Test // DATAJDBC-493
public void updateConcurrencyWithDelete() throws Exception {
public void concurrentUpdateAndDelete() throws Exception {
DummyEntity entity = createDummyEntity();
entity = repository.save(entity);
Long targetId = entity.getId();
assertThat(targetId).isNotNull();
List<DummyEntity> concurrencyEntities = createEntityStates(entity);
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
List<Exception> exceptions = new CopyOnWriteArrayList<>();
CountDownLatch startLatch = new CountDownLatch(concurrencyEntities.size() + 1); // latch for all threads to wait on.
CountDownLatch doneLatch = new CountDownLatch(concurrencyEntities.size() + 1); // latch for main thread to wait on until all threads are done.
// update
concurrencyEntities.stream() //
.map(e -> new Thread(() -> {
try {
startLatch.countDown();
startLatch.await();
transactionTemplate.execute(status -> repository.save(e));
} catch (Exception ex) {
// When the delete execution is complete, the Update execution throws an IncorrectUpdateSemanticsDataAccessException.
if (ex.getCause() instanceof IncorrectUpdateSemanticsDataAccessException) {
return;
}
exceptions.add(ex);
} finally {
doneLatch.countDown();
}
})) //
.forEach(Thread::start);
// delete
new Thread(() -> {
CountDownLatch doneLatch = new CountDownLatch(concurrencyEntities.size() + 1); // latch for main thread to wait on
// until all threads are done.
UnaryOperator<DummyEntity> updateAction = e -> {
try {
startLatch.countDown();
startLatch.await();
transactionTemplate.execute(status -> {
repository.deleteById(targetId);
return null;
});
return repository.save(e);
} catch (Exception ex) {
exceptions.add(ex);
} finally {
doneLatch.countDown();
// When the delete execution is complete, the Update execution throws an
// IncorrectUpdateSemanticsDataAccessException.
if (ex.getCause() instanceof IncorrectUpdateSemanticsDataAccessException) {
return null;
}
throw ex;
}
}).start();
};
UnaryOperator<DummyEntity> deleteAction = e -> {
repository.deleteById(entity.id);
return null;
};
concurrencyEntities.forEach(e -> executeInParallel(startLatch, doneLatch, updateAction, e));
executeInParallel(startLatch, doneLatch, deleteAction, entity);
doneLatch.await();
@@ -188,42 +153,41 @@ public class JdbcRepositoryConcurrencyIntegrationTests {
}
@Test // DATAJDBC-493
public void updateConcurrencyWithDeleteAll() throws Exception {
public void concurrentUpdateAndDeleteAll() throws Exception {
DummyEntity entity = createDummyEntity();
entity = repository.save(entity);
List<DummyEntity> concurrencyEntities = createEntityStates(entity);
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
List<Exception> exceptions = new CopyOnWriteArrayList<>();
CountDownLatch startLatch = new CountDownLatch(concurrencyEntities.size() + 1); // latch for all threads to wait on.
CountDownLatch doneLatch = new CountDownLatch(concurrencyEntities.size() + 1); // latch for main thread to wait on until all threads are done.
CountDownLatch doneLatch = new CountDownLatch(concurrencyEntities.size() + 1); // latch for main thread to wait on
// until all threads are done.
// update
concurrencyEntities.stream() //
.map(e -> new Thread(() -> {
try {
startLatch.countDown();
startLatch.await();
transactionTemplate.execute(status -> repository.save(e));
} catch (Exception ex) {
// When the delete execution is complete, the Update execution throws an IncorrectUpdateSemanticsDataAccessException.
if (ex.getCause() instanceof IncorrectUpdateSemanticsDataAccessException) {
return;
}
exceptions.add(ex);
} finally {
doneLatch.countDown();
UnaryOperator<DummyEntity> updateAction = e -> {
try {
return repository.save(e);
} catch (Exception ex) {
// When the delete execution is complete, the Update execution throws an
// IncorrectUpdateSemanticsDataAccessException.
if (ex.getCause() instanceof IncorrectUpdateSemanticsDataAccessException) {
return null;
}
})) //
.forEach(Thread::start);
throw ex;
}
};
UnaryOperator<DummyEntity> deleteAction = e -> {
repository.deleteAll();
return null;
};
concurrencyEntities.forEach(e -> executeInParallel(startLatch, doneLatch, updateAction, e));
executeInParallel(startLatch, doneLatch, deleteAction, entity);
doneLatch.await();
assertThat(exceptions).isEmpty();
assertThat(repository.count()).isEqualTo(0);
}
private void executeInParallel(CountDownLatch startLatch, CountDownLatch doneLatch,
UnaryOperator<DummyEntity> deleteAction, DummyEntity entity) {
// delete
new Thread(() -> {
try {
@@ -231,21 +195,13 @@ public class JdbcRepositoryConcurrencyIntegrationTests {
startLatch.countDown();
startLatch.await();
transactionTemplate.execute(status -> {
repository.deleteAll();
return null;
});
transactionTemplate.execute(status -> deleteAction.apply(entity));
} catch (Exception ex) {
exceptions.add(ex);
} finally {
doneLatch.countDown();
}
}).start();
doneLatch.await();
assertThat(exceptions).isEmpty();
assertThat(repository.count()).isEqualTo(0);
}
private List<DummyEntity> createEntityStates(DummyEntity entity) {
@@ -254,7 +210,7 @@ public class JdbcRepositoryConcurrencyIntegrationTests {
Element element1 = new Element(null, 1L);
Element element2 = new Element(null, 2L);
for (int i = 0; i < 100; i++) {
for (int i = 0; i < 50; i++) {
List<Element> newContent = Arrays.asList(element1.withContent(element1.content + i + 2),
element2.withContent(element2.content + i + 2));

View File

@@ -64,7 +64,6 @@ import org.springframework.transaction.annotation.Transactional;
* @author Jens Schauder
* @author Mark Paluch
*/
@ContextConfiguration
@Transactional
public class JdbcRepositoryIntegrationTests {

View File

@@ -0,0 +1,2 @@
CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, content BIGINT, Dummy_Entity_key BIGINT,dummy_entity BIGINT);

View File

@@ -0,0 +1,2 @@
CREATE TABLE dummy_entity ( id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element (id BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, content BIGINT, Dummy_Entity_key BIGINT,dummy_entity BIGINT);

View File

@@ -0,0 +1,2 @@
CREATE TABLE dummy_entity ( id BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element (id BIGINT AUTO_INCREMENT PRIMARY KEY, content BIGINT, Dummy_Entity_key BIGINT,dummy_entity BIGINT);

View File

@@ -0,0 +1,2 @@
CREATE TABLE dummy_entity ( id BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element (id BIGINT AUTO_INCREMENT PRIMARY KEY, content BIGINT, Dummy_Entity_key BIGINT,dummy_entity BIGINT);

View File

@@ -0,0 +1,4 @@
DROP TABLE dummy_entity;
DROP TABLE element;
CREATE TABLE dummy_entity ( id SERIAL PRIMARY KEY, NAME VARCHAR(100));
CREATE TABLE element (id SERIAL PRIMARY KEY, content BIGINT, Dummy_Entity_key BIGINT,dummy_entity BIGINT);

View File

@@ -327,10 +327,11 @@ public interface DbAction<T> {
* @param <T> type of the entity for which this represents a database interaction.
*/
final class AcquireLockRoot<T> implements DbAction<T> {
private final Object id;
private final Class<T> entityType;
public AcquireLockRoot(Object id, Class<T> entityType) {
AcquireLockRoot(Object id, Class<T> entityType) {
this.id = id;
this.entityType = entityType;
}
@@ -349,15 +350,15 @@ public interface DbAction<T> {
}
/**
* Represents an acquire lock statement for all entities that that a reachable via a give path from any aggregate root of a
* given type.
* Represents an acquire lock statement for all aggregate roots of a given type.
*
* @param <T> type of the entity for which this represents a database interaction.
*/
final class AcquireLockAllRoot<T> implements DbAction<T> {
private final Class<T> entityType;
public AcquireLockAllRoot(Class<T> entityType) {
AcquireLockAllRoot(Class<T> entityType) {
this.entityType = entityType;
}

View File

@@ -57,12 +57,8 @@ public abstract class AbstractDialect implements Dialect {
Function<Select, ? extends CharSequence> afterFromTable = select -> "";
LockClause lockClause = lock();
switch (lockClause.getClausePosition()) {
case AFTER_FROM_TABLE:
afterFromTable = new LockRenderFunction(lockClause);
default:
if (lockClause.getClausePosition() == LockClause.Position.AFTER_FROM_TABLE) {
afterFromTable = new LockRenderFunction(lockClause);
}
return afterFromTable.andThen(PrependWithLeadingWhitespace.INSTANCE);
@@ -78,31 +74,19 @@ public abstract class AbstractDialect implements Dialect {
Function<Select, ? extends CharSequence> afterOrderByLimit = getAfterOrderByLimit();
Function<Select, ? extends CharSequence> afterOrderByLock = getAfterOrderByLock();
return select -> {
StringBuilder afterOrderByBuilder = new StringBuilder();
afterOrderByBuilder.append(afterOrderByLimit.apply(select));
afterOrderByBuilder.append(afterOrderByLock.apply(select));
return afterOrderByBuilder.toString();
};
return select -> String.valueOf(afterOrderByLimit.apply(select)) +
afterOrderByLock.apply(select);
}
private Function<Select, ? extends CharSequence> getAfterOrderByLimit() {
LimitClause limit = limit();
Function<Select, ? extends CharSequence> afterOrderByLimit = select -> "";
switch (limit.getClausePosition()) {
case AFTER_ORDER_BY:
afterOrderByLimit = new AfterOrderByLimitRenderFunction(limit);
break;
default:
throw new UnsupportedOperationException(String.format("Clause position %s not supported!", limit));
if (limit.getClausePosition() == LimitClause.Position.AFTER_ORDER_BY) {
return new AfterOrderByLimitRenderFunction(limit) //
.andThen(PrependWithLeadingWhitespace.INSTANCE);
} else {
throw new UnsupportedOperationException(String.format("Clause position %s not supported!", limit));
}
return afterOrderByLimit.andThen(PrependWithLeadingWhitespace.INSTANCE);
}
private Function<Select, ? extends CharSequence> getAfterOrderByLock() {
@@ -110,12 +94,8 @@ public abstract class AbstractDialect implements Dialect {
Function<Select, ? extends CharSequence> afterOrderByLock = select -> "";
switch (lock.getClausePosition()) {
case AFTER_ORDER_BY:
afterOrderByLock = new LockRenderFunction(lock);
default:
if (lock.getClausePosition() == LockClause.Position.AFTER_ORDER_BY) {
afterOrderByLock = new LockRenderFunction(lock);
}
return afterOrderByLock.andThen(PrependWithLeadingWhitespace.INSTANCE);
@@ -124,7 +104,7 @@ public abstract class AbstractDialect implements Dialect {
/**
* {@link SelectRenderContext} derived from {@link Dialect} specifics.
*/
class DialectSelectRenderContext implements SelectRenderContext {
static class DialectSelectRenderContext implements SelectRenderContext {
private final Function<Select, ? extends CharSequence> afterFromTable;
private final Function<Select, ? extends CharSequence> afterOrderBy;

View File

@@ -13,14 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.jdbc.testing;
package org.springframework.data.relational.core.dialect;
import lombok.RequiredArgsConstructor;
import org.springframework.data.relational.core.dialect.AbstractDialect;
import org.springframework.data.relational.core.dialect.ArrayColumns;
import org.springframework.data.relational.core.dialect.LimitClause;
import org.springframework.data.relational.core.dialect.LockClause;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.relational.core.sql.LockOptions;
import org.springframework.util.Assert;
@@ -81,7 +77,7 @@ public class AnsiDialect extends AbstractDialect {
}
};
private static final LockClause LOCK_CLAUSE = new LockClause() {
static final LockClause LOCK_CLAUSE = new LockClause() {
/*
* (non-Javadoc)

View File

@@ -16,6 +16,7 @@
package org.springframework.data.relational.core.dialect;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.relational.core.sql.LockOptions;
/**
* An SQL dialect for DB2.
@@ -80,6 +81,11 @@ public class Db2Dialect extends AbstractDialect {
return LIMIT_CLAUSE;
}
@Override
public LockClause lock() {
return AnsiDialect.LOCK_CLAUSE;
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.dialect.Dialect#getIdentifierProcessing()

View File

@@ -20,7 +20,6 @@ import lombok.RequiredArgsConstructor;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
import org.springframework.data.relational.core.sql.IdentifierProcessing.LetterCasing;
import org.springframework.data.relational.core.sql.IdentifierProcessing.Quoting;
import org.springframework.data.relational.core.sql.LockOptions;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
@@ -79,27 +78,6 @@ public class H2Dialect extends AbstractDialect {
}
};
private static final LockClause LOCK_CLAUSE = new LockClause() {
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.dialect.LockClause#getLock(LockOptions)
*/
@Override
public String getLock(LockOptions lockOptions) {
return "FOR UPDATE";
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.dialect.LockClause#getClausePosition()
*/
@Override
public Position getClausePosition() {
return Position.AFTER_ORDER_BY;
}
};
private final H2ArrayColumns ARRAY_COLUMNS = new H2ArrayColumns();
/*
@@ -117,7 +95,7 @@ public class H2Dialect extends AbstractDialect {
*/
@Override
public LockClause lock() {
return LOCK_CLAUSE;
return AnsiDialect.LOCK_CLAUSE;
}
/*

View File

@@ -15,8 +15,6 @@
*/
package org.springframework.data.relational.core.dialect;
import org.springframework.data.relational.core.sql.LockOptions;
/**
* A {@link Dialect} for HsqlDb.
*
@@ -36,7 +34,7 @@ public class HsqlDbDialect extends AbstractDialect {
@Override
public LockClause lock() {
return LOCK_CLAUSE;
return AnsiDialect.LOCK_CLAUSE;
}
private static final LimitClause LIMIT_CLAUSE = new LimitClause() {
@@ -61,17 +59,4 @@ public class HsqlDbDialect extends AbstractDialect {
return Position.AFTER_ORDER_BY;
}
};
private static final LockClause LOCK_CLAUSE = new LockClause() {
@Override
public String getLock(LockOptions lockOptions) {
return "FOR UPDATE";
}
@Override
public Position getClausePosition() {
return Position.AFTER_ORDER_BY;
}
};
}

View File

@@ -115,6 +115,7 @@ public class PostgresDialect extends AbstractDialect {
}
static class PostgresLockClause implements LockClause {
private final IdentifierProcessing identifierProcessing;
PostgresLockClause(IdentifierProcessing identifierProcessing) {

View File

@@ -46,8 +46,7 @@ public class SqlServerSelectRenderContext implements SelectRenderContext {
* @param afterFromTable the delegate {@code afterFromTable} function.
* @param afterOrderBy the delegate {@code afterOrderBy} function.
*/
protected SqlServerSelectRenderContext(
Function<Select, CharSequence> afterFromTable,
protected SqlServerSelectRenderContext(Function<Select, CharSequence> afterFromTable,
Function<Select, CharSequence> afterOrderBy) {
this.afterFromTable = afterFromTable;

View File

@@ -273,6 +273,7 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
*/
@Override
public SelectLock lock(LockMode lockMode) {
this.lockMode = lockMode;
return this;
}
@@ -283,6 +284,7 @@ class DefaultSelectBuilder implements SelectBuilder, SelectAndFrom, SelectFromAn
*/
@Override
public Select build() {
DefaultSelect select = new DefaultSelect(distinct, selectList, from, limit, offset, joins, where, orderBy, lockMode);
SelectValidator.validate(select);
return select;

View File

@@ -22,6 +22,7 @@ package org.springframework.data.relational.core.sql;
* @since 2.0
*/
public enum LockMode {
PESSIMISTIC_READ,
PESSIMISTIC_WRITE
}

View File

@@ -22,10 +22,12 @@ package org.springframework.data.relational.core.sql;
* @since 2.0
*/
public class LockOptions {
private final LockMode lockMode;
private final From from;
public LockOptions(LockMode lockMode, From from) {
this.lockMode = lockMode;
this.from = from;
}