HHH-8542 - javax.persistence.Query#setLockMode should throw ISE if not a SELECT JPQL or Criteria query

This commit is contained in:
Steve Ebersole 2013-09-22 19:57:02 -05:00
parent ccd1fb07c8
commit 903321fe92
9 changed files with 127 additions and 14 deletions

View File

@ -110,4 +110,12 @@ public class QueryHints {
*/
public static final String TIMEOUT_JPA = "javax.persistence.query.timeout";
/**
* Available to apply lock mode to a native SQL query since JPA requires that
* {@link javax.persistence.Query#setLockMode} throw an IllegalStateException if called for a native query.
* <p/>
* Accepts a {@link javax.persistence.LockModeType} or a {@link org.hibernate.LockMode}
*/
public static final String NATIVE_LOCKMODE = "org.hibernate.lockMode";
}

View File

@ -400,4 +400,8 @@ public class HQLQueryPlan implements Serializable {
public Class getDynamicInstantiationResultType() {
return translators[0].getDynamicInstantiationResultType();
}
public boolean isSelect() {
return !translators[0].isManipulationStatement();
}
}

View File

@ -23,10 +23,12 @@
*
*/
package org.hibernate.internal;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.hibernate.Filter;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
@ -139,10 +141,9 @@ public class QueryImpl extends AbstractQueryImpl {
return lockOptions;
}
public boolean isSelect() {
return getSession().getFactory().getQueryPlanCache()
.getHQLQueryPlan( getQueryString(), false, Collections.<String, Filter>emptyMap() )
.isSelect();
}
}

View File

@ -22,9 +22,20 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.jpa;
import java.util.HashSet;
import java.util.Set;
import static org.hibernate.annotations.QueryHints.*;
import static org.hibernate.annotations.QueryHints.CACHEABLE;
import static org.hibernate.annotations.QueryHints.CACHE_MODE;
import static org.hibernate.annotations.QueryHints.CACHE_REGION;
import static org.hibernate.annotations.QueryHints.COMMENT;
import static org.hibernate.annotations.QueryHints.FETCH_SIZE;
import static org.hibernate.annotations.QueryHints.FLUSH_MODE;
import static org.hibernate.annotations.QueryHints.NATIVE_LOCKMODE;
import static org.hibernate.annotations.QueryHints.READ_ONLY;
import static org.hibernate.annotations.QueryHints.TIMEOUT_HIBERNATE;
import static org.hibernate.annotations.QueryHints.TIMEOUT_JPA;
/**
* Defines the supported JPA query hints
@ -85,6 +96,8 @@ public class QueryHints {
*/
public static final String HINT_FLUSH_MODE = FLUSH_MODE;
public static final String HINT_NATIVE_LOCKMODE = NATIVE_LOCKMODE;
private static final Set<String> HINTS = buildHintsSet();
private static Set<String> buildHintsSet() {
@ -98,6 +111,7 @@ public class QueryHints {
hints.add( HINT_READONLY );
hints.add( HINT_CACHE_MODE );
hints.add( HINT_FLUSH_MODE );
hints.add( HINT_NATIVE_LOCKMODE );
return java.util.Collections.unmodifiableSet( hints );
}

View File

@ -94,6 +94,20 @@ public class QueryImpl<X> extends AbstractQueryImpl<X> implements TypedQuery<X>,
extractParameterInfo( namedParameterTypeRedefinitions );
}
@Override
protected boolean isNativeSqlQuery() {
return SQLQuery.class.isInstance( query );
}
@Override
protected boolean isSelectQuery() {
if ( isNativeSqlQuery() ) {
throw new IllegalStateException( "Cannot tell if native SQL query is SELECT query" );
}
return org.hibernate.internal.QueryImpl.class.cast( query ).isSelect();
}
@SuppressWarnings({ "unchecked", "RedundantCast" })
private void extractParameterInfo(Map<String,Class> namedParameterTypeRedefinition) {
if ( ! org.hibernate.internal.AbstractQueryImpl.class.isInstance( query ) ) {

View File

@ -402,6 +402,16 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro
);
}
@Override
protected boolean isNativeSqlQuery() {
return false;
}
@Override
protected boolean isSelectQuery() {
return false;
}
@Override
public Query setLockMode(LockModeType lockMode) {
throw new IllegalStateException( "javax.persistence.Query.setLockMode not valid on javax.persistence.StoredProcedureQuery" );
@ -415,6 +425,12 @@ public class StoredProcedureQueryImpl extends BaseQueryImpl implements StoredPro
// unsupported hints/calls ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@Override
protected void internalApplyLockMode(LockModeType lockModeType) {
throw new IllegalStateException( "Specifying LockMode not valid on javax.persistence.StoredProcedureQuery" );
}
@Override
protected void applyFirstResult(int firstResult) {
}

View File

@ -120,6 +120,14 @@ public abstract class AbstractQueryImpl<X> extends BaseQueryImpl implements Type
public TypedQuery<X> setLockMode(javax.persistence.LockModeType lockModeType) {
checkOpen( true );
if ( isNativeSqlQuery() ) {
throw new IllegalStateException( "Illegal attempt to set lock mode on a native SQL query" );
}
if ( ! isSelectQuery() ) {
throw new IllegalStateException( "Illegal attempt to set lock mode on a non-SELECT query" );
}
// todo : technically this check should be on execution of the query, not here : HHH-8521
if ( lockModeType != LockModeType.NONE ) {
if (! getEntityManager().isTransactionInProgress()) {
@ -136,8 +144,6 @@ public abstract class AbstractQueryImpl<X> extends BaseQueryImpl implements Type
return this;
}
protected abstract void internalApplyLockMode(javax.persistence.LockModeType lockModeType);
@Override
public javax.persistence.LockModeType getLockMode() {
getEntityManager().checkOpen( false );

View File

@ -26,6 +26,7 @@ package org.hibernate.jpa.spi;
import javax.persistence.CacheRetrieveMode;
import javax.persistence.CacheStoreMode;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.TemporalType;
@ -59,6 +60,7 @@ import static org.hibernate.jpa.QueryHints.HINT_CACHE_REGION;
import static org.hibernate.jpa.QueryHints.HINT_COMMENT;
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_NATIVE_LOCKMODE;
import static org.hibernate.jpa.QueryHints.HINT_READONLY;
import static org.hibernate.jpa.QueryHints.HINT_TIMEOUT;
import static org.hibernate.jpa.QueryHints.SPEC_HINT_TIMEOUT;
@ -330,6 +332,31 @@ public abstract class BaseQueryImpl implements Query {
CacheModeHelper.interpretCacheMode( storeMode, retrieveMode )
);
}
else if ( QueryHints.HINT_NATIVE_LOCKMODE.equals( hintName ) ) {
if ( !isNativeSqlQuery() ) {
throw new IllegalStateException(
"Illegal attempt to set lock mode on non-native query via hint; use Query#setLockMode instead"
);
}
if ( LockMode.class.isInstance( value ) ) {
internalApplyLockMode( LockModeTypeHelper.getLockModeType( (LockMode) value ) );
}
else if ( LockModeType.class.isInstance( value ) ) {
internalApplyLockMode( (LockModeType) value );
}
else {
throw new IllegalArgumentException(
String.format(
"Native lock-mode hint [%s] must specify %s or %s. Encountered type : %s",
HINT_NATIVE_LOCKMODE,
LockMode.class.getName(),
LockModeType.class.getName(),
value.getClass().getName()
)
);
}
applied = true;
}
else if ( hintName.startsWith( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE ) ) {
if ( canApplyAliasSpecificLockModeHints() ) {
// extract the alias
@ -369,6 +396,21 @@ public abstract class BaseQueryImpl implements Query {
return this;
}
/**
* Is the query represented here a native SQL query?
*
* @return {@code true} if it is a native SQL query; {@code false} otherwise
*/
protected abstract boolean isNativeSqlQuery();
/**
* Is the query represented here a SELECT query?
*
* @return {@code true} if the query is a SELECT; {@code false} otherwise.
*/
protected abstract boolean isSelectQuery();
protected abstract void internalApplyLockMode(javax.persistence.LockModeType lockModeType);
// FlushMode ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -30,7 +30,8 @@ import javax.persistence.LockModeType;
import org.junit.Test;
import org.hibernate.LockMode;
import org.hibernate.ejb.AvailableSettings;
import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.QueryHints;
import org.hibernate.jpa.internal.QueryImpl;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.internal.SessionImpl;
@ -40,6 +41,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author Steve Ebersole
@ -89,12 +91,18 @@ public class QueryLockingTest extends BaseEntityManagerFunctionalTestCase {
QueryImpl query = em.createNativeQuery( "select * from lockable l" ).unwrap( QueryImpl.class );
org.hibernate.internal.SQLQueryImpl hibernateQuery = (org.hibernate.internal.SQLQueryImpl) query.getHibernateQuery();
// assertEquals( LockMode.NONE, hibernateQuery.getLockOptions().getLockMode() );
// assertNull( hibernateQuery.getLockOptions().getAliasSpecificLockMode( "l" ) );
// assertEquals( LockMode.NONE, hibernateQuery.getLockOptions().getEffectiveLockMode( "l" ) );
// the spec disallows calling setLockMode in a native SQL query
try {
query.setLockMode( LockModeType.READ );
fail( "Should have failed" );
}
catch (IllegalStateException expected) {
}
// however, we should be able to set it using hints
query.setHint( QueryHints.HINT_NATIVE_LOCKMODE, LockModeType.READ );
// NOTE : LockModeType.READ should map to LockMode.OPTIMISTIC
query.setLockMode( LockModeType.READ );
assertEquals( LockMode.OPTIMISTIC, hibernateQuery.getLockOptions().getLockMode() );
assertNull( hibernateQuery.getLockOptions().getAliasSpecificLockMode( "l" ) );
assertEquals( LockMode.OPTIMISTIC, hibernateQuery.getLockOptions().getEffectiveLockMode( "l" ) );