JPA requires that IllegalStateException be thrown instead of UOE

leave code comments making this very clear, because it wasn't
clear at all (and is sort-of wrong)

on the other hand, add getHibernateLockMode(), and let the client
bypass the stupid JPA restriction
This commit is contained in:
Gavin King 2022-11-09 18:54:16 +01:00
parent 2a92267cd8
commit c966acf178
10 changed files with 141 additions and 17 deletions

View File

@ -11,6 +11,7 @@ import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType;
@ -537,9 +538,33 @@ public interface NativeQuery<T> extends Query<T>, SynchronizeableQuery {
@Override
NativeQuery<T> setReadOnly(boolean readOnly);
/**
* @inheritDoc
*
* This operation is supported even for native queries.
* Note that specifying an explicit lock mode might
* result in changes to the native SQL query that is
* actually executed.
*/
@Override
LockOptions getLockOptions();
/**
* @inheritDoc
*
* This operation is supported even for native queries.
* Note that specifying an explicit lock mode might
* result in changes to the native SQL query that is
* actually executed.
*/
@Override
NativeQuery<T> setLockOptions(LockOptions lockOptions);
/**
* Not applicable to native SQL queries.
*
* @throws IllegalStateException for consistency with JPA
*/
@Override
NativeQuery<T> setLockMode(String alias, LockMode lockMode);
@ -558,9 +583,52 @@ public interface NativeQuery<T> extends Query<T>, SynchronizeableQuery {
@Override
NativeQuery<T> setHint(String hintName, Object value);
/**
* Not applicable to native SQL queries, due to an unfortunate
* requirement of the JPA specification.
* <p>
* Use {@link #getHibernateLockMode()} to obtain the lock mode.
*
* @throws IllegalStateException as required by JPA
*/
@Override
LockModeType getLockMode();
/**
* @inheritDoc
*
* This operation is supported even for native queries.
* Note that specifying an explicit lock mode might
* result in changes to the native SQL query that is
* actually executed.
*/
@Override
LockMode getHibernateLockMode();
/**
* Not applicable to native SQL queries, due to an unfortunate
* requirement of the JPA specification.
* <p>
* Use {@link #setHibernateLockMode(LockMode)} or the hint named
* {@value org.hibernate.jpa.HibernateHints#HINT_NATIVE_LOCK_MODE}
* to set the lock mode.
*
* @throws IllegalStateException as required by JPA
*/
@Override
NativeQuery<T> setLockMode(LockModeType lockMode);
/**
* @inheritDoc
*
* This operation is supported even for native queries.
* Note that specifying an explicit lock mode might
* result in changes to the native SQL query that is
* actually executed.
*/
@Override
NativeQuery<T> setHibernateLockMode(LockMode lockMode);
@Override
<R> NativeQuery<R> setTupleTransformer(TupleTransformer<R> transformer);

View File

@ -353,8 +353,8 @@ public interface Query<R> extends SelectionQuery<R>, MutationQuery, TypedQuery<R
* driver and database. For maximum portability, the given lock
* mode should be {@link LockMode#PESSIMISTIC_WRITE}.
*
* @param alias a query alias, or {@code this} for a collection filter
* @param lockMode The lock mode to apply.
* @param alias A query alias
* @param lockMode The lock mode to apply
*
* @return {@code this}, for method chaining
*

View File

@ -336,6 +336,11 @@ public interface SelectionQuery<R> extends CommonQueryContract {
*/
SelectionQuery<R> setLockMode(LockModeType lockMode);
/**
* Get the root {@link LockMode} for the query
*/
LockMode getHibernateLockMode();
/**
* Specify the root {@link LockMode} for the query
*/

View File

@ -570,14 +570,24 @@ public abstract class AbstractSelectionQuery<R>
*
* @see #setHibernateLockMode
*/
@Override
public SelectionQuery<R> setLockMode(LockModeType lockMode) {
setHibernateLockMode( LockModeTypeHelper.getLockMode( lockMode ) );
return this;
}
/**
* Get the root LockMode for the query
*/
@Override
public LockMode getHibernateLockMode() {
return getLockOptions().getLockMode();
}
/**
* Specify the root LockMode for the query
*/
@Override
public SelectionQuery<R> setHibernateLockMode(LockMode lockMode) {
getLockOptions().setLockMode( lockMode );
return this;

View File

@ -55,7 +55,7 @@ public interface QueryImplementor<R> extends Query<R> {
@Override
QueryImplementor<R> setResultListTransformer(ResultListTransformer<R> transformer);
@Override
@Override @Deprecated @SuppressWarnings("deprecation")
default <T> QueryImplementor<T> setResultTransformer(ResultTransformer<T> transformer) {
Query.super.setResultTransformer( transformer );
//noinspection unchecked

View File

@ -472,22 +472,34 @@ public class NativeQueryImpl<R>
@Override
public LockModeType getLockMode() {
throw new UnsupportedOperationException( "Illegal attempt to get lock mode on a native-query" );
// the JPA spec requires IllegalStateException here, even
// though it's logically an UnsupportedOperationException
throw new IllegalStateException( "Illegal attempt to get lock mode on a native-query" );
}
@Override
public NativeQueryImplementor<R> setLockOptions(LockOptions lockOptions) {
throw new UnsupportedOperationException( "Illegal attempt to set lock options for a native query" );
super.setLockOptions( lockOptions );
return this;
}
@Override
public NativeQueryImplementor<R> setHibernateLockMode(LockMode lockMode) {
super.setHibernateLockMode( lockMode );
return this;
}
@Override
public NativeQueryImplementor<R> setLockMode(String alias, LockMode lockMode) {
throw new UnsupportedOperationException( "Illegal attempt to set lock mode for a native query" );
// throw IllegalStateException here for consistency with JPA
throw new IllegalStateException( "Illegal attempt to set lock mode for a native query" );
}
@Override
public NativeQueryImplementor<R> setLockMode(LockModeType lockModeType) {
throw new UnsupportedOperationException( "Illegal attempt to set lock mode for a native query" );
// the JPA spec requires IllegalStateException here, even
// though it's logically an UnsupportedOperationException
throw new IllegalStateException( "Illegal attempt to set lock mode for a native query" );
}
@Override

View File

@ -42,12 +42,6 @@ import jakarta.persistence.metamodel.SingularAttribute;
@Incubating
public interface NativeQueryImplementor<R> extends QueryImplementor<R>, NativeQuery<R>, NameableQuery {
@Override
LockOptions getLockOptions();
@Override
LockModeType getLockMode();
/**
* Best guess whether this is a select query. {@code null}
* indicates unknown
@ -59,7 +53,7 @@ public interface NativeQueryImplementor<R> extends QueryImplementor<R>, NativeQu
// covariant overrides - NativeQuery
@Override
@Override @Deprecated @SuppressWarnings("deprecation")
default <T> NativeQueryImplementor<T> setResultTransformer(ResultTransformer<T> transformer) {
QueryImplementor.super.setResultTransformer( transformer );
//noinspection unchecked
@ -179,6 +173,9 @@ public interface NativeQueryImplementor<R> extends QueryImplementor<R>, NativeQu
@Override
NativeQueryImplementor<R> setLockOptions(LockOptions lockOptions);
@Override
NativeQueryImplementor<R> setHibernateLockMode(LockMode lockMode);
@Override
NativeQueryImplementor<R> setLockMode(LockModeType lockMode);

View File

@ -115,12 +115,26 @@ public class QueryLockingTest extends BaseEntityManagerFunctionalTestCase {
em.getTransaction().begin();
NativeQuery query = em.createNativeQuery( "select * from lockable l" ).unwrap( NativeQuery.class );
// the spec disallows calling setLockMode in a native SQL query
// the spec disallows calling setLockMode() and getLockMode()
// on a native SQL query and requires that an IllegalStateException
// be thrown
try {
query.setLockMode( LockModeType.READ );
fail( "Should have failed" );
}
catch (UnsupportedOperationException expected) {
catch (IllegalStateException expected) {
}
catch (Exception e) {
fail( "Should have thrown IllegalStateException but threw " + e.getClass().getName() );
}
try {
query.getLockMode();
fail( "Should have failed" );
}
catch (IllegalStateException expected) {
}
catch (Exception e) {
fail( "Should have thrown IllegalStateException but threw " + e.getClass().getName() );
}
// however, we should be able to set it using hints

View File

@ -106,7 +106,21 @@ public class PagingAndLockingTest extends BaseCoreFunctionalTestCase {
qry.getLockOptions().setLockMode( LockMode.PESSIMISTIC_WRITE );
qry.setFirstResult( 2 );
qry.setMaxResults( 2 );
@SuppressWarnings("unchecked") List results = qry.list();
List results = qry.list();
assertEquals( 2, results.size() );
for ( Object door : results ) {
assertEquals( LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode( door ) );
}
}
);
inTransaction(
session -> {
NativeQuery qry = session.createNativeQuery( "select * from door" );
qry.addRoot( "door", Door.class );
qry.setHibernateLockMode( LockMode.PESSIMISTIC_WRITE );
qry.setFirstResult( 2 );
qry.setMaxResults( 2 );
List results = qry.list();
assertEquals( 2, results.size() );
for ( Object door : results ) {
assertEquals( LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode( door ) );

View File

@ -171,6 +171,7 @@ public class AddNamedQueryTest {
assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() );
assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getHibernateLockMode() );
// jpa timeout is in milliseconds, whereas Hibernate's is in seconds
assertEquals( (Integer) 3, hibernateQuery.getTimeout() );
@ -188,6 +189,7 @@ public class AddNamedQueryTest {
assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() );
assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getHibernateLockMode() );
assertEquals( (Integer) 10, hibernateQuery.getTimeout() );
query.setHint( HINT_SPEC_QUERY_TIMEOUT, 10000 );
@ -202,6 +204,7 @@ public class AddNamedQueryTest {
assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() );
assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getHibernateLockMode() );
assertEquals( (Integer) 10, hibernateQuery.getTimeout() );
query.setFirstResult( 51 );
@ -216,6 +219,7 @@ public class AddNamedQueryTest {
assertEquals( FlushModeType.COMMIT, hibernateQuery.getFlushMode() );
assertEquals( CacheMode.IGNORE, hibernateQuery.getCacheMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getLockMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getHibernateLockMode() );
assertEquals( (Integer) 10, hibernateQuery.getTimeout() );
}
);