Abstract superclass of object loading (and querying) strategies. This class implements
useful common functionality that concrete loaders delegate to. It is not intended that this
functionality would be directly accessed by client code. (Hence, all methods of this class
are declared
s that may be loaded by it.
The present implementation is able to load any number of columns of entities and at most
one collection role per query.
| Method from org.hibernate.loader.Loader Detail: |
protected String applyLocks(String sql,
Map lockModes,
Dialect dialect) throws HibernateException {
return sql;
}
Append FOR UPDATE OF clause, if necessary. This
empty superclass implementation merely returns its first
argument. |
protected void autoDiscoverTypes(ResultSet rs) {
throw new AssertionFailure("Auto discover types not supported in this loader");
}
|
protected int bindNamedParameters(PreparedStatement statement,
Map namedParams,
int startIndex,
SessionImplementor session) throws HibernateException, SQLException {
if ( namedParams != null ) {
// assumes that types are all of span 1
Iterator iter = namedParams.entrySet().iterator();
int result = 0;
while ( iter.hasNext() ) {
Map.Entry e = ( Map.Entry ) iter.next();
String name = ( String ) e.getKey();
TypedValue typedval = ( TypedValue ) e.getValue();
int[] locs = getNamedParameterLocs( name );
for ( int i = 0; i < locs.length; i++ ) {
if ( log.isDebugEnabled() ) {
log.debug(
"bindNamedParameters() " +
typedval.getValue() + " - > " + name +
" [" + ( locs[i] + startIndex ) + "]"
);
}
typedval.getType().nullSafeSet( statement, typedval.getValue(), locs[i] + startIndex, session );
}
result += locs.length;
}
return result;
}
else {
return 0;
}
}
Bind named parameters to the JDBC prepared statement.
This is a generic implementation, the problem being that in the
general case we do not know enough information about the named
parameters to perform this in a complete manner here. Thus this
is generally overridden on subclasses allowing named parameters to
apply the specific behavior. The most usual limitation here is that
we need to assume the type span is always one... |
protected int bindParameterValues(PreparedStatement statement,
QueryParameters queryParameters,
int startIndex,
SessionImplementor session) throws SQLException {
int span = 0;
span += bindPositionalParameters( statement, queryParameters, startIndex, session );
span += bindNamedParameters( statement, queryParameters.getNamedParameters(), startIndex + span, session );
return span;
}
Bind all parameter values into the prepared statement in preparation
for execution. |
protected int bindPositionalParameters(PreparedStatement statement,
QueryParameters queryParameters,
int startIndex,
SessionImplementor session) throws HibernateException, SQLException {
final Object[] values = queryParameters.getFilteredPositionalParameterValues();
final Type[] types = queryParameters.getFilteredPositionalParameterTypes();
int span = 0;
for ( int i = 0; i < values.length; i++ ) {
types[i].nullSafeSet( statement, values[i], startIndex + span, session );
span += types[i].getColumnSpan( getFactory() );
}
return span;
}
Bind positional parameter values to the JDBC prepared statement.
Postional parameters are those specified by JDBC-style ? parameters
in the source query. It is (currently) expected that these come
before any named parameters in the source query. |
protected void checkScrollability() throws HibernateException {
// Allows various loaders (ok mainly the QueryLoader :) to check
// whether scrolling of their result set should be allowed.
//
// By default it is allowed.
return;
}
Check whether the current loader can support returning ScrollableResults. |
protected List doList(SessionImplementor session,
QueryParameters queryParameters) throws HibernateException {
final boolean stats = getFactory().getStatistics().isStatisticsEnabled();
long startTime = 0;
if ( stats ) startTime = System.currentTimeMillis();
List result;
try {
result = doQueryAndInitializeNonLazyCollections( session, queryParameters, true );
}
catch ( SQLException sqle ) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not execute query",
getSQLString()
);
}
if ( stats ) {
getFactory().getStatisticsImplementor().queryExecuted(
getQueryIdentifier(),
result.size(),
System.currentTimeMillis() - startTime
);
}
return result;
}
Actually execute a query, ignoring the query cache |
protected String[] getAliases() {
return null;
}
Get the SQL table aliases of entities whose
associations are subselect-loadable, returning
null if this loader does not support subselect
loading |
abstract protected CollectionAliases[] getCollectionAliases()
|
protected int[] getCollectionOwners() {
return null;
}
Get the index of the entity that owns the collection, or -1
if there is no owner in the query results (ie. in the case of a
collection initializer) or no collection. |
protected CollectionPersister[] getCollectionPersisters() {
return null;
}
An (optional) persister for a collection to be initialized; only
collection loaders return a non-null value |
abstract protected EntityAliases[] getEntityAliases()
Get the result set descriptor |
protected boolean[] getEntityEagerPropertyFetches() {
return null;
}
An array indicating whether the entities have eager property fetching
enabled. |
abstract protected Loadable[] getEntityPersisters()
An array of persisters of entity classes contained in each row of results;
implemented by all subclasses |
public final SessionFactoryImplementor getFactory() {
return factory;
}
|
abstract protected LockMode[] getLockModes(Map lockModes)
What lock mode does this load entities with? |
public int[] getNamedParameterLocs(String name) {
throw new AssertionFailure("no named parameters");
}
|
protected EntityType[] getOwnerAssociationTypes() {
return null;
}
An array of the owner types corresponding to the #getOwners()
returns. Indices indicating no owner would be null here. |
protected int[] getOwners() {
return null;
}
An array of indexes of the entity that owns a one-to-one association
to the entity at the given index (-1 if there is no "owner"). The
indexes contained here are relative to the result of
#getEntityPersisters . |
protected String getQueryIdentifier() {
return null;
}
Identifies the query for statistics reporting, if null,
no statistics will be reported |
protected Object getResultColumnOrRow(Object[] row,
ResultTransformer transformer,
ResultSet rs,
SessionImplementor session) throws HibernateException, SQLException {
return row;
}
Get the actual object that is returned in the user-visible result list.
This empty implementation merely returns its first argument. This is
overridden by some subclasses. |
protected List getResultList(List results,
ResultTransformer resultTransformer) throws QueryException {
return results;
}
|
protected final ResultSet getResultSet(PreparedStatement st,
boolean autodiscovertypes,
boolean callable,
RowSelection selection,
SessionImplementor session) throws HibernateException, SQLException {
ResultSet rs = null;
try {
Dialect dialect = getFactory().getDialect();
if (callable) {
rs = session.getBatcher().getResultSet( (CallableStatement) st, dialect );
}
else {
rs = session.getBatcher().getResultSet( st );
}
rs = wrapResultSetIfEnabled( rs , session );
if ( !dialect.supportsLimitOffset() || !useLimit( selection, dialect ) ) {
advance( rs, selection );
}
if ( autodiscovertypes ) {
autoDiscoverTypes( rs );
}
return rs;
}
catch ( SQLException sqle ) {
session.getBatcher().closeQueryStatement( st, rs );
throw sqle;
}
}
Fetch a PreparedStatement, call setMaxRows and then execute it,
advance to the first result and return an SQL ResultSet |
abstract protected String getSQLString()
The SQL query string to be called; implemented by all subclasses |
protected boolean hasSubselectLoadableCollections() {
final Loadable[] loadables = getEntityPersisters();
for (int i=0; i< loadables.length; i++ ) {
if ( loadables[i].hasSubselectLoadableCollections() ) return true;
}
return false;
}
|
protected boolean isSingleRowLoader() {
return false;
}
Return false is this loader is a batch entity loader |
protected boolean isSubselectLoadingEnabled() {
return false;
}
|
protected List list(SessionImplementor session,
QueryParameters queryParameters,
Set querySpaces,
Type[] resultTypes) throws HibernateException {
final boolean cacheable = factory.getSettings().isQueryCacheEnabled() &&
queryParameters.isCacheable();
if ( cacheable ) {
return listUsingQueryCache( session, queryParameters, querySpaces, resultTypes );
}
else {
return listIgnoreQueryCache( session, queryParameters );
}
}
Return the query results, using the query cache, called
by subclasses that implement cacheable queries |
public final void loadCollection(SessionImplementor session,
Serializable id,
Type type) throws HibernateException {
if ( log.isDebugEnabled() ) {
log.debug(
"loading collection: "+
MessageHelper.collectionInfoString( getCollectionPersisters()[0], id, getFactory() )
);
}
Serializable[] ids = new Serializable[]{id};
try {
doQueryAndInitializeNonLazyCollections(
session,
new QueryParameters( new Type[]{type}, ids, ids ),
true
);
}
catch ( SQLException sqle ) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not initialize a collection: " +
MessageHelper.collectionInfoString( getCollectionPersisters()[0], id, getFactory() ),
getSQLString()
);
}
log.debug("done loading collection");
}
Called by subclasses that initialize collections |
public final void loadCollectionBatch(SessionImplementor session,
Serializable[] ids,
Type type) throws HibernateException {
if ( log.isDebugEnabled() ) {
log.debug(
"batch loading collection: "+
MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() )
);
}
Type[] idTypes = new Type[ids.length];
Arrays.fill( idTypes, type );
try {
doQueryAndInitializeNonLazyCollections(
session,
new QueryParameters( idTypes, ids, ids ),
true
);
}
catch ( SQLException sqle ) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not initialize a collection batch: " +
MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() ),
getSQLString()
);
}
log.debug("done batch load");
}
Called by wrappers that batch initialize collections |
protected final void loadCollectionSubselect(SessionImplementor session,
Serializable[] ids,
Object[] parameterValues,
Type[] parameterTypes,
Map namedParameters,
Type type) throws HibernateException {
Type[] idTypes = new Type[ids.length];
Arrays.fill( idTypes, type );
try {
doQueryAndInitializeNonLazyCollections( session,
new QueryParameters( parameterTypes, parameterValues, namedParameters, ids ),
true
);
}
catch ( SQLException sqle ) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not load collection by subselect: " +
MessageHelper.collectionInfoString( getCollectionPersisters()[0], ids, getFactory() ),
getSQLString()
);
}
}
Called by subclasses that batch initialize collections |
protected final List loadEntity(SessionImplementor session,
Object key,
Object index,
Type keyType,
Type indexType,
EntityPersister persister) throws HibernateException {
if ( log.isDebugEnabled() ) {
log.debug( "loading collection element by index" );
}
List result;
try {
result = doQueryAndInitializeNonLazyCollections(
session,
new QueryParameters(
new Type[] { keyType, indexType },
new Object[] { key, index }
),
false
);
}
catch ( SQLException sqle ) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not collection element by index",
getSQLString()
);
}
log.debug("done entity load");
return result;
}
Called by subclasses that load entities |
protected final List loadEntity(SessionImplementor session,
Object id,
Type identifierType,
Object optionalObject,
String optionalEntityName,
Serializable optionalIdentifier,
EntityPersister persister) throws HibernateException {
if ( log.isDebugEnabled() ) {
log.debug(
"loading entity: " +
MessageHelper.infoString( persister, id, identifierType, getFactory() )
);
}
List result;
try {
result = doQueryAndInitializeNonLazyCollections(
session,
new QueryParameters(
new Type[] { identifierType },
new Object[] { id },
optionalObject,
optionalEntityName,
optionalIdentifier
),
false
);
}
catch ( SQLException sqle ) {
final Loadable[] persisters = getEntityPersisters();
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not load an entity: " +
MessageHelper.infoString( persisters[persisters.length-1], id, identifierType, getFactory() ),
getSQLString()
);
}
log.debug("done entity load");
return result;
}
Called by subclasses that load entities |
public final List loadEntityBatch(SessionImplementor session,
Serializable[] ids,
Type idType,
Object optionalObject,
String optionalEntityName,
Serializable optionalId,
EntityPersister persister) throws HibernateException {
if ( log.isDebugEnabled() ) {
log.debug(
"batch loading entity: " +
MessageHelper.infoString(persister, ids, getFactory() )
);
}
Type[] types = new Type[ids.length];
Arrays.fill( types, idType );
List result;
try {
result = doQueryAndInitializeNonLazyCollections(
session,
new QueryParameters( types, ids, optionalObject, optionalEntityName, optionalId ),
false
);
}
catch ( SQLException sqle ) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not load an entity batch: " +
MessageHelper.infoString( getEntityPersisters()[0], ids, getFactory() ),
getSQLString()
);
}
log.debug("done entity batch load");
return result;
}
Called by wrappers that batch load entities |
public Object loadSequentialRowsForward(ResultSet resultSet,
SessionImplementor session,
QueryParameters queryParameters,
boolean returnProxies) throws HibernateException {
// note that for sequential scrolling, we make the assumption that
// the first persister element is the "root entity"
try {
if ( resultSet.isAfterLast() ) {
// don't even bother trying to read further
return null;
}
if ( resultSet.isBeforeFirst() ) {
resultSet.next();
}
// We call getKeyFromResultSet() here so that we can know the
// key value upon which to perform the breaking logic. However,
// it is also then called from getRowFromResultSet() which is certainly
// not the most efficient. But the call here is needed, and there
// currently is no other way without refactoring of the doQuery()/getRowFromResultSet()
// methods
final EntityKey currentKey = getKeyFromResultSet(
0,
getEntityPersisters()[0],
null,
resultSet,
session
);
return sequentialLoad( resultSet, session, queryParameters, returnProxies, currentKey );
}
catch ( SQLException sqle ) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not perform sequential read of results (forward)",
getSQLString()
);
}
}
Loads a single logical row from the result set moving forward. This is the
processing used from the ScrollableResults where there were collection fetches
encountered; thus a single logical row may have multiple rows in the underlying
result set. |
public Object loadSequentialRowsReverse(ResultSet resultSet,
SessionImplementor session,
QueryParameters queryParameters,
boolean returnProxies,
boolean isLogicallyAfterLast) throws HibernateException {
// note that for sequential scrolling, we make the assumption that
// the first persister element is the "root entity"
try {
if ( resultSet.isFirst() ) {
// don't even bother trying to read any further
return null;
}
EntityKey keyToRead = null;
// This check is needed since processing leaves the cursor
// after the last physical row for the current logical row;
// thus if we are after the last physical row, this might be
// caused by either:
// 1) scrolling to the last logical row
// 2) scrolling past the last logical row
// In the latter scenario, the previous logical row
// really is the last logical row.
//
// In all other cases, we should process back two
// logical records (the current logic row, plus the
// previous logical row).
if ( resultSet.isAfterLast() && isLogicallyAfterLast ) {
// position cursor to the last row
resultSet.last();
keyToRead = getKeyFromResultSet(
0,
getEntityPersisters()[0],
null,
resultSet,
session
);
}
else {
// Since the result set cursor is always left at the first
// physical row after the "last processed", we need to jump
// back one position to get the key value we are interested
// in skipping
resultSet.previous();
// sequentially read the result set in reverse until we recognize
// a change in the key value. At that point, we are pointed at
// the last physical sequential row for the logical row in which
// we are interested in processing
boolean firstPass = true;
final EntityKey lastKey = getKeyFromResultSet(
0,
getEntityPersisters()[0],
null,
resultSet,
session
);
while ( resultSet.previous() ) {
EntityKey checkKey = getKeyFromResultSet(
0,
getEntityPersisters()[0],
null,
resultSet,
session
);
if ( firstPass ) {
firstPass = false;
keyToRead = checkKey;
}
if ( !lastKey.equals( checkKey ) ) {
break;
}
}
}
// Read backwards until we read past the first physical sequential
// row with the key we are interested in loading
while ( resultSet.previous() ) {
EntityKey checkKey = getKeyFromResultSet(
0,
getEntityPersisters()[0],
null,
resultSet,
session
);
if ( !keyToRead.equals( checkKey ) ) {
break;
}
}
// Finally, read ahead one row to position result set cursor
// at the first physical row we are interested in loading
resultSet.next();
// and perform the load
return sequentialLoad( resultSet, session, queryParameters, returnProxies, keyToRead );
}
catch ( SQLException sqle ) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not perform sequential read of results (forward)",
getSQLString()
);
}
}
Loads a single logical row from the result set moving forward. This is the
processing used from the ScrollableResults where there were collection fetches
encountered; thus a single logical row may have multiple rows in the underlying
result set. |
public Object loadSingleRow(ResultSet resultSet,
SessionImplementor session,
QueryParameters queryParameters,
boolean returnProxies) throws HibernateException {
final int entitySpan = getEntityPersisters().length;
final List hydratedObjects = entitySpan == 0 ?
null : new ArrayList( entitySpan );
final Object result;
try {
result = getRowFromResultSet(
resultSet,
session,
queryParameters,
getLockModes( queryParameters.getLockModes() ),
null,
hydratedObjects,
new EntityKey[entitySpan],
returnProxies
);
}
catch ( SQLException sqle ) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not read next row of results",
getSQLString()
);
}
initializeEntitiesAndCollections(
hydratedObjects,
resultSet,
session,
queryParameters.isReadOnly()
);
session.getPersistenceContext().initializeNonLazyCollections();
return result;
}
Loads a single row from the result set. This is the processing used from the
ScrollableResults where no collection fetches were encountered. |
protected boolean needsFetchingScroll() {
return false;
}
Does the result set to be scrolled contain collection fetches? |
protected void postInstantiate() {
}
Calculate and cache select-clause suffixes. Must be
called by subclasses after instantiation. |
protected final PreparedStatement prepareQueryStatement(QueryParameters queryParameters,
boolean scroll,
SessionImplementor session) throws HibernateException, SQLException {
queryParameters.processFilters( getSQLString(), session );
String sql = queryParameters.getFilteredSQL();
final Dialect dialect = getFactory().getDialect();
final RowSelection selection = queryParameters.getRowSelection();
boolean useLimit = useLimit( selection, dialect );
boolean hasFirstRow = getFirstRow( selection ) > 0;
boolean useOffset = hasFirstRow && useLimit && dialect.supportsLimitOffset();
boolean callable = queryParameters.isCallable();
boolean useScrollableResultSetToSkip = hasFirstRow &&
!useOffset &&
getFactory().getSettings().isScrollableResultSetsEnabled();
ScrollMode scrollMode = scroll ? queryParameters.getScrollMode() : ScrollMode.SCROLL_INSENSITIVE;
if ( useLimit ) {
sql = dialect.getLimitString(
sql.trim(), //use of trim() here is ugly?
useOffset ? getFirstRow(selection) : 0,
getMaxOrLimit(selection, dialect)
);
}
sql = preprocessSQL( sql, queryParameters, dialect );
PreparedStatement st = null;
if (callable) {
st = session.getBatcher()
.prepareCallableQueryStatement( sql, scroll || useScrollableResultSetToSkip, scrollMode );
}
else {
st = session.getBatcher()
.prepareQueryStatement( sql, scroll || useScrollableResultSetToSkip, scrollMode );
}
try {
int col = 1;
//TODO: can we limit stored procedures ?!
if ( useLimit && dialect.bindLimitParametersFirst() ) {
col += bindLimitParameters( st, col, selection );
}
if (callable) {
col = dialect.registerResultSetOutParameter( (CallableStatement)st, col );
}
col += bindParameterValues( st, queryParameters, col, session );
if ( useLimit && !dialect.bindLimitParametersFirst() ) {
col += bindLimitParameters( st, col, selection );
}
if ( !useLimit ) {
setMaxRows( st, selection );
}
if ( selection != null ) {
if ( selection.getTimeout() != null ) {
st.setQueryTimeout( selection.getTimeout().intValue() );
}
if ( selection.getFetchSize() != null ) {
st.setFetchSize( selection.getFetchSize().intValue() );
}
}
}
catch ( SQLException sqle ) {
session.getBatcher().closeQueryStatement( st, null );
throw sqle;
}
catch ( HibernateException he ) {
session.getBatcher().closeQueryStatement( st, null );
throw he;
}
return st;
}
Obtain a PreparedStatement with all parameters pre-bound.
Bind JDBC-style ? parameters, named parameters, and
limit parameters. |
protected String preprocessSQL(String sql,
QueryParameters parameters,
Dialect dialect) throws HibernateException {
sql = applyLocks( sql, parameters.getLockModes(), dialect );
return getFactory().getSettings().isCommentsEnabled() ?
prependComment( sql, parameters ) : sql;
}
Modify the SQL, adding lock hints and comments, if necessary |
protected ScrollableResults scroll(QueryParameters queryParameters,
Type[] returnTypes,
HolderInstantiator holderInstantiator,
SessionImplementor session) throws HibernateException {
checkScrollability();
final boolean stats = getQueryIdentifier() != null &&
getFactory().getStatistics().isStatisticsEnabled();
long startTime = 0;
if ( stats ) startTime = System.currentTimeMillis();
try {
PreparedStatement st = prepareQueryStatement( queryParameters, true, session );
ResultSet rs = getResultSet(st, queryParameters.hasAutoDiscoverScalarTypes(), queryParameters.isCallable(), queryParameters.getRowSelection(), session);
if ( stats ) {
getFactory().getStatisticsImplementor().queryExecuted(
getQueryIdentifier(),
0,
System.currentTimeMillis() - startTime
);
}
if ( needsFetchingScroll() ) {
return new FetchingScrollableResultsImpl(
rs,
st,
session,
this,
queryParameters,
returnTypes,
holderInstantiator
);
}
else {
return new ScrollableResultsImpl(
rs,
st,
session,
this,
queryParameters,
returnTypes,
holderInstantiator
);
}
}
catch ( SQLException sqle ) {
throw JDBCExceptionHelper.convert(
factory.getSQLExceptionConverter(),
sqle,
"could not execute query using scroll",
getSQLString()
);
}
}
Return the query results, as an instance of ScrollableResults |
public String toString() {
return getClass().getName() + '(" + getSQLString() + ')";
}
|
protected boolean upgradeLocks() {
return false;
}
Does this query return objects that might be already cached
by the session, whose lock mode may need upgrading |