org.hibernate.type.EntityType.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.type.EntityType.java

Source

/*
 * 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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
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.
     * <p/>
     * 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<String> 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;
        }
    }

    /**
     * The nullability of the property.
     *
     * @return The nullability of the property.
     */
    public 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.getPersistenceContextInternal();
        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;
    }
}