HHH-9486: Use follow-on locking when paging only
This commit is contained in:
parent
757f51d298
commit
832b62f7bb
|
@ -57,6 +57,8 @@ public class LockOptions implements Serializable {
|
|||
|
||||
private Map<String,LockMode> aliasSpecificLockModes;
|
||||
|
||||
private Boolean followOnLocking;
|
||||
|
||||
/**
|
||||
* Constructs a LockOptions with all default options.
|
||||
*/
|
||||
|
@ -278,6 +280,25 @@ public class LockOptions implements Serializable {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the current follow-on-locking setting.
|
||||
*
|
||||
* @return true if follow-on-locking is enabled
|
||||
*/
|
||||
public Boolean getFollowOnLocking() {
|
||||
return followOnLocking;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the the follow-on-locking setting.
|
||||
* @param followOnLocking The new follow-on-locking setting
|
||||
* @return this (for method chaining).
|
||||
*/
|
||||
public LockOptions setFollowOnLocking(Boolean followOnLocking) {
|
||||
this.followOnLocking = followOnLocking;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a copy.
|
||||
*
|
||||
|
@ -304,6 +325,7 @@ public class LockOptions implements Serializable {
|
|||
if ( source.aliasSpecificLockModes != null ) {
|
||||
destination.aliasSpecificLockModes = new HashMap<String,LockMode>( source.aliasSpecificLockModes );
|
||||
}
|
||||
destination.setFollowOnLocking( source.getFollowOnLocking() );
|
||||
return destination;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
*/
|
||||
package org.hibernate.annotations;
|
||||
|
||||
import org.hibernate.engine.spi.QueryParameters;
|
||||
|
||||
/**
|
||||
* Consolidation of hints available to Hibernate JPA queries. Mainly used to define features available on
|
||||
* Hibernate queries that have no corollary in JPA queries.
|
||||
|
@ -117,4 +119,13 @@ public class QueryHints {
|
|||
*/
|
||||
public static final String LOADGRAPH = "javax.persistence.loadgraph";
|
||||
|
||||
/**
|
||||
* Hint to enable/disable the follow-on-locking mechanism provided by {@link org.hibernate.dialect.Dialect#useFollowOnLocking(QueryParameters)}.
|
||||
* A value of {@code true} enables follow-on-locking, whereas a value of {@code false} disables it.
|
||||
* If the value is {@code null}, the the {@code Dialect} strategy is going to be used instead.
|
||||
*
|
||||
* @since 5.2
|
||||
*/
|
||||
public static final String FOLLOW_ON_LOCKING = "hibernate.query.followOnLocking";
|
||||
|
||||
}
|
||||
|
|
|
@ -1473,7 +1473,7 @@ public interface AvailableSettings {
|
|||
*/
|
||||
String PROCEDURE_NULL_PARAM_PASSING = "hibernate.proc.param_null_passing";
|
||||
|
||||
/*
|
||||
/**
|
||||
* Enable instantiation of composite/embedded objects when all of its attribute values are {@code null}.
|
||||
* The default (and historical) behavior is that a {@code null} reference will be used to represent the
|
||||
* composite when all of its attributes are {@code null}
|
||||
|
|
|
@ -16,6 +16,7 @@ import javax.persistence.QueryHint;
|
|||
import org.hibernate.AnnotationException;
|
||||
import org.hibernate.CacheMode;
|
||||
import org.hibernate.FlushMode;
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.annotations.QueryHints;
|
||||
|
@ -69,6 +70,20 @@ public class QueryHintDefinition {
|
|||
}
|
||||
}
|
||||
|
||||
public LockMode getLockMode(String query) {
|
||||
String hitName = QueryHints.NATIVE_LOCKMODE;
|
||||
String value =(String) hintsMap.get( hitName );
|
||||
if ( value == null ) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LockMode.fromExternalForm( value );
|
||||
}
|
||||
catch ( MappingException e ) {
|
||||
throw new AnnotationException( "Unknown LockMode in hint: " + query + ":" + hitName, e );
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getBoolean(String query, String hintName) {
|
||||
String value =(String) hintsMap.get( hintName );
|
||||
if ( value == null ) {
|
||||
|
@ -120,8 +135,15 @@ public class QueryHintDefinition {
|
|||
public LockOptions determineLockOptions(NamedQuery namedQueryAnnotation) {
|
||||
LockModeType lockModeType = namedQueryAnnotation.lockMode();
|
||||
Integer lockTimeoutHint = getInteger( namedQueryAnnotation.name(), "javax.persistence.lock.timeout" );
|
||||
Boolean followOnLocking = getBoolean( namedQueryAnnotation.name(), QueryHints.FOLLOW_ON_LOCKING );
|
||||
|
||||
LockOptions lockOptions = new LockOptions( LockModeConverter.convertToLockMode( lockModeType ) );
|
||||
return determineLockOptions(lockModeType, lockTimeoutHint, followOnLocking);
|
||||
}
|
||||
|
||||
private LockOptions determineLockOptions(LockModeType lockModeType, Integer lockTimeoutHint, Boolean followOnLocking) {
|
||||
|
||||
LockOptions lockOptions = new LockOptions( LockModeConverter.convertToLockMode( lockModeType ) )
|
||||
.setFollowOnLocking( followOnLocking );
|
||||
if ( lockTimeoutHint != null ) {
|
||||
lockOptions.setTimeOut( lockTimeoutHint );
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder;
|
|||
import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport;
|
||||
import org.hibernate.engine.jdbc.env.spi.SchemaNameResolver;
|
||||
import org.hibernate.engine.jdbc.spi.JdbcServices;
|
||||
import org.hibernate.engine.spi.QueryParameters;
|
||||
import org.hibernate.engine.spi.SharedSessionContractImplementor;
|
||||
import org.hibernate.exception.spi.ConversionContext;
|
||||
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
|
||||
|
@ -2650,7 +2651,22 @@ public abstract class Dialect implements ConversionContext {
|
|||
* @return {@code true} indicates that the dialect requests that locking be applied by subsequent select;
|
||||
* {@code false} (the default) indicates that locking should be applied to the main SQL statement..
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean useFollowOnLocking() {
|
||||
return useFollowOnLocking( null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Some dialects have trouble applying pessimistic locking depending upon what other query options are
|
||||
* specified (paging, ordering, etc). This method allows these dialects to request that locking be applied
|
||||
* by subsequent selects.
|
||||
*
|
||||
* @param parameters query parameters
|
||||
* @return {@code true} indicates that the dialect requests that locking be applied by subsequent select;
|
||||
* {@code false} (the default) indicates that locking should be applied to the main SQL statement..
|
||||
* @since 5.2
|
||||
*/
|
||||
public boolean useFollowOnLocking(QueryParameters parameters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.sql.SQLException;
|
|||
import java.sql.Types;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.hibernate.JDBCException;
|
||||
import org.hibernate.QueryTimeoutException;
|
||||
|
@ -25,6 +26,7 @@ import org.hibernate.dialect.function.VarArgsSQLFunction;
|
|||
import org.hibernate.dialect.pagination.AbstractLimitHandler;
|
||||
import org.hibernate.dialect.pagination.LimitHandler;
|
||||
import org.hibernate.dialect.pagination.LimitHelper;
|
||||
import org.hibernate.engine.spi.QueryParameters;
|
||||
import org.hibernate.engine.spi.RowSelection;
|
||||
import org.hibernate.exception.ConstraintViolationException;
|
||||
import org.hibernate.exception.LockAcquisitionException;
|
||||
|
@ -55,6 +57,12 @@ import org.hibernate.type.descriptor.sql.SqlTypeDescriptor;
|
|||
@SuppressWarnings("deprecation")
|
||||
public class Oracle8iDialect extends Dialect {
|
||||
|
||||
private static final Pattern DISTINCT_KEYWORD_PATTERN = Pattern.compile( "\\bdistinct\\b" );
|
||||
|
||||
private static final Pattern GROUP_BY_KEYWORD_PATTERN = Pattern.compile( "\\bgroup by\\b" );
|
||||
|
||||
private static final Pattern ORDER_BY_KEYWORD_PATTERN = Pattern.compile( "\\border by\\b" );
|
||||
|
||||
private static final AbstractLimitHandler LIMIT_HANDLER = new AbstractLimitHandler() {
|
||||
@Override
|
||||
public String processSql(String sql, RowSelection selection) {
|
||||
|
@ -624,9 +632,32 @@ public class Oracle8iDialect extends Dialect {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* For Oracle, the FOR UPDATE clause cannot be applied when using ORDER BY, DISTINCT or views.
|
||||
* @param parameters
|
||||
* @return
|
||||
@see <a href="https://docs.oracle.com/database/121/SQLRF/statements_10002.htm#SQLRF01702">Oracle FOR UPDATE restrictions</a>
|
||||
*/
|
||||
@Override
|
||||
public boolean useFollowOnLocking() {
|
||||
return true;
|
||||
public boolean useFollowOnLocking(QueryParameters parameters) {
|
||||
|
||||
if (parameters != null ) {
|
||||
String lowerCaseSQL = parameters.getFilteredSQL().toLowerCase();
|
||||
|
||||
return
|
||||
DISTINCT_KEYWORD_PATTERN.matcher( lowerCaseSQL ).find() ||
|
||||
GROUP_BY_KEYWORD_PATTERN.matcher( lowerCaseSQL ).find() ||
|
||||
(
|
||||
parameters.hasRowSelection() &&
|
||||
(
|
||||
ORDER_BY_KEYWORD_PATTERN.matcher( lowerCaseSQL ).find() ||
|
||||
parameters.getRowSelection().getFirstRow() != null
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -22,6 +22,7 @@ import org.hibernate.dialect.function.SQLFunctionTemplate;
|
|||
import org.hibernate.dialect.identity.IdentityColumnSupport;
|
||||
import org.hibernate.dialect.identity.Teradata14IdentityColumnSupport;
|
||||
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
|
||||
import org.hibernate.engine.spi.QueryParameters;
|
||||
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtracter;
|
||||
import org.hibernate.exception.spi.ViolatedConstraintNameExtracter;
|
||||
import org.hibernate.mapping.Column;
|
||||
|
@ -191,7 +192,7 @@ public class Teradata14Dialect extends TeradataDialect {
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean useFollowOnLocking() {
|
||||
public boolean useFollowOnLocking(QueryParameters parameters) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -96,6 +96,7 @@ public class CascadingActions {
|
|||
LockOptions lockOptions = (LockOptions) anything;
|
||||
lr.setTimeOut( lockOptions.getTimeOut() );
|
||||
lr.setScope( lockOptions.getScope() );
|
||||
lr.setFollowOnLocking( lockOptions.getFollowOnLocking() );
|
||||
if ( lockOptions.getScope() ) {
|
||||
lockMode = lockOptions.getLockMode();
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import static org.hibernate.annotations.QueryHints.COMMENT;
|
|||
import static org.hibernate.annotations.QueryHints.FETCHGRAPH;
|
||||
import static org.hibernate.annotations.QueryHints.FETCH_SIZE;
|
||||
import static org.hibernate.annotations.QueryHints.FLUSH_MODE;
|
||||
import static org.hibernate.annotations.QueryHints.FOLLOW_ON_LOCKING;
|
||||
import static org.hibernate.annotations.QueryHints.LOADGRAPH;
|
||||
import static org.hibernate.annotations.QueryHints.NATIVE_LOCKMODE;
|
||||
import static org.hibernate.annotations.QueryHints.READ_ONLY;
|
||||
|
@ -99,6 +100,8 @@ public class QueryHints {
|
|||
*/
|
||||
public static final String HINT_LOADGRAPH = LOADGRAPH;
|
||||
|
||||
public static final String HINT_FOLLOW_ON_LOCKING = FOLLOW_ON_LOCKING;
|
||||
|
||||
private static final Set<String> HINTS = buildHintsSet();
|
||||
|
||||
private static Set<String> buildHintsSet() {
|
||||
|
|
|
@ -248,7 +248,8 @@ public abstract class Loader {
|
|||
QueryParameters parameters,
|
||||
Dialect dialect,
|
||||
List<AfterLoadAction> afterLoadActions) {
|
||||
if ( dialect.useFollowOnLocking() ) {
|
||||
if ( ( parameters.getLockOptions().getFollowOnLocking() == null && dialect.useFollowOnLocking( parameters ) ) ||
|
||||
( parameters.getLockOptions().getFollowOnLocking() != null && parameters.getLockOptions().getFollowOnLocking() ) ) {
|
||||
// currently only one lock mode is allowed in follow-on locking
|
||||
final LockMode lockMode = determineFollowOnLockMode( parameters.getLockOptions() );
|
||||
final LockOptions lockOptions = new LockOptions( lockMode );
|
||||
|
|
|
@ -205,9 +205,10 @@ public class CriteriaLoader extends OuterJoinLoader {
|
|||
return sql;
|
||||
}
|
||||
|
||||
if ( dialect.useFollowOnLocking() ) {
|
||||
final LockMode lockMode = determineFollowOnLockMode( lockOptions );
|
||||
if ( lockMode != LockMode.UPGRADE_SKIPLOCKED ) {
|
||||
if ( ( parameters.getLockOptions().getFollowOnLocking() == null && dialect.useFollowOnLocking( parameters ) ) ||
|
||||
( parameters.getLockOptions().getFollowOnLocking() != null && parameters.getLockOptions().getFollowOnLocking() ) ) {
|
||||
final LockMode lockMode = determineFollowOnLockMode( lockOptions );
|
||||
if( lockMode != LockMode.UPGRADE_SKIPLOCKED ) {
|
||||
// Dialect prefers to perform locking in a separate step
|
||||
LOG.usingFollowOnLocking();
|
||||
|
||||
|
|
|
@ -89,6 +89,7 @@ import static org.hibernate.jpa.QueryHints.HINT_COMMENT;
|
|||
import static org.hibernate.jpa.QueryHints.HINT_FETCHGRAPH;
|
||||
import static org.hibernate.jpa.QueryHints.HINT_FETCH_SIZE;
|
||||
import static org.hibernate.jpa.QueryHints.HINT_FLUSH_MODE;
|
||||
import static org.hibernate.jpa.QueryHints.HINT_FOLLOW_ON_LOCKING;
|
||||
import static org.hibernate.jpa.QueryHints.HINT_LOADGRAPH;
|
||||
import static org.hibernate.jpa.QueryHints.HINT_READONLY;
|
||||
import static org.hibernate.jpa.QueryHints.HINT_TIMEOUT;
|
||||
|
@ -257,6 +258,7 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
|||
this.lockOptions.setLockMode( lockOptions.getLockMode() );
|
||||
this.lockOptions.setScope( lockOptions.getScope() );
|
||||
this.lockOptions.setTimeOut( lockOptions.getTimeOut() );
|
||||
this.lockOptions.setFollowOnLocking( lockOptions.getFollowOnLocking() );
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -988,6 +990,9 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
|||
}
|
||||
applied = true;
|
||||
}
|
||||
else if ( HINT_FOLLOW_ON_LOCKING.equals( hintName ) ) {
|
||||
applied = applyFollowOnLockingHint( ConfigurationHelper.getBoolean( value ) );
|
||||
}
|
||||
else {
|
||||
log.ignoringUnrecognizedQueryHint( hintName );
|
||||
}
|
||||
|
@ -1172,6 +1177,16 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
|||
this.entityGraphQueryHint = hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the follow-on-locking hint.
|
||||
*
|
||||
* @param followOnLocking The follow-on-locking strategy.
|
||||
*/
|
||||
protected boolean applyFollowOnLockingHint(Boolean followOnLocking) {
|
||||
getLockOptions().setFollowOnLocking( followOnLocking );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the query represented here a native (SQL) query?
|
||||
*
|
||||
|
|
|
@ -0,0 +1,448 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
|
||||
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
|
||||
*/
|
||||
package org.hibernate.test.dialect.functional;
|
||||
|
||||
import java.util.List;
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.LockModeType;
|
||||
import javax.persistence.NamedQuery;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.QueryHint;
|
||||
|
||||
import org.hibernate.LockMode;
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.annotations.QueryHints;
|
||||
import org.hibernate.boot.SessionFactoryBuilder;
|
||||
import org.hibernate.dialect.Oracle8iDialect;
|
||||
import org.hibernate.exception.SQLGrammarException;
|
||||
|
||||
import org.hibernate.testing.RequiresDialect;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase;
|
||||
import org.hibernate.test.util.jdbc.SQLStatementInterceptor;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Vlad Mihalcea
|
||||
*/
|
||||
@RequiresDialect(value = { Oracle8iDialect.class })
|
||||
@TestForIssue(jiraKey = "HHH-9486")
|
||||
public class OracleFollowOnLockingTest extends
|
||||
BaseNonConfigCoreFunctionalTestCase {
|
||||
|
||||
private SQLStatementInterceptor sqlStatementInterceptor;
|
||||
|
||||
@Override
|
||||
protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) {
|
||||
sqlStatementInterceptor = new SQLStatementInterceptor( sfb );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class[] {
|
||||
Product.class
|
||||
};
|
||||
}
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
for ( int i = 0; i < 50; i++ ) {
|
||||
Product product = new Product();
|
||||
product.name = "Product " + i % 10;
|
||||
session.persist( product );
|
||||
}
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isCleanupTestDataRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithMaxResultsThenNoFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ) )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithFirstResultsThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ) )
|
||||
.setFirstResult( 40 )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 11, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithNamedQueryExplicitlyEnablingFollowOnLockingThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products = session.createNamedQuery(
|
||||
"product_by_name", Product.class )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 50, products.size() );
|
||||
assertEquals( 51, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithCountDistinctThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products = session.createQuery(
|
||||
"select p from Product p where ( select count(distinct p1.id) from Product p1 ) > 0 ", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ).setFollowOnLocking( false ) )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 50, products.size() );
|
||||
assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithFirstResultsWhileExplicitlyDisablingFollowOnLockingThenFails() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
try {
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( false ) )
|
||||
.setFirstResult( 40 )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
fail( "Should throw exception since Oracle does not support ORDER BY if follow on locking is disabled" );
|
||||
}
|
||||
catch ( PersistenceException expected ) {
|
||||
assertEquals(
|
||||
SQLGrammarException.class,
|
||||
expected.getCause().getClass()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithFirstResultsWhileExplicitlyEnablingFollowOnLockingThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( true ) )
|
||||
.setFirstResult( 40 )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 11, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithMaxResultsAndOrderByThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p order by p.id", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ) )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 11, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithMaxResultsAndOrderByWhileExplicitlyDisablingFollowOnLockingThenFails() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
try {
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p order by p.id",
|
||||
Product.class
|
||||
)
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( false ) )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
fail( "Should throw exception since Oracle does not support ORDER BY if follow on locking is disabled" );
|
||||
}
|
||||
catch ( PersistenceException expected ) {
|
||||
assertEquals(
|
||||
SQLGrammarException.class,
|
||||
expected.getCause().getClass()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithMaxResultsAndOrderByWhileExplicitlyEnablingFollowOnLockingThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select p from Product p order by p.id", Product.class )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( true ) )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 11, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithDistinctThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select distinct p from Product p",
|
||||
Product.class
|
||||
)
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ) )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 50, products.size() );
|
||||
assertEquals( 51, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithDistinctWhileExplicitlyDisablingFollowOnLockingThenFails() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
try {
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select distinct p from Product p where p.id > 40",
|
||||
Product.class
|
||||
)
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( false ) )
|
||||
.getResultList();
|
||||
fail( "Should throw exception since Oracle does not support DISTINCT if follow on locking is disabled" );
|
||||
}
|
||||
catch ( PersistenceException expected ) {
|
||||
assertEquals(
|
||||
SQLGrammarException.class,
|
||||
expected.getCause().getClass()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithDistinctWhileExplicitlyEnablingFollowOnLockingThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Product> products =
|
||||
session.createQuery(
|
||||
"select distinct p from Product p where p.id > 40" )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( true ) )
|
||||
.setMaxResults( 10 )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 10, products.size() );
|
||||
assertEquals( 11, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithGroupByThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Object[]> products =
|
||||
session.createQuery(
|
||||
"select count(p), p " +
|
||||
"from Product p " +
|
||||
"group by p.id, p.name " )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE ) )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 50, products.size() );
|
||||
assertEquals( 51, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithGroupByWhileExplicitlyDisablingFollowOnLockingThenFails() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
try {
|
||||
List<Object[]> products =
|
||||
session.createQuery(
|
||||
"select count(p), p " +
|
||||
"from Product p " +
|
||||
"group by p.id, p.name " )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( false ) )
|
||||
.getResultList();
|
||||
fail( "Should throw exception since Oracle does not support GROUP BY if follow on locking is disabled" );
|
||||
}
|
||||
catch ( PersistenceException expected ) {
|
||||
assertEquals(
|
||||
SQLGrammarException.class,
|
||||
expected.getCause().getClass()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPessimisticLockWithGroupByWhileExplicitlyEnablingFollowOnLockingThenFollowOnLocking() {
|
||||
|
||||
final Session session = openSession();
|
||||
session.beginTransaction();
|
||||
|
||||
sqlStatementInterceptor.getSqlQueries().clear();
|
||||
|
||||
List<Object[]> products =
|
||||
session.createQuery(
|
||||
"select count(p), p " +
|
||||
"from Product p " +
|
||||
"group by p.id, p.name " )
|
||||
.setLockOptions( new LockOptions( LockMode.PESSIMISTIC_WRITE )
|
||||
.setFollowOnLocking( true ) )
|
||||
.getResultList();
|
||||
|
||||
assertEquals( 50, products.size() );
|
||||
assertEquals( 51, sqlStatementInterceptor.getSqlQueries().size() );
|
||||
|
||||
session.getTransaction().commit();
|
||||
session.close();
|
||||
}
|
||||
|
||||
@NamedQuery(
|
||||
name = "product_by_name",
|
||||
query = "select p from Product p where p.name is not null",
|
||||
lockMode = LockModeType.PESSIMISTIC_WRITE,
|
||||
hints = @QueryHint(name = QueryHints.FOLLOW_ON_LOCKING, value = "true")
|
||||
)
|
||||
@Entity(name = "Product")
|
||||
public static class Product {
|
||||
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
||||
private String name;
|
||||
}
|
||||
}
|
|
@ -192,7 +192,7 @@ abstract public class DialectChecks {
|
|||
|
||||
public static class DoesNotSupportFollowOnLocking implements DialectCheck {
|
||||
public boolean isMatch(Dialect dialect) {
|
||||
return !dialect.useFollowOnLocking();
|
||||
return !dialect.useFollowOnLocking( null );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue