org.hibernate.type.EntityType 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
The core O/RM functionality as provided by Hibernate
/*
* 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.type;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Set;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.engine.internal.ForeignKeys;
import org.hibernate.engine.spi.EntityUniqueKey;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.PersistenceContext;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.entity.UniqueKeyLoadable;
import org.hibernate.proxy.HibernateProxy;
/**
* Base for types which map associations to persistent entities.
*
* @author Gavin King
*/
public abstract class EntityType extends AbstractType implements AssociationType {
private final TypeFactory.TypeScope scope;
private final String associatedEntityName;
protected final String uniqueKeyPropertyName;
private final boolean eager;
private final boolean unwrapProxy;
private final boolean referenceToPrimaryKey;
/**
* Cached because of performance
*
* @see #getIdentifierType
* @see #getIdentifierType
*/
private transient volatile Type associatedIdentifierType;
/**
* Cached because of performance
*
* @see #getAssociatedEntityPersister
*/
private transient volatile EntityPersister associatedEntityPersister;
private transient Class returnedClass;
/**
* Constructs the requested entity type mapping.
*
* @param scope The type scope
* @param entityName The name of the associated entity.
* @param uniqueKeyPropertyName The property-ref name, or null if we
* reference the PK of the associated entity.
* @param eager Is eager fetching enabled.
* @param unwrapProxy Is unwrapping of proxies allowed for this association; unwrapping
* says to return the "implementation target" of lazy prooxies; typically only possible
* with lazy="no-proxy".
*
* @deprecated Use {@link #EntityType(org.hibernate.type.TypeFactory.TypeScope, String, boolean, String, boolean, boolean)} instead.
*/
@Deprecated
protected EntityType(
TypeFactory.TypeScope scope,
String entityName,
String uniqueKeyPropertyName,
boolean eager,
boolean unwrapProxy) {
this( scope, entityName, uniqueKeyPropertyName == null, uniqueKeyPropertyName, eager, unwrapProxy );
}
/**
* Constructs the requested entity type mapping.
*
* @param scope The type scope
* @param entityName The name of the associated entity.
* @param referenceToPrimaryKey True if association references a primary key.
* @param uniqueKeyPropertyName The property-ref name, or null if we
* reference the PK of the associated entity.
* @param eager Is eager fetching enabled.
* @param unwrapProxy Is unwrapping of proxies allowed for this association; unwrapping
* says to return the "implementation target" of lazy prooxies; typically only possible
* with lazy="no-proxy".
*/
protected EntityType(
TypeFactory.TypeScope scope,
String entityName,
boolean referenceToPrimaryKey,
String uniqueKeyPropertyName,
boolean eager,
boolean unwrapProxy) {
this.scope = scope;
this.associatedEntityName = entityName;
this.uniqueKeyPropertyName = uniqueKeyPropertyName;
this.eager = eager;
this.unwrapProxy = unwrapProxy;
this.referenceToPrimaryKey = referenceToPrimaryKey;
}
protected EntityType(EntityType original, String superTypeEntityName) {
this.scope = original.scope;
this.associatedEntityName = superTypeEntityName;
this.uniqueKeyPropertyName = original.uniqueKeyPropertyName;
this.eager = original.eager;
this.unwrapProxy = original.unwrapProxy;
this.referenceToPrimaryKey = original.referenceToPrimaryKey;
}
protected TypeFactory.TypeScope scope() {
return scope;
}
/**
* An entity type is a type of association type
*
* @return True.
*/
@Override
public boolean isAssociationType() {
return true;
}
/**
* Explicitly, an entity type is an entity type ;)
*
* @return True.
*/
@Override
public final boolean isEntityType() {
return true;
}
@Override
public boolean isMutable() {
return false;
}
/**
* Generates a string representation of this type.
*
* @return string rep
*/
@Override
public String toString() {
return getClass().getName() + '(' + getAssociatedEntityName() + ')';
}
/**
* For entity types, the name correlates to the associated entity name.
*/
@Override
public String getName() {
return associatedEntityName;
}
/**
* Does this association foreign key reference the primary key of the other table?
* Otherwise, it references a property-ref.
*
* @return True if this association reference the PK of the associated entity.
*/
public boolean isReferenceToPrimaryKey() {
return referenceToPrimaryKey;
}
@Override
public String getRHSUniqueKeyPropertyName() {
// Return null if this type references a PK. This is important for
// associations' use of mappedBy referring to a derived ID.
return referenceToPrimaryKey ? null : uniqueKeyPropertyName;
}
@Override
public String getLHSPropertyName() {
return null;
}
public String getPropertyName() {
return null;
}
/**
* The name of the associated entity.
*
* @return The associated entity name.
*/
public final String getAssociatedEntityName() {
return associatedEntityName;
}
/**
* The name of the associated entity.
*
* @param factory The session factory, for resolution.
*
* @return The associated entity name.
*/
@Override
public String getAssociatedEntityName(SessionFactoryImplementor factory) {
return getAssociatedEntityName();
}
/**
* Retrieves the {@link Joinable} defining the associated entity.
*
* @param factory The session factory.
*
* @return The associated joinable
*
* @throws MappingException Generally indicates an invalid entity name.
*/
@Override
public Joinable getAssociatedJoinable(SessionFactoryImplementor factory) throws MappingException {
return (Joinable) getAssociatedEntityPersister( factory );
}
/**
* This returns the wrong class for an entity with a proxy, or for a named
* entity. Theoretically it should return the proxy class, but it doesn't.
*
* The problem here is that we do not necessarily have a ref to the associated
* entity persister (nor to the session factory, to look it up) which is really
* needed to "do the right thing" here...
*
* @return The entiyt class.
*/
@Override
public final Class getReturnedClass() {
if ( returnedClass == null ) {
returnedClass = determineAssociatedEntityClass();
}
return returnedClass;
}
private Class determineAssociatedEntityClass() {
final String entityName = getAssociatedEntityName();
try {
return ReflectHelper.classForName( entityName );
}
catch (ClassNotFoundException cnfe) {
return this.scope.getTypeConfiguration().getSessionFactory().getMetamodel().entityPersister( entityName ).
getEntityTuplizer().getMappedClass();
}
}
@Override
public Object nullSafeGet(ResultSet rs, String name, SharedSessionContractImplementor session, Object owner)
throws HibernateException, SQLException {
return nullSafeGet( rs, new String[] {name}, session, owner );
}
@Override
public final Object nullSafeGet(
ResultSet rs,
String[] names,
SharedSessionContractImplementor session,
Object owner) throws HibernateException, SQLException {
return resolve( hydrate( rs, names, session, owner ), session, owner );
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, boolean[] settable, SharedSessionContractImplementor session)
throws SQLException {
if ( settable.length > 0 ) {
requireIdentifierOrUniqueKeyType( session.getFactory() )
.nullSafeSet( st, getIdentifier( value, session ), index, settable, session );
}
}
@Override
public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor session)
throws SQLException {
requireIdentifierOrUniqueKeyType( session.getFactory() )
.nullSafeSet( st, getIdentifier( value, session ), index, session );
}
/**
* Two entities are considered the same when their instances are the same.
*
* @param x One entity instance
* @param y Another entity instance
*
* @return True if x == y; false otherwise.
*/
@Override
public final boolean isSame(Object x, Object y) {
return x == y;
}
@Override
public int compare(Object x, Object y) {
return 0; //TODO: entities CAN be compared, by PK, fix this! -> only if/when we can extract the id values....
}
@Override
public Object deepCopy(Object value, SessionFactoryImplementor factory) {
return value; //special case ... this is the leaf of the containment graph, even though not immutable
}
@Override
public Object replace(
Object original,
Object target,
SharedSessionContractImplementor session,
Object owner,
Map copyCache) throws HibernateException {
if ( original == null ) {
return null;
}
Object cached = copyCache.get( original );
if ( cached != null ) {
return cached;
}
else {
if ( original == target ) {
return target;
}
if ( session.getContextEntityIdentifier( original ) == null &&
ForeignKeys.isTransient( associatedEntityName, original, Boolean.FALSE, session ) ) {
// original is transient; it is possible that original is a "managed" entity that has
// not been made persistent yet, so check if copyCache contains original as a "managed" value
// that corresponds with some "merge" value.
if ( copyCache.containsValue( original ) ) {
return original;
}
else {
// the transient entity is not "managed"; add the merge/managed pair to copyCache
final Object copy = session.getEntityPersister( associatedEntityName, original )
.instantiate( null, session );
copyCache.put( original, copy );
return copy;
}
}
else {
Object id = getIdentifier( original, session );
if ( id == null ) {
throw new AssertionFailure(
"non-transient entity has a null id: " + original.getClass()
.getName()
);
}
id = getIdentifierOrUniqueKeyType( session.getFactory() )
.replace( id, null, session, owner, copyCache );
return resolve( id, session, owner );
}
}
}
@Override
public int getHashCode(Object x, SessionFactoryImplementor factory) {
EntityPersister persister = getAssociatedEntityPersister( factory );
if ( !persister.canExtractIdOutOfEntity() ) {
return super.getHashCode( x );
}
final Serializable id;
if ( x instanceof HibernateProxy ) {
id = ( (HibernateProxy) x ).getHibernateLazyInitializer().getIdentifier();
}
else {
final Class mappedClass = persister.getMappedClass();
if ( mappedClass.isAssignableFrom( x.getClass() ) ) {
id = persister.getIdentifier( x );
}
else {
id = (Serializable) x;
}
}
return persister.getIdentifierType().getHashCode( id, factory );
}
@Override
public boolean isEqual(Object x, Object y, SessionFactoryImplementor factory) {
// associations (many-to-one and one-to-one) can be null...
if ( x == null || y == null ) {
return x == y;
}
EntityPersister persister = getAssociatedEntityPersister( factory );
if ( !persister.canExtractIdOutOfEntity() ) {
return super.isEqual( x, y );
}
final Class mappedClass = persister.getMappedClass();
Serializable xid;
if ( x instanceof HibernateProxy ) {
xid = ( (HibernateProxy) x ).getHibernateLazyInitializer()
.getIdentifier();
}
else {
if ( mappedClass.isAssignableFrom( x.getClass() ) ) {
xid = persister.getIdentifier( x );
}
else {
//JPA 2 case where @IdClass contains the id and not the associated entity
xid = (Serializable) x;
}
}
Serializable yid;
if ( y instanceof HibernateProxy ) {
yid = ( (HibernateProxy) y ).getHibernateLazyInitializer()
.getIdentifier();
}
else {
if ( mappedClass.isAssignableFrom( y.getClass() ) ) {
yid = persister.getIdentifier( y );
}
else {
//JPA 2 case where @IdClass contains the id and not the associated entity
yid = (Serializable) y;
}
}
return persister.getIdentifierType()
.isEqual( xid, yid, factory );
}
@Override
public String getOnCondition(String alias, SessionFactoryImplementor factory, Map enabledFilters) {
return getOnCondition( alias, factory, enabledFilters, null );
}
@Override
public String getOnCondition(
String alias,
SessionFactoryImplementor factory,
Map enabledFilters,
Set treatAsDeclarations) {
if ( isReferenceToPrimaryKey() && ( treatAsDeclarations == null || treatAsDeclarations.isEmpty() ) ) {
return "";
}
else {
return getAssociatedJoinable( factory ).filterFragment( alias, enabledFilters, treatAsDeclarations );
}
}
/**
* Resolve an identifier or unique key value
*/
@Override
public Object resolve(Object value, SharedSessionContractImplementor session, Object owner) throws HibernateException {
return resolve(value, session, owner, null);
}
@Override
public Object resolve(Object value, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) throws HibernateException {
if ( value != null && !isNull( owner, session ) ) {
if ( isReferenceToPrimaryKey() ) {
return resolveIdentifier( (Serializable) value, session, overridingEager );
}
else if ( uniqueKeyPropertyName != null ) {
return loadByUniqueKey( getAssociatedEntityName(), uniqueKeyPropertyName, value, session );
}
}
return null;
}
@Override
public Type getSemiResolvedType(SessionFactoryImplementor factory) {
return getAssociatedEntityPersister( factory ).getIdentifierType();
}
protected EntityPersister getAssociatedEntityPersister(final SessionFactoryImplementor factory) {
final EntityPersister persister = associatedEntityPersister;
//The following branch implements a simple lazy-initialization, but rather than the canonical
//form it returns the local variable to avoid a second volatile read: associatedEntityPersister
//needs to be volatile as the initialization might happen by a different thread than the readers.
if ( persister == null ) {
associatedEntityPersister = factory.getMetamodel().entityPersister( getAssociatedEntityName() );
return associatedEntityPersister;
}
else {
return persister;
}
}
protected final Object getIdentifier(Object value, SharedSessionContractImplementor session) throws HibernateException {
if ( isReferenceToPrimaryKey() || uniqueKeyPropertyName == null ) {
return ForeignKeys.getEntityIdentifierIfNotUnsaved(
getAssociatedEntityName(),
value,
session
); //tolerates nulls
}
else if ( value == null ) {
return null;
}
else {
EntityPersister entityPersister = getAssociatedEntityPersister( session.getFactory() );
Object propertyValue = entityPersister.getPropertyValue( value, uniqueKeyPropertyName );
// We now have the value of the property-ref we reference. However,
// we need to dig a little deeper, as that property might also be
// an entity type, in which case we need to resolve its identitifier
Type type = entityPersister.getPropertyType( uniqueKeyPropertyName );
if ( type.isEntityType() ) {
propertyValue = ( (EntityType) type ).getIdentifier( propertyValue, session );
}
return propertyValue;
}
}
/**
* Generate a loggable representation of an instance of the value mapped by this type.
*
* @param value The instance to be logged.
* @param factory The session factory.
*
* @return The loggable string.
*
* @throws HibernateException Generally some form of resolution problem.
*/
@Override
public String toLoggableString(Object value, SessionFactoryImplementor factory) {
if ( value == null ) {
return "null";
}
final EntityPersister persister = getAssociatedEntityPersister( factory );
if ( !persister.getEntityTuplizer().isInstance( value ) ) {
// it should be the id type...
if ( persister.getIdentifierType().getReturnedClass().isInstance( value ) ) {
return associatedEntityName + "#" + value;
}
}
final StringBuilder result = new StringBuilder().append( associatedEntityName );
if ( persister.hasIdentifierProperty() ) {
final Serializable id;
if ( value instanceof HibernateProxy ) {
HibernateProxy proxy = (HibernateProxy) value;
id = proxy.getHibernateLazyInitializer().getIdentifier();
}
else {
id = persister.getIdentifier( value );
}
result.append( '#' )
.append( persister.getIdentifierType().toLoggableString( id, factory ) );
}
return result.toString();
}
/**
* Is the association modeled here defined as a 1-1 in the database (physical model)?
*
* @return True if a 1-1 in the database; false otherwise.
*/
public abstract boolean isOneToOne();
/**
* Is the association modeled here a 1-1 according to the logical moidel?
*
* @return True if a 1-1 in the logical model; false otherwise.
*/
public boolean isLogicalOneToOne() {
return isOneToOne();
}
/**
* Convenience method to locate the identifier type of the associated entity.
*
* @param factory The mappings...
*
* @return The identifier type
*/
Type getIdentifierType(final Mapping factory) {
final Type type = associatedIdentifierType;
//The following branch implements a simple lazy-initialization, but rather than the canonical
//form it returns the local variable to avoid a second volatile read: associatedIdentifierType
//needs to be volatile as the initialization might happen by a different thread than the readers.
if ( type == null ) {
associatedIdentifierType = factory.getIdentifierType( getAssociatedEntityName() );
return associatedIdentifierType;
}
else {
return type;
}
}
/**
* Convenience method to locate the identifier type of the associated entity.
*
* @param session The originating session
*
* @return The identifier type
*/
Type getIdentifierType(final SharedSessionContractImplementor session) {
final Type type = associatedIdentifierType;
if ( type == null ) {
associatedIdentifierType = getIdentifierType( session.getFactory() );
return associatedIdentifierType;
}
else {
return type;
}
}
/**
* Determine the type of either (1) the identifier if we reference the
* associated entity's PK or (2) the unique key to which we refer (i.e.
* the property-ref).
*
* @param factory The mappings...
*
* @return The appropriate type.
*
* @throws MappingException Generally, if unable to resolve the associated entity name
* or unique key property name.
*/
public final Type getIdentifierOrUniqueKeyType(Mapping factory) throws MappingException {
if ( isReferenceToPrimaryKey() || uniqueKeyPropertyName == null ) {
return getIdentifierType( factory );
}
else {
Type type = factory.getReferencedPropertyType( getAssociatedEntityName(), uniqueKeyPropertyName );
if ( type.isEntityType() ) {
type = ( (EntityType) type ).getIdentifierOrUniqueKeyType( factory );
}
return type;
}
}
/**
* The name of the property on the associated entity to which our FK
* refers
*
* @param factory The mappings...
*
* @return The appropriate property name.
*
* @throws MappingException Generally, if unable to resolve the associated entity name
*/
public final String getIdentifierOrUniqueKeyPropertyName(Mapping factory)
throws MappingException {
if ( isReferenceToPrimaryKey() || uniqueKeyPropertyName == null ) {
return factory.getIdentifierPropertyName( getAssociatedEntityName() );
}
else {
return uniqueKeyPropertyName;
}
}
protected abstract boolean isNullable();
/**
* Resolve an identifier via a load.
*
* @param id The entity id to resolve
* @param session The orginating session.
*
* @return The resolved identifier (i.e., loaded entity).
*
* @throws org.hibernate.HibernateException Indicates problems performing the load.
*/
protected final Object resolveIdentifier(Serializable id, SharedSessionContractImplementor session, Boolean overridingEager) throws HibernateException {
boolean isProxyUnwrapEnabled = unwrapProxy &&
getAssociatedEntityPersister( session.getFactory() )
.isInstrumented();
boolean eager = overridingEager != null ? overridingEager : this.eager;
Object proxyOrEntity = session.internalLoad(
getAssociatedEntityName(),
id,
eager,
isNullable()
);
if ( proxyOrEntity instanceof HibernateProxy ) {
( (HibernateProxy) proxyOrEntity ).getHibernateLazyInitializer()
.setUnwrap( isProxyUnwrapEnabled );
}
return proxyOrEntity;
}
protected final Object resolveIdentifier(Serializable id, SharedSessionContractImplementor session) throws HibernateException {
return resolveIdentifier( id, session, null );
}
protected boolean isNull(Object owner, SharedSessionContractImplementor session) {
return false;
}
/**
* Load an instance by a unique key that is not the primary key.
*
* @param entityName The name of the entity to load
* @param uniqueKeyPropertyName The name of the property defining the uniqie key.
* @param key The unique key property value.
* @param session The originating session.
*
* @return The loaded entity
*
* @throws HibernateException generally indicates problems performing the load.
*/
public Object loadByUniqueKey(
String entityName,
String uniqueKeyPropertyName,
Object key,
SharedSessionContractImplementor session) throws HibernateException {
final SessionFactoryImplementor factory = session.getFactory();
UniqueKeyLoadable persister = (UniqueKeyLoadable) factory.getMetamodel().entityPersister( entityName );
//TODO: implement 2nd level caching?! natural id caching ?! proxies?!
EntityUniqueKey euk = new EntityUniqueKey(
entityName,
uniqueKeyPropertyName,
key,
getIdentifierOrUniqueKeyType( factory ),
persister.getEntityMode(),
session.getFactory()
);
final PersistenceContext persistenceContext = session.getPersistenceContext();
Object result = persistenceContext.getEntity( euk );
if ( result == null ) {
result = persister.loadByUniqueKey( uniqueKeyPropertyName, key, session );
// If the entity was not in the Persistence Context, but was found now,
// add it to the Persistence Context
if (result != null) {
persistenceContext.addEntity(euk, result);
}
}
return result == null ? null : persistenceContext.proxyFor( result );
}
protected Type requireIdentifierOrUniqueKeyType(Mapping mapping) {
final Type fkTargetType = getIdentifierOrUniqueKeyType( mapping );
if ( fkTargetType == null ) {
throw new MappingException(
"Unable to determine FK target Type for many-to-one or one-to-one mapping: " +
"referenced-entity-name=[" + getAssociatedEntityName() +
"], referenced-entity-attribute-name=[" + getLHSPropertyName() + "]"
);
}
return fkTargetType;
}
}