HHH-1168 - Problem combining locking and paging on Oracle

This commit is contained in:
Steve Ebersole 2012-11-20 13:19:57 -06:00
parent c01dd40a65
commit 4b2871cfba
17 changed files with 484 additions and 46 deletions

View File

@ -2399,4 +2399,8 @@ public abstract class Dialect implements ConversionContext {
public boolean forceLobAsLastValue() { public boolean forceLobAsLastValue() {
return false; return false;
} }
public boolean supportsLockingAndPaging() {
return true;
}
} }

View File

@ -577,4 +577,8 @@ public class Oracle8iDialect extends Dialect {
return true; return true;
} }
@Override
public boolean supportsLockingAndPaging() {
return false;
}
} }

View File

@ -953,7 +953,8 @@ public class QueryTranslatorImpl extends BasicLoader implements FilterTranslator
if ( stats ) startTime = System.currentTimeMillis(); if ( stats ) startTime = System.currentTimeMillis();
try { try {
final ResultSet rs = executeQueryStatement( queryParameters, false, session ); final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>();
final ResultSet rs = executeQueryStatement( queryParameters, false, afterLoadActions, session );
final PreparedStatement st = (PreparedStatement) rs.getStatement(); final PreparedStatement st = (PreparedStatement) rs.getStatement();
HolderInstantiator hi = HolderInstantiator.createClassicHolderInstantiator(holderConstructor, queryParameters.getResultTransformer()); HolderInstantiator hi = HolderInstantiator.createClassicHolderInstantiator(holderConstructor, queryParameters.getResultTransformer());
Iterator result = new IteratorImpl( rs, st, session, queryParameters.isReadOnly( session ), returnTypes, getColumnNames(), hi ); Iterator result = new IteratorImpl( rs, st, session, queryParameters.isReadOnly( session ), returnTypes, getColumnNames(), hi );
@ -1094,8 +1095,13 @@ public class QueryTranslatorImpl extends BasicLoader implements FilterTranslator
} }
@Override @Override
protected String applyLocks(String sql, LockOptions lockOptions, Dialect dialect) throws QueryException { protected String applyLocks(
String sql,
QueryParameters parameters,
Dialect dialect,
List<AfterLoadAction> afterLoadActions) throws QueryException {
// can't cache this stuff either (per-invocation) // can't cache this stuff either (per-invocation)
final LockOptions lockOptions = parameters.getLockOptions();
final String result; final String result;
if ( lockOptions == null || if ( lockOptions == null ||
( lockOptions.getLockMode() == LockMode.NONE && lockOptions.getAliasLockCount() == 0 ) ) { ( lockOptions.getLockMode() == LockMode.NONE && lockOptions.getAliasLockCount() == 0 ) ) {

View File

@ -1583,4 +1583,12 @@ public interface CoreMessageLogger extends BasicLogger {
id = 443 id = 443
) )
void tooManyInExpressions(String dialectName, int limit, String paramName, int size); void tooManyInExpressions(String dialectName, int limit, String paramName, int size);
@LogMessage(level = WARN)
@Message(
value = "Encountered request which combined locking and paging, however dialect reports that database does " +
"not support that combination. Results will be locked after initial query executes",
id = 444
)
void delayedLockingDueToPaging();
} }

View File

@ -68,6 +68,7 @@ public class SQLQueryImpl extends AbstractQueryImpl implements SQLQuery {
private Collection<String> querySpaces; private Collection<String> querySpaces;
private final boolean callable; private final boolean callable;
private final LockOptions lockOptions = new LockOptions();
/** /**
* Constructs a SQLQueryImpl given a sql query defined in the mappings. * Constructs a SQLQueryImpl given a sql query defined in the mappings.
@ -245,8 +246,8 @@ public class SQLQueryImpl extends AbstractQueryImpl implements SQLQuery {
@Override @Override
public LockOptions getLockOptions() { public LockOptions getLockOptions() {
//we never need to apply locks to the SQL //we never need to apply locks to the SQL, however the native-sql loader handles this specially
return null; return lockOptions;
} }
public SQLQuery addScalar(final String columnAlias, final Type type) { public SQLQuery addScalar(final String columnAlias, final Type type) {

View File

@ -26,6 +26,7 @@ package org.hibernate.internal;
import java.sql.CallableStatement; import java.sql.CallableStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -299,6 +300,8 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
this.session = session; this.session = session;
} }
// todo : this would be a great way to add locking to stored procedure support (at least where returning entities).
public List processResultSet(ResultSet resultSet) throws SQLException { public List processResultSet(ResultSet resultSet) throws SQLException {
super.autoDiscoverTypes( resultSet ); super.autoDiscoverTypes( resultSet );
return super.processResultSet( return super.processResultSet(
@ -307,7 +310,8 @@ public class StoredProcedureOutputsImpl implements StoredProcedureOutputs {
session, session,
true, true,
null, null,
Integer.MAX_VALUE Integer.MAX_VALUE,
Collections.<AfterLoadAction>emptyList()
); );
} }
} }

View File

@ -31,6 +31,7 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
@ -47,6 +48,7 @@ import org.hibernate.LockOptions;
import org.hibernate.QueryException; import org.hibernate.QueryException;
import org.hibernate.ScrollMode; import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults; import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.StaleObjectStateException; import org.hibernate.StaleObjectStateException;
import org.hibernate.WrongClassException; import org.hibernate.WrongClassException;
import org.hibernate.cache.spi.FilterKey; import org.hibernate.cache.spi.FilterKey;
@ -59,6 +61,7 @@ import org.hibernate.dialect.pagination.LimitHelper;
import org.hibernate.dialect.pagination.NoopLimitHandler; import org.hibernate.dialect.pagination.NoopLimitHandler;
import org.hibernate.engine.internal.TwoPhaseLoad; import org.hibernate.engine.internal.TwoPhaseLoad;
import org.hibernate.engine.jdbc.ColumnNameCache; import org.hibernate.engine.jdbc.ColumnNameCache;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.EntityUniqueKey; import org.hibernate.engine.spi.EntityUniqueKey;
import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.PersistenceContext;
@ -195,7 +198,11 @@ public abstract class Loader {
* empty superclass implementation merely returns its first * empty superclass implementation merely returns its first
* argument. * argument.
*/ */
protected String applyLocks(String sql, LockOptions lockOptions, Dialect dialect) throws HibernateException { protected String applyLocks(
String sql,
QueryParameters parameters,
Dialect dialect,
List<AfterLoadAction> afterLoadActions) throws HibernateException {
return sql; return sql;
} }
@ -227,13 +234,48 @@ public abstract class Loader {
/** /**
* Modify the SQL, adding lock hints and comments, if necessary * Modify the SQL, adding lock hints and comments, if necessary
*/ */
protected String preprocessSQL(String sql, QueryParameters parameters, Dialect dialect) protected String preprocessSQL(
throws HibernateException { String sql,
QueryParameters parameters,
Dialect dialect,
List<AfterLoadAction> afterLoadActions) throws HibernateException {
sql = applyLocks( sql, parameters, dialect, afterLoadActions );
return getFactory().getSettings().isCommentsEnabled()
? prependComment( sql, parameters )
: sql;
}
sql = applyLocks( sql, parameters.getLockOptions(), dialect ); protected static interface AfterLoadAction {
public void afterLoad(SessionImplementor session, Object entity, Loadable persister);
}
return getFactory().getSettings().isCommentsEnabled() ? protected boolean shouldDelayLockingDueToPaging(
prependComment( sql, parameters ) : sql; String sql,
QueryParameters parameters,
Dialect dialect,
List<AfterLoadAction> afterLoadActions) {
final LockOptions lockOptions = parameters.getLockOptions();
final RowSelection rowSelection = parameters.getRowSelection();
final LimitHandler limitHandler = dialect.buildLimitHandler( sql, rowSelection );
if ( LimitHelper.useLimit( limitHandler, rowSelection ) ) {
// user has requested a combination of paging and locking. See if the dialect supports that
// (ahem, Oracle...)
if ( ! dialect.supportsLockingAndPaging() ) {
LOG.delayedLockingDueToPaging();
afterLoadActions.add(
new AfterLoadAction() {
private final LockOptions originalLockOptions = lockOptions.makeCopy();
@Override
public void afterLoad(SessionImplementor session, Object entity, Loadable persister) {
( (Session) session ).buildLockRequest( originalLockOptions ).lock( persister.getEntityName(), entity );
}
}
);
parameters.setLockOptions( new LockOptions() );
}
return true;
}
return false;
} }
private String prependComment(String sql, QueryParameters parameters) { private String prependComment(String sql, QueryParameters parameters) {
@ -351,7 +393,7 @@ public abstract class Loader {
resultSet, resultSet,
session, session,
queryParameters.isReadOnly( session ) queryParameters.isReadOnly( session )
); );
session.getPersistenceContext().initializeNonLazyCollections(); session.getPersistenceContext().initializeNonLazyCollections();
return result; return result;
} }
@ -410,7 +452,7 @@ public abstract class Loader {
resultSet, resultSet,
session, session,
queryParameters.isReadOnly( session ) queryParameters.isReadOnly( session )
); );
session.getPersistenceContext().initializeNonLazyCollections(); session.getPersistenceContext().initializeNonLazyCollections();
return result; return result;
} }
@ -843,7 +885,9 @@ public abstract class Loader {
selection.getMaxRows() : selection.getMaxRows() :
Integer.MAX_VALUE; Integer.MAX_VALUE;
final ResultSet rs = executeQueryStatement( queryParameters, false, session ); final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>();
final ResultSet rs = executeQueryStatement( queryParameters, false, afterLoadActions, session );
final Statement st = rs.getStatement(); final Statement st = rs.getStatement();
// would be great to move all this below here into another method that could also be used // would be great to move all this below here into another method that could also be used
@ -853,7 +897,7 @@ public abstract class Loader {
// that I could do the control breaking at the means to know when to stop // that I could do the control breaking at the means to know when to stop
try { try {
return processResultSet( rs, queryParameters, session, returnProxies, forcedResultTransformer, maxRows ); return processResultSet( rs, queryParameters, session, returnProxies, forcedResultTransformer, maxRows, afterLoadActions );
} }
finally { finally {
st.close(); st.close();
@ -867,7 +911,8 @@ public abstract class Loader {
SessionImplementor session, SessionImplementor session,
boolean returnProxies, boolean returnProxies,
ResultTransformer forcedResultTransformer, ResultTransformer forcedResultTransformer,
int maxRows) throws SQLException { int maxRows,
List<AfterLoadAction> afterLoadActions) throws SQLException {
final int entitySpan = getEntityPersisters().length; final int entitySpan = getEntityPersisters().length;
final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session ); final EntityKey optionalObjectKey = getOptionalObjectKey( queryParameters, session );
final LockMode[] lockModesArray = getLockModes( queryParameters.getLockOptions() ); final LockMode[] lockModesArray = getLockModes( queryParameters.getLockOptions() );
@ -902,8 +947,16 @@ public abstract class Loader {
LOG.tracev( "Done processing result set ({0} rows)", count ); LOG.tracev( "Done processing result set ({0} rows)", count );
initializeEntitiesAndCollections( hydratedObjects, rs, session, queryParameters.isReadOnly( session ) ); initializeEntitiesAndCollections(
if ( createSubselects ) createSubselects( subselectResultKeys, queryParameters, session ); hydratedObjects,
rs,
session,
queryParameters.isReadOnly( session ),
afterLoadActions
);
if ( createSubselects ) {
createSubselects( subselectResultKeys, queryParameters, session );
}
return results; return results;
} }
@ -989,8 +1042,22 @@ public abstract class Loader {
final List hydratedObjects, final List hydratedObjects,
final Object resultSetId, final Object resultSetId,
final SessionImplementor session, final SessionImplementor session,
final boolean readOnly) final boolean readOnly) throws HibernateException {
throws HibernateException { initializeEntitiesAndCollections(
hydratedObjects,
resultSetId,
session,
readOnly,
Collections.<AfterLoadAction>emptyList()
);
}
private void initializeEntitiesAndCollections(
final List hydratedObjects,
final Object resultSetId,
final SessionImplementor session,
final boolean readOnly,
List<AfterLoadAction> afterLoadActions) throws HibernateException {
final CollectionPersister[] collectionPersisters = getCollectionPersisters(); final CollectionPersister[] collectionPersisters = getCollectionPersisters();
if ( collectionPersisters != null ) { if ( collectionPersisters != null ) {
@ -1042,9 +1109,19 @@ public abstract class Loader {
// split off from initializeEntity. It *must* occur after // split off from initializeEntity. It *must* occur after
// endCollectionLoad to ensure the collection is in the // endCollectionLoad to ensure the collection is in the
// persistence context. // persistence context.
if ( hydratedObjects!=null ) { if ( hydratedObjects != null ) {
for ( Object hydratedObject : hydratedObjects ) { for ( Object hydratedObject : hydratedObjects ) {
TwoPhaseLoad.postLoad( hydratedObject, session, post ); TwoPhaseLoad.postLoad( hydratedObject, session, post );
if ( afterLoadActions != null ) {
for ( AfterLoadAction afterLoadAction : afterLoadActions ) {
final EntityEntry entityEntry = session.getPersistenceContext().getEntry( hydratedObject );
if ( entityEntry == null ) {
// big problem
throw new HibernateException( "Could not locate EntityEntry immediately after two-phase load" );
}
afterLoadAction.afterLoad( session, hydratedObject, (Loadable) entityEntry.getPersister() );
}
}
} }
} }
} }
@ -1721,15 +1798,17 @@ public abstract class Loader {
protected ResultSet executeQueryStatement( protected ResultSet executeQueryStatement(
final QueryParameters queryParameters, final QueryParameters queryParameters,
final boolean scroll, final boolean scroll,
List<AfterLoadAction> afterLoadActions,
final SessionImplementor session) throws SQLException { final SessionImplementor session) throws SQLException {
return executeQueryStatement( getSQLString(), queryParameters, scroll, session ); return executeQueryStatement( getSQLString(), queryParameters, scroll, afterLoadActions, session );
} }
protected ResultSet executeQueryStatement( protected ResultSet executeQueryStatement(
final String sqlStatement, String sqlStatement,
final QueryParameters queryParameters, QueryParameters queryParameters,
final boolean scroll, boolean scroll,
final SessionImplementor session) throws SQLException { List<AfterLoadAction> afterLoadActions,
SessionImplementor session) throws SQLException {
// Processing query filters. // Processing query filters.
queryParameters.processFilters( sqlStatement, session ); queryParameters.processFilters( sqlStatement, session );
@ -1742,7 +1821,7 @@ public abstract class Loader {
String sql = limitHandler.getProcessedSql(); String sql = limitHandler.getProcessedSql();
// Adding locks and comments. // Adding locks and comments.
sql = preprocessSQL( sql, queryParameters, getFactory().getDialect() ); sql = preprocessSQL( sql, queryParameters, getFactory().getDialect(), afterLoadActions );
final PreparedStatement st = prepareQueryStatement( sql, queryParameters, limitHandler, scroll, session ); final PreparedStatement st = prepareQueryStatement( sql, queryParameters, limitHandler, scroll, session );
return getResultSet( st, queryParameters.getRowSelection(), limitHandler, queryParameters.hasAutoDiscoverScalarTypes(), session ); return getResultSet( st, queryParameters.getRowSelection(), limitHandler, queryParameters.hasAutoDiscoverScalarTypes(), session );
@ -2499,7 +2578,7 @@ public abstract class Loader {
if ( stats ) startTime = System.currentTimeMillis(); if ( stats ) startTime = System.currentTimeMillis();
try { try {
final ResultSet rs = executeQueryStatement( queryParameters, true, session ); final ResultSet rs = executeQueryStatement( queryParameters, true, Collections.<AfterLoadAction>emptyList(), session );
final PreparedStatement st = (PreparedStatement) rs.getStatement(); final PreparedStatement st = (PreparedStatement) rs.getStatement();
if ( stats ) { if ( stats ) {

View File

@ -28,6 +28,8 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.dialect.pagination.LimitHelper; import org.hibernate.dialect.pagination.LimitHelper;
@ -250,10 +252,11 @@ public class DynamicBatchingCollectionInitializerBuilder extends BatchingCollect
selection.getMaxRows() : selection.getMaxRows() :
Integer.MAX_VALUE; Integer.MAX_VALUE;
final ResultSet rs = executeQueryStatement( sql, queryParameters, false, session ); final List<AfterLoadAction> afterLoadActions = Collections.emptyList();
final ResultSet rs = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session );
final Statement st = rs.getStatement(); final Statement st = rs.getStatement();
try { try {
processResultSet( rs, queryParameters, session, true, null, maxRows ); processResultSet( rs, queryParameters, session, true, null, maxRows, afterLoadActions );
} }
finally { finally {
st.close(); st.close();

View File

@ -36,14 +36,19 @@ import org.hibernate.LockOptions;
import org.hibernate.QueryException; import org.hibernate.QueryException;
import org.hibernate.ScrollMode; import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults; import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitHelper;
import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.internal.CriteriaImpl; import org.hibernate.internal.CriteriaImpl;
import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.loader.OuterJoinLoader; import org.hibernate.loader.OuterJoinLoader;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.Lockable; import org.hibernate.persister.entity.Lockable;
import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.transform.ResultTransformer; import org.hibernate.transform.ResultTransformer;
@ -190,12 +195,55 @@ public class CriteriaLoader extends OuterJoinLoader {
return querySpaces; return querySpaces;
} }
protected String applyLocks(String sqlSelectString, LockOptions lockOptions, Dialect dialect) throws QueryException { @Override
protected String applyLocks(
String sql,
QueryParameters parameters,
Dialect dialect,
List<AfterLoadAction> afterLoadActions) throws QueryException {
final LockOptions lockOptions = parameters.getLockOptions();
if ( lockOptions == null || if ( lockOptions == null ||
( lockOptions.getLockMode() == LockMode.NONE && lockOptions.getAliasLockCount() == 0 ) ) { ( lockOptions.getLockMode() == LockMode.NONE && lockOptions.getAliasLockCount() == 0 ) ) {
return sqlSelectString; return sql;
} }
// user is request locking, lets see if we can apply locking directly to the SQL...
// some dialects wont allow locking with paging...
final RowSelection rowSelection = parameters.getRowSelection();
final LimitHandler limitHandler = dialect.buildLimitHandler( sql, rowSelection );
if ( LimitHelper.useLimit( limitHandler, rowSelection ) ) {
// user has requested a combination of paging and locking. See if the dialect supports that
// (ahem, Oracle...)
if ( ! dialect.supportsLockingAndPaging() ) {
LOG.delayedLockingDueToPaging();
// this one is kind of ugly. currently we do not track the needed alias-to-entity
// mapping into the "hydratedEntities" which drives these callbacks
// so for now apply the root lock mode to all. The root lock mode is listed in
// the alias specific map under the alias "this_"...
final LockOptions lockOptionsToUse = new LockOptions();
lockOptionsToUse.setLockMode( lockOptions.getEffectiveLockMode( "this_" ) );
lockOptionsToUse.setTimeOut( lockOptions.getTimeOut() );
lockOptionsToUse.setScope( lockOptions.getScope() );
afterLoadActions.add(
new AfterLoadAction() {
@Override
public void afterLoad(SessionImplementor session, Object entity, Loadable persister) {
( (Session) session ).buildLockRequest( lockOptionsToUse )
.lock( persister.getEntityName(), entity );
}
}
);
parameters.setLockOptions( new LockOptions() );
return sql;
}
}
// there are other conditions we might want to add here, such as checking the result types etc
// but those are better served after we have redone the SQL generation to use ASTs.
final LockOptions locks = new LockOptions(lockOptions.getLockMode()); final LockOptions locks = new LockOptions(lockOptions.getLockMode());
locks.setScope( lockOptions.getScope()); locks.setScope( lockOptions.getScope());
locks.setTimeOut( lockOptions.getTimeOut()); locks.setTimeOut( lockOptions.getTimeOut());
@ -213,7 +261,7 @@ public class CriteriaLoader extends OuterJoinLoader {
} }
} }
} }
return dialect.applyLocksToSql( sqlSelectString, locks, keyColumnNames ); return dialect.applyLocksToSql( sql, locks, keyColumnNames );
} }
protected LockMode[] getLockModes(LockOptions lockOptions) { protected LockMode[] getLockModes(LockOptions lockOptions) {

View File

@ -27,6 +27,7 @@ import java.sql.ResultSet;
import java.sql.ResultSetMetaData; import java.sql.ResultSetMetaData;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -38,7 +39,12 @@ import org.hibernate.LockMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.QueryException; import org.hibernate.QueryException;
import org.hibernate.ScrollableResults; import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitHelper;
import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.hql.internal.HolderInstantiator; import org.hibernate.hql.internal.HolderInstantiator;
@ -50,6 +56,7 @@ import org.hibernate.loader.Loader;
import org.hibernate.persister.collection.CollectionPersister; import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.collection.QueryableCollection; import org.hibernate.persister.collection.QueryableCollection;
import org.hibernate.persister.entity.Loadable; import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.Lockable;
import org.hibernate.persister.entity.Queryable; import org.hibernate.persister.entity.Queryable;
import org.hibernate.transform.ResultTransformer; import org.hibernate.transform.ResultTransformer;
import org.hibernate.type.CollectionType; import org.hibernate.type.CollectionType;
@ -331,6 +338,35 @@ public class CustomLoader extends Loader {
return list( session, queryParameters, querySpaces, resultTypes ); return list( session, queryParameters, querySpaces, resultTypes );
} }
@Override
protected String applyLocks(
String sql,
QueryParameters parameters,
Dialect dialect,
List<AfterLoadAction> afterLoadActions) throws QueryException {
final LockOptions lockOptions = parameters.getLockOptions();
if ( lockOptions == null ||
( lockOptions.getLockMode() == LockMode.NONE && lockOptions.getAliasLockCount() == 0 ) ) {
return sql;
}
// user is request locking, lets see if we can apply locking directly to the SQL...
// some dialects wont allow locking with paging...
afterLoadActions.add(
new AfterLoadAction() {
private final LockOptions originalLockOptions = lockOptions.makeCopy();
@Override
public void afterLoad(SessionImplementor session, Object entity, Loadable persister) {
( (Session) session ).buildLockRequest( originalLockOptions ).lock( persister.getEntityName(), entity );
}
}
);
parameters.getLockOptions().setLockMode( LockMode.READ );
return sql;
}
public ScrollableResults scroll( public ScrollableResults scroll(
final QueryParameters queryParameters, final QueryParameters queryParameters,
final SessionImplementor session) throws HibernateException { final SessionImplementor session) throws HibernateException {

View File

@ -27,6 +27,7 @@ import java.io.Serializable;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
@ -253,10 +254,11 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil
selection.getMaxRows() : selection.getMaxRows() :
Integer.MAX_VALUE; Integer.MAX_VALUE;
final ResultSet rs = executeQueryStatement( sql, queryParameters, false, session ); final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>();
final ResultSet rs = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session );
final Statement st = rs.getStatement(); final Statement st = rs.getStatement();
try { try {
return processResultSet( rs, queryParameters, session, false, null, maxRows ); return processResultSet( rs, queryParameters, session, false, null, maxRows, afterLoadActions );
} }
finally { finally {
st.close(); st.close();

View File

@ -23,10 +23,12 @@
*/ */
package org.hibernate.loader.hql; package org.hibernate.loader.hql;
import java.io.Serializable;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -37,8 +39,12 @@ import org.hibernate.LockMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.QueryException; import org.hibernate.QueryException;
import org.hibernate.ScrollableResults; import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.dialect.Dialect; import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.pagination.LimitHandler;
import org.hibernate.dialect.pagination.LimitHelper;
import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.RowSelection;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.event.spi.EventSource; import org.hibernate.event.spi.EventSource;
@ -307,16 +313,35 @@ public class QueryLoader extends BasicLoader {
return lockModesArray; return lockModesArray;
} }
protected String applyLocks(String sql, LockOptions lockOptions, Dialect dialect) throws QueryException { @Override
protected String applyLocks(
String sql,
QueryParameters parameters,
Dialect dialect,
List<AfterLoadAction> afterLoadActions) throws QueryException {
// can't cache this stuff either (per-invocation) // can't cache this stuff either (per-invocation)
// we are given a map of user-alias -> lock mode // we are given a map of user-alias -> lock mode
// create a new map of sql-alias -> lock mode // create a new map of sql-alias -> lock mode
final LockOptions lockOptions = parameters.getLockOptions();
if ( lockOptions == null || if ( lockOptions == null ||
( lockOptions.getLockMode() == LockMode.NONE && lockOptions.getAliasLockCount() == 0 ) ) { ( lockOptions.getLockMode() == LockMode.NONE && lockOptions.getAliasLockCount() == 0 ) ) {
return sql; return sql;
} }
// user is request locking, lets see if we can apply locking directly to the SQL...
// some dialects wont allow locking with paging...
if ( shouldDelayLockingDueToPaging( sql, parameters, dialect, afterLoadActions ) ) {
return sql;
}
// there are other conditions we might want to add here, such as checking the result types etc
// but those are better served after we have redone the SQL generation to use ASTs.
// we need both the set of locks and the columns to reference in locks // we need both the set of locks and the columns to reference in locks
// as the ultimate output of this section... // as the ultimate output of this section...
final LockOptions locks = new LockOptions( lockOptions.getLockMode() ); final LockOptions locks = new LockOptions( lockOptions.getLockMode() );
@ -490,7 +515,7 @@ public class QueryLoader extends BasicLoader {
if ( queryParameters.isCallable() ) { if ( queryParameters.isCallable() ) {
throw new QueryException("iterate() not supported for callable statements"); throw new QueryException("iterate() not supported for callable statements");
} }
final ResultSet rs = executeQueryStatement( queryParameters, false, session ); final ResultSet rs = executeQueryStatement( queryParameters, false, Collections.<AfterLoadAction>emptyList(), session );
final PreparedStatement st = (PreparedStatement) rs.getStatement(); final PreparedStatement st = (PreparedStatement) rs.getStatement();
final Iterator result = new IteratorImpl( final Iterator result = new IteratorImpl(
rs, rs,

View File

@ -0,0 +1,61 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.locking.paging;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* @author Steve Ebersole
*/
@Entity
public class Door {
private Integer id;
private String name;
public Door() {
}
public Door(Integer id, String name) {
this.id = id;
this.name = name;
}
@Id
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,131 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2012, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.test.locking.paging;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.LockMode;
import org.hibernate.LockOptions;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
import static org.junit.Assert.assertEquals;
/**
* Test of paging and locking in combination
*
* @author Steve Ebersole
*/
@TestForIssue( jiraKey = "HHH-1168" )
public class PagingAndLockingTest extends BaseCoreFunctionalTestCase {
@Override
protected Class<?>[] getAnnotatedClasses() {
return new Class[] { Door.class };
}
@Before
public void createTestData() {
Session session = openSession();
session.beginTransaction();
session.save( new Door( 1, "Front" ) );
session.save( new Door( 2, "Back" ) );
session.save( new Door( 3, "Garage" ) );
session.save( new Door( 4, "French" ) );
session.getTransaction().commit();
session.close();
}
@After
public void deleteTestData() {
Session session = openSession();
session.beginTransaction();
session.createQuery( "delete Door" ).executeUpdate();
session.getTransaction().commit();
session.close();
}
@Test
public void testHql() {
Session session = openSession();
session.beginTransaction();
Query qry = session.createQuery( "from Door" );
qry.getLockOptions().setLockMode( LockMode.PESSIMISTIC_WRITE );
qry.setFirstResult( 2 );
qry.setMaxResults( 2 );
@SuppressWarnings("unchecked") List<Door> results = qry.list();
assertEquals( 2, results.size() );
for ( Door door : results ) {
assertEquals( LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode( door ) );
}
session.getTransaction().commit();
session.close();
}
@Test
public void testCriteria() {
Session session = openSession();
session.beginTransaction();
Criteria criteria = session.createCriteria( Door.class );
criteria.setLockMode( LockMode.PESSIMISTIC_WRITE );
criteria.setFirstResult( 2 );
criteria.setMaxResults( 2 );
@SuppressWarnings("unchecked") List<Door> results = criteria.list();
assertEquals( 2, results.size() );
for ( Door door : results ) {
assertEquals( LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode( door ) );
}
session.getTransaction().commit();
session.close();
}
@Test
// @Ignore( "Support for locking on native-sql queries not yet implemented" )
public void testNativeSql() {
Session session = openSession();
session.beginTransaction();
SQLQuery qry = session.createSQLQuery( "select * from door" );
qry.addRoot( "door", Door.class );
qry.getLockOptions().setLockMode( LockMode.PESSIMISTIC_WRITE );
qry.setFirstResult( 2 );
qry.setMaxResults( 2 );
@SuppressWarnings("unchecked") List<Door> results = qry.list();
assertEquals( 2, results.size() );
for ( Door door : results ) {
assertEquals( LockMode.PESSIMISTIC_WRITE, session.getCurrentLockMode( door ) );
}
session.getTransaction().commit();
session.close();
}
}

View File

@ -20,7 +20,7 @@
# Free Software Foundation, Inc. # Free Software Foundation, Inc.
# 51 Franklin Street, Fifth Floor # 51 Franklin Street, Fifth Floor
# Boston, MA 02110-1301 USA # Boston, MA 02110-1301 USA
#
hibernate.dialect org.hibernate.dialect.H2Dialect hibernate.dialect org.hibernate.dialect.H2Dialect
hibernate.connection.driver_class org.h2.Driver hibernate.connection.driver_class org.h2.Driver
hibernate.connection.url jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE hibernate.connection.url jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MVCC=TRUE

View File

@ -53,6 +53,7 @@ import org.hibernate.engine.query.spi.NamedParameterDescriptor;
import org.hibernate.engine.query.spi.OrdinalParameterDescriptor; import org.hibernate.engine.query.spi.OrdinalParameterDescriptor;
import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.hql.internal.QueryExecutionRequestException; import org.hibernate.hql.internal.QueryExecutionRequestException;
import org.hibernate.internal.SQLQueryImpl;
import org.hibernate.jpa.AvailableSettings; import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.HibernateQuery; import org.hibernate.jpa.HibernateQuery;
import org.hibernate.jpa.internal.util.ConfigurationHelper; import org.hibernate.jpa.internal.util.ConfigurationHelper;
@ -242,12 +243,13 @@ public class QueryImpl<X> extends AbstractQueryImpl<X> implements TypedQuery<X>,
@Override @Override
protected boolean canApplyLockModes() { protected boolean canApplyLockModes() {
return org.hibernate.internal.QueryImpl.class.isInstance( query ); return org.hibernate.internal.QueryImpl.class.isInstance( query )
|| SQLQueryImpl.class.isInstance( query );
} }
@Override @Override
protected void applyAliasSpecificLockMode(String alias, LockMode lockMode) { protected void applyAliasSpecificLockMode(String alias, LockMode lockMode) {
( (org.hibernate.internal.QueryImpl) query ).getLockOptions().setAliasSpecificLockMode( alias, lockMode ); query.getLockOptions().setAliasSpecificLockMode( alias, lockMode );
} }
/** /**
@ -631,18 +633,16 @@ public class QueryImpl<X> extends AbstractQueryImpl<X> implements TypedQuery<X>,
throw new IllegalStateException( "Not a JPAQL/Criteria query" ); throw new IllegalStateException( "Not a JPAQL/Criteria query" );
} }
this.jpaLockMode = lockModeType; this.jpaLockMode = lockModeType;
( (org.hibernate.internal.QueryImpl) query ).getLockOptions().setLockMode( query.getLockOptions().setLockMode( LockModeTypeHelper.getLockMode( lockModeType ) );
LockModeTypeHelper.getLockMode( lockModeType ) if ( getHints() != null && getHints().containsKey( AvailableSettings.LOCK_TIMEOUT ) ) {
); applyLockTimeout( ConfigurationHelper.getInteger( getHints().get( AvailableSettings.LOCK_TIMEOUT ) ) );
if ( getHints()!=null && getHints().containsKey( AvailableSettings.LOCK_TIMEOUT ) ) {
applyLockTimeout( ConfigurationHelper.getInteger( getHints().get( AvailableSettings.LOCK_TIMEOUT )) );
} }
return this; return this;
} }
@Override @Override
protected void applyLockTimeout(int timeout) { protected void applyLockTimeout(int timeout) {
( (org.hibernate.internal.QueryImpl) query ).getLockOptions().setTimeOut( timeout ); query.getLockOptions().setTimeOut( timeout );
} }
@Override @Override

View File

@ -82,6 +82,32 @@ public class QueryLockingTest extends BaseEntityManagerFunctionalTestCase {
em.close(); em.close();
} }
@Test
public void testNativeSql() {
EntityManager em = getOrCreateEntityManager();
em.getTransaction().begin();
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" ) );
// 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" ) );
query.setHint( AvailableSettings.ALIAS_SPECIFIC_LOCK_MODE+".l", LockModeType.PESSIMISTIC_WRITE );
assertEquals( LockMode.OPTIMISTIC, hibernateQuery.getLockOptions().getLockMode() );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getAliasSpecificLockMode( "l" ) );
assertEquals( LockMode.PESSIMISTIC_WRITE, hibernateQuery.getLockOptions().getEffectiveLockMode( "l" ) );
em.getTransaction().commit();
em.close();
}
@Test @Test
public void testPessimisticForcedIncrementOverall() { public void testPessimisticForcedIncrementOverall() {
EntityManager em = getOrCreateEntityManager(); EntityManager em = getOrCreateEntityManager();