DATAJDBC-493 - Avoids deadlocks by acquiring lock on aggregate root table.

Introduces infrastructure to obtain locks and uses them to acquire locks on the table of the aggregate root before deleting references.
Without this lock deletes access non root entities before the aggregate root, which is the opposite order of updates and thus may cause deadlocks.

Original pull request: #196.
This commit is contained in:
mhyeon-lee
2020-02-22 14:54:20 +09:00
committed by Jens Schauder
parent fa8b95c5f3
commit 04c29f4004
42 changed files with 1364 additions and 57 deletions

View File

@@ -27,6 +27,7 @@ import org.springframework.lang.Nullable;
* Executes an {@link MutableAggregateChange}.
*
* @author Jens Schauder
* @author Myeonghyeon Lee
* @since 2.0
*/
class AggregateChangeExecutor {
@@ -77,6 +78,10 @@ class AggregateChangeExecutor {
executionContext.executeDeleteRoot((DbAction.DeleteRoot<?>) action);
} else if (action instanceof DbAction.DeleteAllRoot) {
executionContext.executeDeleteAllRoot((DbAction.DeleteAllRoot<?>) action);
} else if (action instanceof DbAction.AcquireLockRoot) {
executionContext.executeAcquireLock((DbAction.AcquireLockRoot<?>) action);
} else if (action instanceof DbAction.AcquireLockAllRoot) {
executionContext.executeAcquireLockAllRoot((DbAction.AcquireLockAllRoot<?>) action);
} else {
throw new RuntimeException("unexpected action");
}

View File

@@ -42,6 +42,7 @@ import org.springframework.data.relational.core.conversion.RelationalEntityVersi
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.sql.LockMode;
import org.springframework.data.util.Pair;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -49,6 +50,7 @@ import org.springframework.util.Assert;
/**
* @author Jens Schauder
* @author Umut Erturk
* @author Myeonghyeon Lee
*/
class JdbcAggregateChangeExecutionContext {
@@ -164,6 +166,14 @@ class JdbcAggregateChangeExecutionContext {
}
}
<T> void executeAcquireLock(DbAction.AcquireLockRoot<T> acquireLock) {
accessStrategy.acquireLockById(acquireLock.getId(), LockMode.PESSIMISTIC_WRITE, acquireLock.getEntityType());
}
<T> void executeAcquireLockAllRoot(DbAction.AcquireLockAllRoot<T> acquireLock) {
accessStrategy.acquireLockAll(LockMode.PESSIMISTIC_WRITE, acquireLock.getEntityType());
}
private void add(DbActionExecutionResult result) {
results.put(result.getAction(), result);
}

View File

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

View File

@@ -24,6 +24,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.sql.LockMode;
/**
* Delegates each methods to the {@link DataAccessStrategy}s passed to the constructor in turn until the first that does
@@ -33,6 +34,7 @@ import org.springframework.data.relational.core.mapping.RelationalPersistentProp
* @author Mark Paluch
* @author Tyler Van Gorder
* @author Milan Milanov
* @author Myeonghyeon Lee
* @since 1.1
*/
public class CascadingDataAccessStrategy implements DataAccessStrategy {
@@ -115,6 +117,24 @@ public class CascadingDataAccessStrategy implements DataAccessStrategy {
collectVoid(das -> das.deleteAll(propertyPath));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#acquireLockById(java.lang.Object, org.springframework.data.relational.core.sql.LockMode, java.lang.Class)
*/
@Override
public <T> void acquireLockById(Object id, LockMode lockMode, Class<T> domainType) {
collectVoid(das -> das.acquireLockById(id, lockMode, domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#acquireLockAll(org.springframework.data.relational.core.sql.LockMode, java.lang.Class)
*/
@Override
public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
collectVoid(das -> das.acquireLockAll(lockMode, domainType));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)

View File

@@ -23,6 +23,7 @@ import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.JdbcAggregateOperations;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.sql.LockMode;
import org.springframework.lang.Nullable;
/**
@@ -33,6 +34,7 @@ import org.springframework.lang.Nullable;
* @author Jens Schauder
* @author Tyler Van Gorder
* @author Milan Milanov
* @author Myeonghyeon Lee
*/
public interface DataAccessStrategy extends RelationResolver {
@@ -129,6 +131,23 @@ public interface DataAccessStrategy extends RelationResolver {
*/
void deleteAll(PersistentPropertyPath<RelationalPersistentProperty> propertyPath);
/**
* Acquire Lock
*
* @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}.
* @param domainType the domain type of the entity. Must not be {@code null}.
*/
<T> void acquireLockById(Object id, LockMode lockMode, Class<T> domainType);
/**
* Acquire Lock entities 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}.
*/
<T> void acquireLockAll(LockMode lockMode, Class<T> domainType);
/**
* Counts the rows in the table representing the given domain type.
*

View File

@@ -18,6 +18,8 @@ package org.springframework.data.jdbc.core.convert;
import static org.springframework.data.jdbc.core.convert.SqlGenerator.*;
import java.sql.JDBCType;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@@ -25,10 +27,7 @@ import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.dao.*;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.support.JdbcUtil;
@@ -41,7 +40,9 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
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.ResultSetExtractor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
@@ -62,6 +63,7 @@ import org.springframework.util.Assert;
* @author Tom Hombergs
* @author Tyler Van Gorder
* @author Milan Milanov
* @author Myeonghyeon Lee
* @since 1.1
*/
public class DefaultDataAccessStrategy implements DataAccessStrategy {
@@ -237,6 +239,27 @@ public class DefaultDataAccessStrategy implements DataAccessStrategy {
.update(sql(propertyPath.getBaseProperty().getOwner().getType()).createDeleteAllSql(propertyPath));
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#acquireLockById(java.lang.Object, org.springframework.data.relational.core.sql.LockMode, java.lang.Class)
*/
@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);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#acquireLockAll(org.springframework.data.relational.core.sql.LockMode, java.lang.Class)
*/
@Override
public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
String acquireLockAllSql = sql(domainType).getAcquireLockAll(lockMode);
operations.query(acquireLockAllSql, Collections.emptyMap(), new NoMappingResultSetExtractor());
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)
@@ -582,4 +605,14 @@ 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

@@ -19,6 +19,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.mapping.PersistentPropertyPath;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.sql.LockMode;
import org.springframework.util.Assert;
/**
@@ -28,6 +29,7 @@ import org.springframework.util.Assert;
* @author Jens Schauder
* @author Tyler Van Gorder
* @author Milan Milanov
* @author Myeonghyeon Lee
* @since 1.1
*/
public class DelegatingDataAccessStrategy implements DataAccessStrategy {
@@ -107,6 +109,24 @@ public class DelegatingDataAccessStrategy implements DataAccessStrategy {
delegate.deleteAll(propertyPath);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#acquireLockById(java.lang.Object, org.springframework.data.relational.core.sql.LockMode, java.lang.Class)
*/
@Override
public <T> void acquireLockById(Object id, LockMode lockMode, Class<T> domainType) {
delegate.acquireLockById(id, lockMode, domainType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#acquireLockAll(org.springframework.data.relational.core.sql.LockMode, java.lang.Class)
*/
@Override
public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
delegate.acquireLockAll(lockMode, domainType);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#count(java.lang.Class)

View File

@@ -258,6 +258,26 @@ class SqlGenerator {
return findOneSql.get();
}
/**
* Create a {@code SELECT count(id) FROM … WHERE :id = … (LOCK CLAUSE)} statement.
*
* @param lockMode Lock clause mode.
* @return the statement as a {@link String}. Guaranteed to be not {@literal null}.
*/
String getAcquireLockById(LockMode lockMode) {
return this.createAcquireLockById(lockMode);
}
/**
* Create a {@code SELECT count(id) FROM … (LOCK CLAUSE)} statement.
*
* @param lockMode Lock clause mode.
* @return the statement as a {@link String}. Guaranteed to be not {@literal null}.
*/
String getAcquireLockAll(LockMode lockMode) {
return this.createAcquireLockAll(lockMode);
}
/**
* Create a {@code INSERT INTO … (…) VALUES(…)} statement.
*
@@ -359,6 +379,33 @@ class SqlGenerator {
return render(select);
}
private String createAcquireLockById(LockMode lockMode) {
Table table = this.getTable();
Select select = StatementBuilder //
.select(getIdColumn()) //
.from(table) //
.where(getIdColumn().isEqualTo(getBindMarker(ID_SQL_PARAMETER))) //
.lock(lockMode) //
.build();
return render(select);
}
private String createAcquireLockAll(LockMode lockMode) {
Table table = this.getTable();
Select select = StatementBuilder //
.select(getIdColumn()) //
.from(table) //
.lock(lockMode) //
.build();
return render(select);
}
private String createFindAllSql() {
return render(selectBuilder().build());
}

View File

@@ -26,6 +26,7 @@ import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jdbc.core.convert.CascadingDataAccessStrategy;
@@ -41,6 +42,7 @@ import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
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.namedparam.NamedParameterJdbcOperations;
import org.springframework.util.Assert;
@@ -60,6 +62,7 @@ import org.springframework.util.Assert;
* @author Mark Paluch
* @author Tyler Van Gorder
* @author Milan Milanov
* @author Myeonghyeon Lee
*/
public class MyBatisDataAccessStrategy implements DataAccessStrategy {
@@ -248,6 +251,34 @@ public class MyBatisDataAccessStrategy implements DataAccessStrategy {
sqlSession().delete(statement, parameter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#acquireLockById(java.lang.Object, org.springframework.data.relational.core.sql.LockMode, java.lang.Class)
*/
@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);
}
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#acquireLockAll(org.springframework.data.relational.core.sql.LockMode, java.lang.Class)
*/
@Override
public <T> void acquireLockAll(LockMode lockMode, Class<T> domainType) {
String statement = namespace(domainType) + ".acquireLockAll";
MyBatisContext parameter = new MyBatisContext(null, null, domainType, Collections.emptyMap());
sqlSession().selectOne(statement, parameter);
}
/*
* (non-Javadoc)
* @see org.springframework.data.jdbc.core.DataAccessStrategy#findById(java.lang.Object, java.lang.Class)

View File

@@ -19,6 +19,7 @@ import org.springframework.data.relational.core.dialect.AbstractDialect;
import org.springframework.data.relational.core.dialect.Dialect;
import org.springframework.data.relational.core.dialect.HsqlDbDialect;
import org.springframework.data.relational.core.dialect.LimitClause;
import org.springframework.data.relational.core.dialect.LockClause;
import org.springframework.data.relational.core.sql.IdentifierProcessing;
/**
@@ -27,6 +28,7 @@ import org.springframework.data.relational.core.sql.IdentifierProcessing;
* @author Mark Paluch
* @author Milan Milanov
* @author Jens Schauder
* @author Myeonghyeon Lee
*/
public class NonQuotingDialect extends AbstractDialect implements Dialect {
@@ -39,6 +41,11 @@ public class NonQuotingDialect extends AbstractDialect implements Dialect {
return HsqlDbDialect.INSTANCE.limit();
}
@Override
public LockClause lock() {
return HsqlDbDialect.INSTANCE.lock();
}
@Override
public IdentifierProcessing getIdentifierProcessing() {
return IdentifierProcessing.create(new IdentifierProcessing.Quoting(""), IdentifierProcessing.LetterCasing.AS_IS);

View File

@@ -46,6 +46,7 @@ import org.springframework.data.relational.core.mapping.RelationalMappingContext
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
import org.springframework.data.relational.core.sql.Aliased;
import org.springframework.data.relational.core.sql.LockMode;
import org.springframework.data.relational.core.sql.SqlIdentifier;
import org.springframework.data.relational.core.sql.Table;
@@ -96,16 +97,45 @@ public class SqlGeneratorUnitTests {
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");
.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();
}
@Test // DATAJDBC-493
public void getAcquireLockById() {
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();
}
@Test // DATAJDBC-493
public void getAcquireLockAll() {
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();
}

View File

@@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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;
@@ -34,7 +35,6 @@ 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.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.rules.SpringClassRule;
import org.springframework.test.context.junit4.rules.SpringMethodRule;
@@ -123,6 +123,131 @@ public class JdbcRepositoryConcurrencyIntegrationTests {
assertThat(exceptions).isEmpty();
}
@Test // DATAJDBC-493
public void updateConcurrencyWithDelete() 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(() -> {
try {
startLatch.countDown();
startLatch.await();
transactionTemplate.execute(status -> {
repository.deleteById(targetId);
return null;
});
} catch (Exception ex) {
exceptions.add(ex);
} finally {
doneLatch.countDown();
}
}).start();
doneLatch.await();
assertThat(exceptions).isEmpty();
assertThat(repository.findById(entity.id)).isEmpty();
}
@Test // DATAJDBC-493
public void updateConcurrencyWithDeleteAll() 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.
// 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(() -> {
try {
startLatch.countDown();
startLatch.await();
transactionTemplate.execute(status -> {
repository.deleteAll();
return null;
});
} 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) {
List<DummyEntity> concurrencyEntities = new ArrayList<>();

View File

@@ -20,7 +20,9 @@ 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;
import org.springframework.util.ClassUtils;
@@ -28,6 +30,7 @@ import org.springframework.util.ClassUtils;
* An SQL dialect for the ANSI SQL standard.
*
* @author Milan Milanov
* @author Myeonghyeon Lee
* @since 2.0
*/
public class AnsiDialect extends AbstractDialect {
@@ -78,6 +81,27 @@ public class AnsiDialect 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.LimitClause#getClausePosition()
*/
@Override
public Position getClausePosition() {
return Position.AFTER_ORDER_BY;
}
};
private final AnsiArrayColumns ARRAY_COLUMNS = new AnsiArrayColumns();
/*
@@ -89,6 +113,15 @@ public class AnsiDialect extends AbstractDialect {
return LIMIT_CLAUSE;
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.dialect.Dialect#lock()
*/
@Override
public LockClause lock() {
return LOCK_CLAUSE;
}
/*
* (non-Javadoc)
* @see org.springframework.data.relational.core.dialect.Dialect#getArraySupport()