org.hibernate.loader.plan.exec.process.internal.EntityReferenceInitializerImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of hibernate-core Show documentation
Show all versions of hibernate-core Show documentation
Hibernate's core ORM functionality
/*
* 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 .
*/
package org.hibernate.loader.plan.exec.process.internal;
import java.io.Serializable;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.StaleObjectStateException;
import org.hibernate.WrongClassException;
import org.hibernate.bytecode.enhance.spi.interceptor.BytecodeLazyAttributeInterceptor;
import org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor;
import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
import org.hibernate.engine.internal.TwoPhaseLoad;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.EntityKey;
import org.hibernate.engine.spi.EntityUniqueKey;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreLogging;
import org.hibernate.loader.EntityAliases;
import org.hibernate.loader.plan.exec.process.spi.EntityReferenceInitializer;
import org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext;
import org.hibernate.loader.plan.exec.spi.EntityReferenceAliases;
import org.hibernate.loader.plan.spi.EntityFetch;
import org.hibernate.loader.plan.spi.EntityReference;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Loadable;
import org.hibernate.persister.entity.UniqueKeyLoadable;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.stat.spi.StatisticsImplementor;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
import org.hibernate.type.VersionType;
import org.jboss.logging.Logger;
import static org.hibernate.loader.plan.exec.process.spi.ResultSetProcessingContext.EntityReferenceProcessingState;
/**
* @author Steve Ebersole
*/
public class EntityReferenceInitializerImpl implements EntityReferenceInitializer {
private static final Logger log = CoreLogging.logger( EntityReferenceInitializerImpl.class );
private final EntityReference entityReference;
private final EntityReferenceAliases entityReferenceAliases;
private final boolean isReturn;
public EntityReferenceInitializerImpl(
EntityReference entityReference,
EntityReferenceAliases entityReferenceAliases) {
this( entityReference, entityReferenceAliases, false );
}
public EntityReferenceInitializerImpl(
EntityReference entityReference,
EntityReferenceAliases entityReferenceAliases,
boolean isRoot) {
this.entityReference = entityReference;
this.entityReferenceAliases = entityReferenceAliases;
isReturn = isRoot;
}
@Override
public EntityReference getEntityReference() {
return entityReference;
}
@Override
public void hydrateIdentifier(ResultSet resultSet, ResultSetProcessingContextImpl context) throws SQLException {
final EntityReferenceProcessingState processingState = context.getProcessingState( entityReference );
// get any previously registered identifier hydrated-state
Object identifierHydratedForm = processingState.getIdentifierHydratedForm();
if ( identifierHydratedForm == null ) {
// if there is none, read it from the result set
identifierHydratedForm = readIdentifierHydratedState( resultSet, context );
// broadcast the fact that a hydrated identifier value just became associated with
// this entity reference
processingState.registerIdentifierHydratedForm( identifierHydratedForm );
}
}
/**
* Read the identifier state for the entity reference for the currently processing row in the ResultSet
*
* @param resultSet The ResultSet being processed
* @param context The processing context
*
* @return The hydrated state
*
* @throws java.sql.SQLException Indicates a problem accessing the ResultSet
*/
private Object readIdentifierHydratedState(ResultSet resultSet, ResultSetProcessingContext context)
throws SQLException {
try {
return entityReference.getEntityPersister().getIdentifierType().hydrate(
resultSet,
entityReferenceAliases.getColumnAliases().getSuffixedKeyAliases(),
context.getSession(),
null
);
}
catch (Exception e) {
throw new HibernateException(
"Encountered problem trying to hydrate identifier for entity ["
+ entityReference.getEntityPersister() + "]",
e
);
}
}
@Override
public void resolveEntityKey(ResultSet resultSet, ResultSetProcessingContextImpl context) {
final EntityReferenceProcessingState processingState = context.getProcessingState( entityReference );
// see if we already have an EntityKey associated with this EntityReference in the processing state.
// if we do, this should have come from the optional entity identifier...
final EntityKey entityKey = processingState.getEntityKey();
if ( entityKey != null ) {
log.debugf(
"On call to EntityIdentifierReaderImpl#resolve, EntityKey was already known; " +
"should only happen on root returns with an optional identifier specified"
);
return;
}
// Look for the hydrated form
final Object identifierHydratedForm = processingState.getIdentifierHydratedForm();
if ( identifierHydratedForm == null ) {
// we need to register the missing identifier, but that happens later after all readers have had a chance
// to resolve its EntityKey
return;
}
final Type identifierType = entityReference.getEntityPersister().getIdentifierType();
final Serializable resolvedId = (Serializable) identifierType.resolve(
identifierHydratedForm,
context.getSession(),
null
);
if ( resolvedId != null ) {
processingState.registerEntityKey(
context.getSession().generateEntityKey( resolvedId, entityReference.getEntityPersister() )
);
}
}
@Override
public void hydrateEntityState(ResultSet resultSet, ResultSetProcessingContextImpl context) {
final EntityReferenceProcessingState processingState = context.getProcessingState( entityReference );
// If there is no identifier for this entity reference for this row, nothing to do
if ( processingState.isMissingIdentifier() ) {
handleMissingIdentifier( context );
return;
}
// make sure we have the EntityKey
final EntityKey entityKey = processingState.getEntityKey();
if ( entityKey == null ) {
handleMissingIdentifier( context );
return;
}
// Have we already hydrated this entity's state?
if ( processingState.getEntityInstance() != null ) {
return;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// In getting here, we know that:
// 1) We need to hydrate the entity state
// 2) We have a valid EntityKey for the entity
// see if we have an existing entry in the session for this EntityKey
final Object existing = context.getSession().getEntityUsingInterceptor( entityKey );
if ( existing != null ) {
// It is previously associated with the Session, perform some checks
if ( ! entityReference.getEntityPersister().isInstance( existing ) ) {
throw new WrongClassException(
"loaded object was of wrong class " + existing.getClass(),
entityKey.getIdentifier(),
entityReference.getEntityPersister().getEntityName()
);
}
checkVersion( resultSet, context, entityKey, existing );
// use the existing association as the hydrated state
processingState.registerEntityInstance( existing );
//context.registerHydratedEntity( entityReference, entityKey, existing );
// see if the entity is enhanced and is being used as a "proxy" (is fully uninitialized)
final BytecodeEnhancementMetadata enhancementMetadata = entityReference.getEntityPersister()
.getEntityMetamodel()
.getBytecodeEnhancementMetadata();
if ( enhancementMetadata.isEnhancedForLazyLoading() ) {
final BytecodeLazyAttributeInterceptor interceptor = enhancementMetadata.extractLazyInterceptor( existing );
if ( interceptor instanceof EnhancementAsProxyLazinessInterceptor ) {
final LockMode requestedLockMode = context.resolveLockMode( entityReference );
final LockMode lockModeToAcquire = requestedLockMode == LockMode.NONE
? LockMode.READ
: requestedLockMode;
final EnhancementAsProxyLazinessInterceptor enhancementAsProxyLazinessInterceptor = (EnhancementAsProxyLazinessInterceptor) interceptor;
if ( enhancementAsProxyLazinessInterceptor.isInitializing() ) {
return;
}
enhancementAsProxyLazinessInterceptor.setInitializing();
loadFromResultSet(
resultSet,
context,
existing,
getConcreteEntityTypeName( resultSet, context, entityKey ),
entityKey,
lockModeToAcquire
);
}
}
return;
}
// Otherwise, we need to load it from the ResultSet...
// determine which entity instance to use. Either the supplied one, or instantiate one
Object entityInstance = null;
// If an "optional" instance with an EntityKey equal to entityKey is available
// in the context, then use that instance.
if ( isReturn && context.getQueryParameters().getOptionalObject() != null ) {
final EntityKey optionalEntityKey = ResultSetProcessorHelper.getOptionalObjectKey(
context.getQueryParameters(),
context.getSession()
);
if ( optionalEntityKey != null && optionalEntityKey.equals( entityKey ) ) {
entityInstance = context.getQueryParameters().getOptionalObject();
}
}
final String concreteEntityTypeName = getConcreteEntityTypeName( resultSet, context, entityKey );
if ( entityInstance == null ) {
entityInstance = context.getSession().instantiate( concreteEntityTypeName, entityKey.getIdentifier() );
}
processingState.registerEntityInstance( entityInstance );
// need to hydrate it.
// grab its state from the ResultSet and keep it in the Session
// (but don't yet initialize the object itself)
// note that we acquire LockMode.READ even if it was not requested
log.trace( "hydrating entity state" );
final LockMode requestedLockMode = context.resolveLockMode( entityReference );
final LockMode lockModeToAcquire = requestedLockMode == LockMode.NONE
? LockMode.READ
: requestedLockMode;
loadFromResultSet(
resultSet,
context,
entityInstance,
concreteEntityTypeName,
entityKey,
lockModeToAcquire
);
}
private void handleMissingIdentifier(ResultSetProcessingContext context) {
if ( EntityFetch.class.isInstance( entityReference ) ) {
final EntityFetch fetch = (EntityFetch) entityReference;
final EntityType fetchedType = fetch.getFetchedType();
if ( ! fetchedType.isOneToOne() ) {
return;
}
final EntityReferenceProcessingState fetchOwnerState = context.getOwnerProcessingState( fetch );
if ( fetchOwnerState == null ) {
throw new IllegalStateException( "Could not locate fetch owner state" );
}
final EntityKey ownerEntityKey = fetchOwnerState.getEntityKey();
if ( ownerEntityKey != null ) {
context.getSession().getPersistenceContextInternal().addNullProperty(
ownerEntityKey,
fetchedType.getPropertyName()
);
}
}
}
private void loadFromResultSet(
ResultSet resultSet,
ResultSetProcessingContext context,
Object entityInstance,
String concreteEntityTypeName,
EntityKey entityKey,
LockMode lockModeToAcquire) {
final Serializable id = entityKey.getIdentifier();
// Get the persister for the _subclass_
final SharedSessionContractImplementor session = context.getSession();
final Loadable concreteEntityPersister = (Loadable) session.getFactory().getMetamodel().entityPersister( concreteEntityTypeName );
if ( log.isTraceEnabled() ) {
log.tracev(
"Initializing object from ResultSet: {0}",
MessageHelper.infoString(
concreteEntityPersister,
id,
session.getFactory()
)
);
}
// add temp entry so that the next step is circular-reference
// safe - only needed because some types don't take proper
// advantage of two-phase-load (esp. components)
TwoPhaseLoad.addUninitializedEntity(
entityKey,
entityInstance,
concreteEntityPersister,
lockModeToAcquire,
session
);
final EntityPersister rootEntityPersister = session.getFactory().getMetamodel().entityPersister(
concreteEntityPersister.getRootEntityName()
);
final Object[] values;
try {
values = concreteEntityPersister.hydrate(
resultSet,
id,
entityInstance,
(Loadable) entityReference.getEntityPersister(),
concreteEntityPersister == rootEntityPersister
? entityReferenceAliases.getColumnAliases().getSuffixedPropertyAliases()
: entityReferenceAliases.getColumnAliases().getSuffixedPropertyAliases( concreteEntityPersister ),
context.getLoadPlan().areLazyAttributesForceFetched(),
session
);
context.getProcessingState( entityReference ).registerHydratedState( values );
}
catch (SQLException e) {
throw session.getFactory().getServiceRegistry().getService( JdbcServices.class ).getSqlExceptionHelper().convert(
e,
"Could not read entity state from ResultSet : " + entityKey
);
}
final Object rowId;
try {
rowId = concreteEntityPersister.hasRowId()
? resultSet.getObject( entityReferenceAliases.getColumnAliases().getRowIdAlias() )
: null;
if ( rowId != null && log.isTraceEnabled() ) {
log.tracev(
"extracted ROWID value: {0}",
rowId
);
}
}
catch (SQLException e) {
throw session.getFactory().getServiceRegistry().getService( JdbcServices.class ).getSqlExceptionHelper().convert(
e,
"Could not read entity row-id from ResultSet : " + entityKey
);
}
final EntityType entityType = EntityFetch.class.isInstance( entityReference )
? ( (EntityFetch) entityReference ).getFetchedType()
: entityReference.getEntityPersister().getEntityMetamodel().getEntityType();
if ( entityType != null ) {
String ukName = entityType.getRHSUniqueKeyPropertyName();
if ( ukName != null ) {
final int index = ( (UniqueKeyLoadable) concreteEntityPersister ).getPropertyIndex( ukName );
final Type type = concreteEntityPersister.getPropertyTypes()[index];
// polymorphism not really handled completely correctly,
// perhaps...well, actually its ok, assuming that the
// entity name used in the lookup is the same as the
// the one used here, which it will be
EntityUniqueKey euk = new EntityUniqueKey(
entityReference.getEntityPersister().getEntityName(),
ukName,
type.semiResolve( values[index], session, entityInstance ),
type,
concreteEntityPersister.getEntityMode(),
session.getFactory()
);
session.getPersistenceContextInternal().addEntity( euk, entityInstance );
}
}
TwoPhaseLoad.postHydrate(
concreteEntityPersister,
id,
values,
rowId,
entityInstance,
lockModeToAcquire,
session
);
context.registerHydratedEntity( entityReference, entityKey, entityInstance );
}
private String getConcreteEntityTypeName(
ResultSet resultSet,
ResultSetProcessingContext context,
EntityKey entityKey) {
final Loadable loadable = (Loadable) entityReference.getEntityPersister();
if ( ! loadable.hasSubclasses() ) {
return entityReference.getEntityPersister().getEntityName();
}
final Object discriminatorValue;
try {
discriminatorValue = loadable.getDiscriminatorType().nullSafeGet(
resultSet,
entityReferenceAliases.getColumnAliases().getSuffixedDiscriminatorAlias(),
context.getSession(),
null
);
}
catch (SQLException e) {
throw context.getSession().getFactory().getServiceRegistry().getService( JdbcServices.class ).getSqlExceptionHelper().convert(
e,
"Could not read discriminator value from ResultSet"
);
}
final String result = loadable.getSubclassForDiscriminatorValue( discriminatorValue );
if ( result == null ) {
// whoops! we got an instance of another class hierarchy branch
throw new WrongClassException(
"Discriminator: " + discriminatorValue,
entityKey.getIdentifier(),
entityReference.getEntityPersister().getEntityName()
);
}
return result;
}
private void checkVersion(
ResultSet resultSet,
ResultSetProcessingContext context,
EntityKey entityKey,
Object existing) {
final LockMode requestedLockMode = context.resolveLockMode( entityReference );
if ( requestedLockMode != LockMode.NONE ) {
final SharedSessionContractImplementor session = context.getSession();
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final LockMode currentLockMode = persistenceContext.getEntry( existing ).getLockMode();
final boolean isVersionCheckNeeded = entityReference.getEntityPersister().isVersioned()
&& currentLockMode.lessThan( requestedLockMode );
// we don't need to worry about existing version being uninitialized because this block isn't called
// by a re-entrant load (re-entrant loads *always* have lock mode NONE)
if ( isVersionCheckNeeded ) {
//we only check the version when *upgrading* lock modes
checkVersion(
session,
resultSet,
entityReference.getEntityPersister(),
entityReferenceAliases.getColumnAliases(),
entityKey,
existing
);
//we need to upgrade the lock mode to the mode requested
persistenceContext.getEntry( existing ).setLockMode( requestedLockMode );
}
}
}
private void checkVersion(
SharedSessionContractImplementor session,
ResultSet resultSet,
EntityPersister persister,
EntityAliases entityAliases,
EntityKey entityKey,
Object entityInstance) {
final Object version = session.getPersistenceContextInternal().getEntry( entityInstance ).getVersion();
if ( version != null ) {
//null version means the object is in the process of being loaded somewhere else in the ResultSet
VersionType versionType = persister.getVersionType();
final Object currentVersion;
try {
currentVersion = versionType.nullSafeGet(
resultSet,
entityAliases.getSuffixedVersionAliases(),
session,
null
);
}
catch (SQLException e) {
throw session.getFactory().getServiceRegistry().getService( JdbcServices.class ).getSqlExceptionHelper().convert(
e,
"Could not read version value from result set"
);
}
if ( !versionType.isEqual( version, currentVersion ) ) {
final StatisticsImplementor statistics = session.getFactory().getStatistics();
if ( statistics.isStatisticsEnabled() ) {
statistics.optimisticFailure( persister.getEntityName() );
}
throw new StaleObjectStateException( persister.getEntityName(), entityKey.getIdentifier() );
}
}
}
@Override
public void finishUpRow(ResultSet resultSet, ResultSetProcessingContextImpl context) {
// cant remember exactly what I was thinking here. Maybe managing the row value caching stuff that is currently
// done in ResultSetProcessingContextImpl.finishUpRow()
//
// anything else?
}
}