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:
@@ -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 {
|
||||
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -64,7 +64,6 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
* @author Jens Schauder
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@ContextConfiguration
|
||||
@Transactional
|
||||
public class JdbcRepositoryIntegrationTests {
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -115,6 +115,7 @@ public class PostgresDialect extends AbstractDialect {
|
||||
}
|
||||
|
||||
static class PostgresLockClause implements LockClause {
|
||||
|
||||
private final IdentifierProcessing identifierProcessing;
|
||||
|
||||
PostgresLockClause(IdentifierProcessing identifierProcessing) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -22,6 +22,7 @@ package org.springframework.data.relational.core.sql;
|
||||
* @since 2.0
|
||||
*/
|
||||
public enum LockMode {
|
||||
|
||||
PESSIMISTIC_READ,
|
||||
PESSIMISTIC_WRITE
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user