HHH-9486: Use follow-on locking when paging only

This commit is contained in:
Piotr Findeisen 2014-11-18 14:47:31 +01:00 committed by Vlad Mihalcea
parent 757f51d298
commit 832b62f7bb
14 changed files with 582 additions and 10 deletions

View File

@ -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;
}
}

View File

@ -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";
}

View File

@ -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}

View File

@ -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 );
}

View File

@ -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;
}

View File

@ -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

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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() {

View File

@ -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 );

View File

@ -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();

View File

@ -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?
*

View File

@ -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;
}
}

View File

@ -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 );
}
}