gt: 		StringBuilder buf = new StringBuilder().append( rootAlias );
context: /*
   * 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.persister.entity;
  
  import java.io.Serializable;
  import java.sql.PreparedStatement;
  import java.sql.ResultSet;
  import java.sql.SQLException;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  
  import org.hibernate.AssertionFailure;
  import org.hibernate.EntityMode;
  import org.hibernate.FetchMode;
  import org.hibernate.HibernateException;
  import org.hibernate.LockMode;
  import org.hibernate.LockOptions;
  import org.hibernate.MappingException;
  import org.hibernate.QueryException;
  import org.hibernate.Session;
  import org.hibernate.StaleObjectStateException;
  import org.hibernate.StaleStateException;
  import org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer;
  import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeDescriptor;
  import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor;
  import org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributesMetadata;
  import org.hibernate.bytecode.spi.BytecodeEnhancementMetadata;
  import org.hibernate.cache.spi.access.EntityRegionAccessStrategy;
  import org.hibernate.cache.spi.access.NaturalIdRegionAccessStrategy;
  import org.hibernate.cache.spi.entry.CacheEntry;
  import org.hibernate.cache.spi.entry.CacheEntryStructure;
  import org.hibernate.cache.spi.entry.ReferenceCacheEntryImpl;
  import org.hibernate.cache.spi.entry.StandardCacheEntryImpl;
  import org.hibernate.cache.spi.entry.StructuredCacheEntry;
  import org.hibernate.cache.spi.entry.UnstructuredCacheEntry;
  import org.hibernate.collection.spi.PersistentCollection;
  import org.hibernate.dialect.lock.LockingStrategy;
  import org.hibernate.engine.OptimisticLockStyle;
  import org.hibernate.engine.internal.CacheHelper;
  import org.hibernate.engine.internal.ImmutableEntityEntryFactory;
  import org.hibernate.engine.internal.MutableEntityEntryFactory;
  import org.hibernate.engine.internal.StatefulPersistenceContext;
  import org.hibernate.engine.internal.Versioning;
  import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey;
  import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
  import org.hibernate.engine.spi.CachedNaturalIdValueSource;
  import org.hibernate.engine.spi.CascadeStyle;
  import org.hibernate.engine.spi.CascadingActions;
  import org.hibernate.engine.spi.CollectionKey;
  import org.hibernate.engine.spi.EntityEntry;
  import org.hibernate.engine.spi.EntityEntryFactory;
  import org.hibernate.engine.spi.EntityKey;
  import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle;
  import org.hibernate.engine.spi.LoadQueryInfluencers;
  import org.hibernate.engine.spi.Mapping;
  import org.hibernate.engine.spi.PersistenceContext.NaturalIdHelper;
  import org.hibernate.engine.spi.PersistentAttributeInterceptable;
  import org.hibernate.engine.spi.SessionFactoryImplementor;
  import org.hibernate.engine.spi.SessionImplementor;
  import org.hibernate.engine.spi.ValueInclusion;
  import org.hibernate.id.IdentifierGenerator;
  import org.hibernate.id.PostInsertIdentifierGenerator;
  import org.hibernate.id.PostInsertIdentityPersister;
  import org.hibernate.id.insert.Binder;
  import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
  import org.hibernate.internal.CoreLogging;
  import org.hibernate.internal.CoreMessageLogger;
  import org.hibernate.internal.FilterHelper;
  import org.hibernate.internal.SessionImpl;
  import org.hibernate.internal.util.StringHelper;
  import org.hibernate.internal.util.collections.ArrayHelper;
  import org.hibernate.jdbc.Expectation;
  import org.hibernate.jdbc.Expectations;
  import org.hibernate.jdbc.TooManyRowsAffectedException;
  import org.hibernate.loader.entity.BatchingEntityLoaderBuilder;
  import org.hibernate.loader.entity.CascadeEntityLoader;
  import org.hibernate.loader.entity.DynamicBatchingEntityLoaderBuilder;
  import org.hibernate.loader.entity.EntityLoader;
  import org.hibernate.loader.entity.UniqueEntityLoader;
  import org.hibernate.mapping.Column;
  import org.hibernate.mapping.Component;
  import org.hibernate.mapping.PersistentClass;
  import org.hibernate.mapping.Property;
  import org.hibernate.mapping.Selectable;
  import org.hibernate.mapping.Table;
  import org.hibernate.metadata.ClassMetadata;
  import org.hibernate.persister.collection.CollectionPersister;
  import org.hibernate.persister.spi.PersisterCreationContext;
  import org.hibernate.persister.walking.internal.EntityIdentifierDefinitionHelper;
  import org.hibernate.persister.walking.spi.AttributeDefinition;
  import org.hibernate.persister.walking.spi.EntityIdentifierDefinition;
  import org.hibernate.pretty.MessageHelper;
  import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl;
  import org.hibernate.sql.Alias;
  import org.hibernate.sql.Delete;
  import org.hibernate.sql.Insert;
  import org.hibernate.sql.JoinFragment;
  import org.hibernate.sql.JoinType;
  import org.hibernate.sql.Select;
  import org.hibernate.sql.SelectFragment;
  import org.hibernate.sql.SimpleSelect;
  import org.hibernate.sql.Template;
  import org.hibernate.sql.Update;
  import org.hibernate.tuple.GenerationTiming;
  import org.hibernate.tuple.InDatabaseValueGenerationStrategy;
  import org.hibernate.tuple.InMemoryValueGenerationStrategy;
  import org.hibernate.tuple.NonIdentifierAttribute;
  import org.hibernate.tuple.ValueGeneration;
  import org.hibernate.tuple.entity.EntityMetamodel;
  import org.hibernate.tuple.entity.EntityTuplizer;
  import org.hibernate.type.AssociationType;
  import org.hibernate.type.CollectionType;
  import org.hibernate.type.ComponentType;
  import org.hibernate.type.CompositeType;
  import org.hibernate.type.EntityType;
  import org.hibernate.type.Type;
  import org.hibernate.type.TypeHelper;
  import org.hibernate.type.VersionType;
  
  /**
   * Basic functionality for persisting an entity via JDBC
   * through either generated or custom SQL
   *
   * @author Gavin King
   */
  public abstract class AbstractEntityPersister
  		implements OuterJoinLoadable, Queryable, ClassMetadata, UniqueKeyLoadable,
  				SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable {
  
  	private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractEntityPersister.class );
  
  	public static final String ENTITY_CLASS = "class";
  
  	// moved up from AbstractEntityPersister ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  	private final SessionFactoryImplementor factory;
  	private final EntityRegionAccessStrategy cacheAccessStrategy;
  	private final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy;
  	private final boolean isLazyPropertiesCacheable;
  	private final CacheEntryHelper cacheEntryHelper;
  	private final EntityMetamodel entityMetamodel;
  	private final EntityTuplizer entityTuplizer;
  	private final EntityEntryFactory entityEntryFactory;
  	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  
  	private final String[] rootTableKeyColumnNames;
  	private final String[] rootTableKeyColumnReaders;
  	private final String[] rootTableKeyColumnReaderTemplates;
  	private final String[] identifierAliases;
  	private final int identifierColumnSpan;
  	private final String versionColumnName;
  	private final boolean hasFormulaProperties;
  	protected final int batchSize;
  	private final boolean hasSubselectLoadableCollections;
  	protected final String rowIdName;
  
  	// The optional SQL string defined in the where attribute
  	private final String sqlWhereString;
  	private final String sqlWhereStringTemplate;
  
  	//information about properties of this class,
  	//including inherited properties
  	//(only really needed for updatable/insertable properties)
  	private final int[] propertyColumnSpans;
  	private final String[] propertySubclassNames;
  	private final String[][] propertyColumnAliases;
  	private final String[][] propertyColumnNames;
  	private final String[][] propertyColumnFormulaTemplates;
  	private final String[][] propertyColumnReaderTemplates;
  	private final String[][] propertyColumnWriters;
  	private final boolean[][] propertyColumnUpdateable;
  	private final boolean[][] propertyColumnInsertable;
  	private final boolean[] propertyUniqueness;
  	private final boolean[] propertySelectable;
  
  	private final List<Integer> lobProperties = new ArrayList<Integer>();
  
  	//information about lazy properties of this class
  	private final String[] lazyPropertyNames;
  	private final int[] lazyPropertyNumbers;
  	private final Type[] lazyPropertyTypes;
  	private final String[][] lazyPropertyColumnAliases;
  
  	//information about all properties in class hierarchy
  	private final String[] subclassPropertyNameClosure;
  	private final String[] subclassPropertySubclassNameClosure;
  	private final Type[] subclassPropertyTypeClosure;
  	private final String[][] subclassPropertyFormulaTemplateClosure;
  	private final String[][] subclassPropertyColumnNameClosure;
  	private final String[][] subclassPropertyColumnReaderClosure;
  	private final String[][] subclassPropertyColumnReaderTemplateClosure;
  	private final FetchMode[] subclassPropertyFetchModeClosure;
  	private final boolean[] subclassPropertyNullabilityClosure;
  	private final boolean[] propertyDefinedOnSubclass;
  	private final int[][] subclassPropertyColumnNumberClosure;
  	private final int[][] subclassPropertyFormulaNumberClosure;
  	private final CascadeStyle[] subclassPropertyCascadeStyleClosure;
  
  	//information about all columns/formulas in class hierarchy
  	private final String[] subclassColumnClosure;
  	private final boolean[] subclassColumnLazyClosure;
  	private final String[] subclassColumnAliasClosure;
  	private final boolean[] subclassColumnSelectableClosure;
  	private final String[] subclassColumnReaderTemplateClosure;
  	private final String[] subclassFormulaClosure;
  	private final String[] subclassFormulaTemplateClosure;
  	private final String[] subclassFormulaAliasClosure;
  	private final boolean[] subclassFormulaLazyClosure;
  
  	// dynamic filters attached to the class-level
  	private final FilterHelper filterHelper;
  
  	private final Set<String> affectingFetchProfileNames = new HashSet<String>();
  
  	private final Map uniqueKeyLoaders = new HashMap();
  	private final Map lockers = new HashMap();
  	private final Map loaders = new HashMap();
  
  	// SQL strings
  	private String sqlVersionSelectString;
  	private String sqlSnapshotSelectString;
  	private Map<String,String> sqlLazySelectStringsByFetchGroup;
  
  	private String sqlIdentityInsertString;
  	private String sqlUpdateByRowIdString;
  	private String sqlLazyUpdateByRowIdString;
  
  	private String[] sqlDeleteStrings;
  	private String[] sqlInsertStrings;
  	private String[] sqlUpdateStrings;
  	private String[] sqlLazyUpdateStrings;
  
  	private String sqlInsertGeneratedValuesSelectString;
  	private String sqlUpdateGeneratedValuesSelectString;
  
  	//Custom SQL (would be better if these were private)
  	protected boolean[] insertCallable;
  	protected boolean[] updateCallable;
  	protected boolean[] deleteCallable;
  	protected String[] customSQLInsert;
  	protected String[] customSQLUpdate;
  	protected String[] customSQLDelete;
  	protected ExecuteUpdateResultCheckStyle[] insertResultCheckStyles;
  	protected ExecuteUpdateResultCheckStyle[] updateResultCheckStyles;
  	protected ExecuteUpdateResultCheckStyle[] deleteResultCheckStyles;
  
  	private InsertGeneratedIdentifierDelegate identityDelegate;
  
  	private boolean[] tableHasColumns;
  
  	private final String loaderName;
  
  	private UniqueEntityLoader queryLoader;
  
  	private final Map subclassPropertyAliases = new HashMap();
  	private final Map subclassPropertyColumnNames = new HashMap();
  
  	protected final BasicEntityPropertyMapping propertyMapping;
  
  	private final boolean useReferenceCacheEntries;
  
  	protected void addDiscriminatorToInsert(Insert insert) {
  	}
  
  	protected void addDiscriminatorToSelect(SelectFragment select, String name, String suffix) {
  	}
  
  	protected abstract int[] getSubclassColumnTableNumberClosure();
  
  	protected abstract int[] getSubclassFormulaTableNumberClosure();
  
  	public abstract String getSubclassTableName(int j);
  
  	protected abstract String[] getSubclassTableKeyColumns(int j);
  
  	protected abstract boolean isClassOrSuperclassTable(int j);
  
  	protected abstract int getSubclassTableSpan();
  
  	protected abstract int getTableSpan();
  
  	protected abstract boolean isTableCascadeDeleteEnabled(int j);
  
  	protected abstract String getTableName(int j);
  
  	protected abstract String[] getKeyColumns(int j);
  
  	protected abstract boolean isPropertyOfTable(int property, int j);
  
  	protected abstract int[] getPropertyTableNumbersInSelect();
  
  	protected abstract int[] getPropertyTableNumbers();
  
  	protected abstract int getSubclassPropertyTableNumber(int i);
  
  	protected abstract String filterFragment(String alias) throws MappingException;
  
  	protected abstract String filterFragment(String alias, Set<String> treatAsDeclarations);
  
  	private static final String DISCRIMINATOR_ALIAS = "clazz_";
  
  	public String getDiscriminatorColumnName() {
  		return DISCRIMINATOR_ALIAS;
  	}
  
  	public String getDiscriminatorColumnReaders() {
  		return DISCRIMINATOR_ALIAS;
  	}
  
  	public String getDiscriminatorColumnReaderTemplate() {
  		return DISCRIMINATOR_ALIAS;
  	}
  
  	protected String getDiscriminatorAlias() {
  		return DISCRIMINATOR_ALIAS;
  	}
  
  	protected String getDiscriminatorFormulaTemplate() {
  		return null;
  	}
  
  	protected boolean isInverseTable(int j) {
  		return false;
  	}
  
  	protected boolean isNullableTable(int j) {
  		return false;
  	}
  
  	protected boolean isNullableSubclassTable(int j) {
  		return false;
  	}
  
  	protected boolean isInverseSubclassTable(int j) {
  		return false;
  	}
  
  	public boolean isSubclassEntityName(String entityName) {
  		return entityMetamodel.getSubclassEntityNames().contains( entityName );
  	}
  
  	private boolean[] getTableHasColumns() {
  		return tableHasColumns;
  	}
  
  	public String[] getRootTableKeyColumnNames() {
  		return rootTableKeyColumnNames;
  	}
  
  	protected String[] getSQLUpdateByRowIdStrings() {
  		if ( sqlUpdateByRowIdString == null ) {
  			throw new AssertionFailure( "no update by row id" );
  		}
  		String[] result = new String[getTableSpan() + 1];
  		result[0] = sqlUpdateByRowIdString;
  		System.arraycopy( sqlUpdateStrings, 0, result, 1, getTableSpan() );
  		return result;
  	}
  
  	protected String[] getSQLLazyUpdateByRowIdStrings() {
  		if ( sqlLazyUpdateByRowIdString == null ) {
  			throw new AssertionFailure( "no update by row id" );
  		}
  		String[] result = new String[getTableSpan()];
  		result[0] = sqlLazyUpdateByRowIdString;
  		System.arraycopy( sqlLazyUpdateStrings, 1, result, 1, getTableSpan() - 1 );
  		return result;
  	}
  
  	protected String getSQLSnapshotSelectString() {
  		return sqlSnapshotSelectString;
  	}
  
  	protected String getSQLLazySelectString(String fetchGroup) {
  		return sqlLazySelectStringsByFetchGroup.get( fetchGroup );
  	}
  
  	protected String[] getSQLDeleteStrings() {
  		return sqlDeleteStrings;
  	}
  
  	protected String[] getSQLInsertStrings() {
  		return sqlInsertStrings;
  	}
  
  	protected String[] getSQLUpdateStrings() {
  		return sqlUpdateStrings;
  	}
  
  	protected String[] getSQLLazyUpdateStrings() {
  		return sqlLazyUpdateStrings;
  	}
  
  	/**
  	 * The query that inserts a row, letting the database generate an id
  	 *
  	 * @return The IDENTITY-based insertion query.
  	 */
  	protected String getSQLIdentityInsertString() {
  		return sqlIdentityInsertString;
  	}
  
  	protected String getVersionSelectString() {
  		return sqlVersionSelectString;
  	}
  
  	protected boolean isInsertCallable(int j) {
  		return insertCallable[j];
  	}
  
  	protected boolean isUpdateCallable(int j) {
  		return updateCallable[j];
  	}
  
  	protected boolean isDeleteCallable(int j) {
  		return deleteCallable[j];
  	}
  
  	protected boolean isSubclassPropertyDeferred(String propertyName, String entityName) {
  		return false;
  	}
  
  	protected boolean isSubclassTableSequentialSelect(int j) {
  		return false;
  	}
  
  	public boolean hasSequentialSelect() {
  		return false;
  	}
  
  	/**
  	 * Decide which tables need to be updated.
  	 * <p/>
  	 * The return here is an array of boolean values with each index corresponding
  	 * to a given table in the scope of this persister.
  	 *
  	 * @param dirtyProperties The indices of all the entity properties considered dirty.
  	 * @param hasDirtyCollection Whether any collections owned by the entity which were considered dirty.
  	 *
  	 * @return Array of booleans indicating which table require updating.
  	 */
  	protected boolean[] getTableUpdateNeeded(final int[] dirtyProperties, boolean hasDirtyCollection) {
  
  		if ( dirtyProperties == null ) {
  			return getTableHasColumns(); // for objects that came in via update()
  		}
  		else {
  			boolean[] updateability = getPropertyUpdateability();
  			int[] propertyTableNumbers = getPropertyTableNumbers();
  			boolean[] tableUpdateNeeded = new boolean[getTableSpan()];
  			for ( int property : dirtyProperties ) {
  				int table = propertyTableNumbers[property];
  				tableUpdateNeeded[table] = tableUpdateNeeded[table] ||
  						( getPropertyColumnSpan( property ) > 0 && updateability[property] );
  			}
  			if ( isVersioned() ) {
  				tableUpdateNeeded[0] = tableUpdateNeeded[0] ||
  						Versioning.isVersionIncrementRequired(
  								dirtyProperties,
  								hasDirtyCollection,
  								getPropertyVersionability()
  						);
  			}
  			return tableUpdateNeeded;
  		}
  	}
  
  	public boolean hasRowId() {
  		return rowIdName != null;
  	}
  
  	protected boolean[][] getPropertyColumnUpdateable() {
  		return propertyColumnUpdateable;
  	}
  
  	protected boolean[][] getPropertyColumnInsertable() {
  		return propertyColumnInsertable;
  	}
  
  	protected boolean[] getPropertySelectable() {
  		return propertySelectable;
  	}
  
  	@SuppressWarnings("UnnecessaryBoxing")
  	public AbstractEntityPersister(
  			final PersistentClass persistentClass,
  			final EntityRegionAccessStrategy cacheAccessStrategy,
  			final NaturalIdRegionAccessStrategy naturalIdRegionAccessStrategy,
  			final PersisterCreationContext creationContext) throws HibernateException {
  
  		// moved up from AbstractEntityPersister ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  		this.factory = creationContext.getSessionFactory();
  		this.cacheAccessStrategy = cacheAccessStrategy;
  		this.naturalIdRegionAccessStrategy = naturalIdRegionAccessStrategy;
  		isLazyPropertiesCacheable = persistentClass.isLazyPropertiesCacheable();
  
  		this.entityMetamodel = new EntityMetamodel( persistentClass, this, factory );
  		this.entityTuplizer = this.entityMetamodel.getTuplizer();
  
  		if ( entityMetamodel.isMutable() ) {
  			this.entityEntryFactory = MutableEntityEntryFactory.INSTANCE;
  		}
  		else {
  			this.entityEntryFactory = ImmutableEntityEntryFactory.INSTANCE;
  		}
  		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  
  		int batch = persistentClass.getBatchSize();
  		if ( batch == -1 ) {
  			batch = factory.getSessionFactoryOptions().getDefaultBatchFetchSize();
  		}
  		batchSize = batch;
  		hasSubselectLoadableCollections = persistentClass.hasSubselectLoadableCollections();
  
  		propertyMapping = new BasicEntityPropertyMapping( this );
  
  		// IDENTIFIER
  
  		identifierColumnSpan = persistentClass.getIdentifier().getColumnSpan();
  		rootTableKeyColumnNames = new String[identifierColumnSpan];
  		rootTableKeyColumnReaders = new String[identifierColumnSpan];
  		rootTableKeyColumnReaderTemplates = new String[identifierColumnSpan];
  		identifierAliases = new String[identifierColumnSpan];
  
  		rowIdName = persistentClass.getRootTable().getRowId();
  
  		loaderName = persistentClass.getLoaderName();
  
  		Iterator iter = persistentClass.getIdentifier().getColumnIterator();
  		int i = 0;
  		while ( iter.hasNext() ) {
  			Column col = (Column) iter.next();
  			rootTableKeyColumnNames[i] = col.getQuotedName( factory.getDialect() );
  			rootTableKeyColumnReaders[i] = col.getReadExpr( factory.getDialect() );
  			rootTableKeyColumnReaderTemplates[i] = col.getTemplate(
  					factory.getDialect(),
  					factory.getSqlFunctionRegistry()
  			);
  			identifierAliases[i] = col.getAlias( factory.getDialect(), persistentClass.getRootTable() );
  			i++;
  		}
  
  		// VERSION
  
  		if ( persistentClass.isVersioned() ) {
  			versionColumnName = ( (Column) persistentClass.getVersion().getColumnIterator().next() ).getQuotedName(
  					factory.getDialect()
  			);
  		}
  		else {
  			versionColumnName = null;
  		}
  
  		//WHERE STRING
  
  		sqlWhereString = StringHelper.isNotEmpty( persistentClass.getWhere() ) ?
  				"( " + persistentClass.getWhere() + ") " :
  				null;
  		sqlWhereStringTemplate = sqlWhereString == null ?
  				null :
  				Template.renderWhereStringTemplate(
  						sqlWhereString,
  						factory.getDialect(),
  						factory.getSqlFunctionRegistry()
  				);
  
  		// PROPERTIES
  
  		final boolean lazyAvailable = isInstrumented();
  
  		int hydrateSpan = entityMetamodel.getPropertySpan();
  		propertyColumnSpans = new int[hydrateSpan];
  		propertySubclassNames = new String[hydrateSpan];
  		propertyColumnAliases = new String[hydrateSpan][];
  		propertyColumnNames = new String[hydrateSpan][];
  		propertyColumnFormulaTemplates = new String[hydrateSpan][];
  		propertyColumnReaderTemplates = new String[hydrateSpan][];
  		propertyColumnWriters = new String[hydrateSpan][];
  		propertyUniqueness = new boolean[hydrateSpan];
  		propertySelectable = new boolean[hydrateSpan];
  		propertyColumnUpdateable = new boolean[hydrateSpan][];
  		propertyColumnInsertable = new boolean[hydrateSpan][];
  		HashSet thisClassProperties = new HashSet();
  
  		ArrayList lazyNames = new ArrayList();
  		ArrayList lazyNumbers = new ArrayList();
  		ArrayList lazyTypes = new ArrayList();
  		ArrayList lazyColAliases = new ArrayList();
  
  		iter = persistentClass.getPropertyClosureIterator();
  		i = 0;
  		boolean foundFormula = false;
  		while ( iter.hasNext() ) {
  			Property prop = (Property) iter.next();
  			thisClassProperties.add( prop );
  
  			int span = prop.getColumnSpan();
  			propertyColumnSpans[i] = span;
  			propertySubclassNames[i] = prop.getPersistentClass().getEntityName();
  			String[] colNames = new String[span];
  			String[] colAliases = new String[span];
  			String[] colReaderTemplates = new String[span];
  			String[] colWriters = new String[span];
  			String[] formulaTemplates = new String[span];
  			Iterator colIter = prop.getColumnIterator();
  			int k = 0;
  			while ( colIter.hasNext() ) {
  				Selectable thing = (Selectable) colIter.next();
  				colAliases[k] = thing.getAlias( factory.getDialect(), prop.getValue().getTable() );
  				if ( thing.isFormula() ) {
  					foundFormula = true;
  					formulaTemplates[k] = thing.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
  				}
  				else {
  					Column col = (Column) thing;
  					colNames[k] = col.getQuotedName( factory.getDialect() );
  					colReaderTemplates[k] = col.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
  					colWriters[k] = col.getWriteExpr();
  				}
  				k++;
  			}
  			propertyColumnNames[i] = colNames;
  			propertyColumnFormulaTemplates[i] = formulaTemplates;
  			propertyColumnReaderTemplates[i] = colReaderTemplates;
  			propertyColumnWriters[i] = colWriters;
  			propertyColumnAliases[i] = colAliases;
  
  			if ( lazyAvailable && prop.isLazy() ) {
  				lazyNames.add( prop.getName() );
  				lazyNumbers.add( i );
  				lazyTypes.add( prop.getValue().getType() );
  				lazyColAliases.add( colAliases );
  			}
  
  			propertyColumnUpdateable[i] = prop.getValue().getColumnUpdateability();
  			propertyColumnInsertable[i] = prop.getValue().getColumnInsertability();
  
  			propertySelectable[i] = prop.isSelectable();
  
  			propertyUniqueness[i] = prop.getValue().isAlternateUniqueKey();
  
  			if ( prop.isLob() && getFactory().getDialect().forceLobAsLastValue() ) {
  				lobProperties.add( i );
  			}
  
  			i++;
  
  		}
  		hasFormulaProperties = foundFormula;
  		lazyPropertyColumnAliases = ArrayHelper.to2DStringArray( lazyColAliases );
  		lazyPropertyNames = ArrayHelper.toStringArray( lazyNames );
  		lazyPropertyNumbers = ArrayHelper.toIntArray( lazyNumbers );
  		lazyPropertyTypes = ArrayHelper.toTypeArray( lazyTypes );
  
  		// SUBCLASS PROPERTY CLOSURE
  
  		ArrayList columns = new ArrayList();
  		ArrayList columnsLazy = new ArrayList();
  		ArrayList columnReaderTemplates = new ArrayList();
  		ArrayList aliases = new ArrayList();
  		ArrayList formulas = new ArrayList();
  		ArrayList formulaAliases = new ArrayList();
  		ArrayList formulaTemplates = new ArrayList();
  		ArrayList formulasLazy = new ArrayList();
  		ArrayList types = new ArrayList();
  		ArrayList names = new ArrayList();
  		ArrayList classes = new ArrayList();
  		ArrayList templates = new ArrayList();
  		ArrayList propColumns = new ArrayList();
  		ArrayList propColumnReaders = new ArrayList();
  		ArrayList propColumnReaderTemplates = new ArrayList();
  		ArrayList joinedFetchesList = new ArrayList();
  		ArrayList cascades = new ArrayList();
  		ArrayList definedBySubclass = new ArrayList();
  		ArrayList propColumnNumbers = new ArrayList();
  		ArrayList propFormulaNumbers = new ArrayList();
  		ArrayList columnSelectables = new ArrayList();
  		ArrayList propNullables = new ArrayList();
  
  		iter = persistentClass.getSubclassPropertyClosureIterator();
  		while ( iter.hasNext() ) {
  			Property prop = (Property) iter.next();
  			names.add( prop.getName() );
  			classes.add( prop.getPersistentClass().getEntityName() );
  			boolean isDefinedBySubclass = !thisClassProperties.contains( prop );
  			definedBySubclass.add( Boolean.valueOf( isDefinedBySubclass ) );
  			propNullables.add( Boolean.valueOf( prop.isOptional() || isDefinedBySubclass ) ); //TODO: is this completely correct?
  			types.add( prop.getType() );
  
  			Iterator colIter = prop.getColumnIterator();
  			String[] cols = new String[prop.getColumnSpan()];
  			String[] readers = new String[prop.getColumnSpan()];
  			String[] readerTemplates = new String[prop.getColumnSpan()];
  			String[] forms = new String[prop.getColumnSpan()];
  			int[] colnos = new int[prop.getColumnSpan()];
  			int[] formnos = new int[prop.getColumnSpan()];
  			int l = 0;
  			Boolean lazy = Boolean.valueOf( prop.isLazy() && lazyAvailable );
  			while ( colIter.hasNext() ) {
  				Selectable thing = (Selectable) colIter.next();
  				if ( thing.isFormula() ) {
  					String template = thing.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
  					formnos[l] = formulaTemplates.size();
  					colnos[l] = -1;
  					formulaTemplates.add( template );
  					forms[l] = template;
  					formulas.add( thing.getText( factory.getDialect() ) );
  					formulaAliases.add( thing.getAlias( factory.getDialect() ) );
  					formulasLazy.add( lazy );
  				}
  				else {
  					Column col = (Column) thing;
  					String colName = col.getQuotedName( factory.getDialect() );
  					colnos[l] = columns.size(); //before add :-)
  					formnos[l] = -1;
  					columns.add( colName );
  					cols[l] = colName;
  					aliases.add( thing.getAlias( factory.getDialect(), prop.getValue().getTable() ) );
  					columnsLazy.add( lazy );
  					columnSelectables.add( Boolean.valueOf( prop.isSelectable() ) );
  
  					readers[l] = col.getReadExpr( factory.getDialect() );
  					String readerTemplate = col.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
  					readerTemplates[l] = readerTemplate;
  					columnReaderTemplates.add( readerTemplate );
  				}
  				l++;
  			}
  			propColumns.add( cols );
  			propColumnReaders.add( readers );
  			propColumnReaderTemplates.add( readerTemplates );
  			templates.add( forms );
  			propColumnNumbers.add( colnos );
  			propFormulaNumbers.add( formnos );
  
  			joinedFetchesList.add( prop.getValue().getFetchMode() );
  			cascades.add( prop.getCascadeStyle() );
  		}
  		subclassColumnClosure = ArrayHelper.toStringArray( columns );
  		subclassColumnAliasClosure = ArrayHelper.toStringArray( aliases );
  		subclassColumnLazyClosure = ArrayHelper.toBooleanArray( columnsLazy );
  		subclassColumnSelectableClosure = ArrayHelper.toBooleanArray( columnSelectables );
  		subclassColumnReaderTemplateClosure = ArrayHelper.toStringArray( columnReaderTemplates );
  
  		subclassFormulaClosure = ArrayHelper.toStringArray( formulas );
  		subclassFormulaTemplateClosure = ArrayHelper.toStringArray( formulaTemplates );
  		subclassFormulaAliasClosure = ArrayHelper.toStringArray( formulaAliases );
  		subclassFormulaLazyClosure = ArrayHelper.toBooleanArray( formulasLazy );
  
  		subclassPropertyNameClosure = ArrayHelper.toStringArray( names );
  		subclassPropertySubclassNameClosure = ArrayHelper.toStringArray( classes );
  		subclassPropertyTypeClosure = ArrayHelper.toTypeArray( types );
  		subclassPropertyNullabilityClosure = ArrayHelper.toBooleanArray( propNullables );
  		subclassPropertyFormulaTemplateClosure = ArrayHelper.to2DStringArray( templates );
  		subclassPropertyColumnNameClosure = ArrayHelper.to2DStringArray( propColumns );
  		subclassPropertyColumnReaderClosure = ArrayHelper.to2DStringArray( propColumnReaders );
  		subclassPropertyColumnReaderTemplateClosure = ArrayHelper.to2DStringArray( propColumnReaderTemplates );
  		subclassPropertyColumnNumberClosure = ArrayHelper.to2DIntArray( propColumnNumbers );
  		subclassPropertyFormulaNumberClosure = ArrayHelper.to2DIntArray( propFormulaNumbers );
  
  		subclassPropertyCascadeStyleClosure = new CascadeStyle[cascades.size()];
  		iter = cascades.iterator();
  		int j = 0;
  		while ( iter.hasNext() ) {
  			subclassPropertyCascadeStyleClosure[j++] = (CascadeStyle) iter.next();
  		}
  		subclassPropertyFetchModeClosure = new FetchMode[joinedFetchesList.size()];
  		iter = joinedFetchesList.iterator();
  		j = 0;
  		while ( iter.hasNext() ) {
  			subclassPropertyFetchModeClosure[j++] = (FetchMode) iter.next();
  		}
  
  		propertyDefinedOnSubclass = new boolean[definedBySubclass.size()];
  		iter = definedBySubclass.iterator();
  		j = 0;
  		while ( iter.hasNext() ) {
  			propertyDefinedOnSubclass[j++] = (Boolean) iter.next();
  		}
  
  		// Handle any filters applied to the class level
  		filterHelper = new FilterHelper( persistentClass.getFilters(), factory );
  
  		// Check if we can use Reference Cached entities in 2lc
  		// todo : should really validate that the cache access type is read-only
  		boolean refCacheEntries = true;
  		if ( !factory.getSessionFactoryOptions().isDirectReferenceCacheEntriesEnabled() ) {
  			refCacheEntries = false;
  		}
  
  		// for now, limit this to just entities that:
  		// 		1) are immutable
  		if ( entityMetamodel.isMutable() ) {
  			refCacheEntries = false;
  		}
  
  		//		2)  have no associations.  Eventually we want to be a little more lenient with associations.
  		for ( Type type : getSubclassPropertyTypeClosure() ) {
  			if ( type.isAssociationType() ) {
  				refCacheEntries = false;
  			}
  		}
  
  		useReferenceCacheEntries = refCacheEntries;
  
  		this.cacheEntryHelper = buildCacheEntryHelper();
  
  	}
  
  	protected CacheEntryHelper buildCacheEntryHelper() {
  		if ( cacheAccessStrategy == null ) {
  			// the entity defined no caching...
  			return NoopCacheEntryHelper.INSTANCE;
  		}
  
  		if ( canUseReferenceCacheEntries() ) {
  			entityMetamodel.setLazy( false );
  			// todo : do we also need to unset proxy factory?
  			return new ReferenceCacheEntryHelper( this );
  		}
  
  		return factory.getSessionFactoryOptions().isStructuredCacheEntriesEnabled()
  				? new StructuredCacheEntryHelper( this )
  				: new StandardCacheEntryHelper( this );
  	}
  
  	public boolean canUseReferenceCacheEntries() {
  		return useReferenceCacheEntries;
  	}
  
  	protected static String getTemplateFromString(String string, SessionFactoryImplementor factory) {
  		return string == null ?
  				null :
  				Template.renderWhereStringTemplate( string, factory.getDialect(), factory.getSqlFunctionRegistry() );
  	}
  
  	protected Map<String,String> generateLazySelectStringsByFetchGroup() {
  		final BytecodeEnhancementMetadata enhancementMetadata = entityMetamodel.getBytecodeEnhancementMetadata();
  		if ( !enhancementMetadata.isEnhancedForLazyLoading()
  				|| !enhancementMetadata.getLazyAttributesMetadata().hasLazyAttributes() ) {
  			return Collections.emptyMap();
  		}
  
  		Map<String,String> result = new HashMap<String, String>();
  
  		final LazyAttributesMetadata lazyAttributesMetadata = enhancementMetadata.getLazyAttributesMetadata();
  		for ( String groupName : lazyAttributesMetadata.getFetchGroupNames() ) {
  			HashSet tableNumbers = new HashSet();
  			ArrayList columnNumbers = new ArrayList();
  			ArrayList formulaNumbers = new ArrayList();
  
  			for ( LazyAttributeDescriptor lazyAttributeDescriptor :
  					lazyAttributesMetadata.getFetchGroupAttributeDescriptors( groupName ) ) {
  				// all this only really needs to consider properties
  				// of this class, not its subclasses, but since we
  				// are reusing code used for sequential selects, we
  				// use the subclass closure
  				int propertyNumber = getSubclassPropertyIndex( lazyAttributeDescriptor.getName() );
  
  				int tableNumber = getSubclassPropertyTableNumber( propertyNumber );
  				tableNumbers.add( tableNumber );
  
  				int[] colNumbers = subclassPropertyColumnNumberClosure[propertyNumber];
  				for ( int colNumber : colNumbers ) {
  					if ( colNumber != -1 ) {
  						columnNumbers.add( colNumber );
  					}
  				}
  				int[] formNumbers = subclassPropertyFormulaNumberClosure[propertyNumber];
  				for ( int formNumber : formNumbers ) {
  					if ( formNumber != -1 ) {
  						formulaNumbers.add( formNumber );
  					}
  				}
  			}
  
  			if ( columnNumbers.size() == 0 && formulaNumbers.size() == 0 ) {
  				// only one-to-one is lazy fetched
  				continue;
  			}
  
  			result.put(
  					groupName,
  					renderSelect(
  							ArrayHelper.toIntArray( tableNumbers ),
  							ArrayHelper.toIntArray( columnNumbers ),
  							ArrayHelper.toIntArray( formulaNumbers )
  					)
  			);
  		}
  
  		return result;
  	}
  
  	public Object initializeLazyProperty(String fieldName, Object entity, SessionImplementor session) {
  		final EntityEntry entry = session.getPersistenceContext().getEntry( entity );
  		final InterceptorImplementor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor();
  		assert interceptor != null : "Expecting bytecode interceptor to be non-null";
  
  		if ( hasCollections() ) {
  			final Type type = getPropertyType( fieldName );
  			if ( type.isCollectionType() ) {
  				// we have a condition where a collection attribute is being access via enhancement:
  				// 		we can circumvent all the rest and just return the PersistentCollection
  				final CollectionType collectionType = (CollectionType) type;
  				final CollectionPersister persister = factory.getCollectionPersister( collectionType.getRole() );
  
  				// Get/create the collection, and make sure it is initialized!  This initialized part is
  				// different from proxy-based scenarios where we have to create the PersistentCollection
  				// reference "ahead of time" to add as a reference to the proxy.  For bytecode solutions
  				// we are not creating the PersistentCollection ahead of time, but instead we are creating
  				// it on first request through the enhanced entity.
  
  				// see if there is already a collection instance associated with the session
  				// 		NOTE : can this ever happen?
  				final Serializable key = getCollectionKey( persister, entity, entry, session );
  				PersistentCollection collection = session.getPersistenceContext().getCollection( new CollectionKey( persister, key ) );
  				if ( collection == null ) {
  					collection = collectionType.instantiate( session, persister, key );
  					collection.setOwner( entity );
  					session.getPersistenceContext().addUninitializedCollection( persister, collection, key );
  				}
  
  				// Initialize it
  				session.initializeCollection( collection, false );
  				interceptor.attributeInitialized( fieldName );
  
  				if ( collectionType.isArrayType() ) {
  					session.getPersistenceContext().addCollectionHolder( collection );
  				}
  
  				// EARLY EXIT!!!
  				return collection;
  			}
  		}
  
  		final Serializable id = session.getContextEntityIdentifier( entity );
  		if ( entry == null ) {
  			throw new HibernateException( "entity is not associated with the session: " + id );
  		}
  
  		if ( LOG.isTraceEnabled() ) {
  			LOG.tracev(
  					"Initializing lazy properties of: {0}, field access: {1}", MessageHelper.infoString(
  							this,
  							id,
  							getFactory()
  					), fieldName
  			);
  		}
  
  		if ( session.getCacheMode().isGetEnabled() && hasCache() && isLazyPropertiesCacheable() ) {
  			final EntityRegionAccessStrategy cache = getCacheAccessStrategy();
  			final Object cacheKey = cache.generateCacheKey(id, this, session.getFactory(), session.getTenantIdentifier() );
  			final Object ce = CacheHelper.fromSharedCache( session, cacheKey, cache );
  			if ( ce != null ) {
  				final CacheEntry cacheEntry = (CacheEntry) getCacheEntryStructure().destructure( ce, factory );
  				final Object initializedValue = initializeLazyPropertiesFromCache( fieldName, entity, session, entry, cacheEntry );
  				interceptor.attributeInitialized( fieldName );
  
  				// NOTE EARLY EXIT!!!
  				return initializedValue;
  			}
  		}
  
  		return initializeLazyPropertiesFromDatastore( fieldName, entity, session, id, entry );
  
  	}
  
  	protected Serializable getCollectionKey(
  			CollectionPersister persister,
  			Object owner,
  			EntityEntry ownerEntry,
  			SessionImplementor session) {
  		final CollectionType collectionType = persister.getCollectionType();
  
  		if ( ownerEntry != null ) {
  			// this call only works when the owner is associated with the Session, which is not always the case
  			return collectionType.getKeyOfOwner( owner, session );
  		}
  
  		if ( collectionType.getLHSPropertyName() == null ) {
  			// collection key is defined by the owning entity identifier
  			return persister.getOwnerEntityPersister().getIdentifier( owner, session );
  		}
  		else {
  			return (Serializable) persister.getOwnerEntityPersister().getPropertyValue( owner, collectionType.getLHSPropertyName() );
  		}
  	}
  
  	private Object initializeLazyPropertiesFromDatastore(
  			final String fieldName,
  			final Object entity,
  			final SessionImplementor session,
  			final Serializable id,
  			final EntityEntry entry) {
  
  		if ( !hasLazyProperties() ) {
  			throw new AssertionFailure( "no lazy properties" );
  		}
  
  		final InterceptorImplementor interceptor = ( (PersistentAttributeInterceptable) entity ).$$_hibernate_getInterceptor();
  		assert interceptor != null : "Expecting bytecode interceptor to be non-null";
  
  		LOG.trace( "Initializing lazy properties from datastore" );
  
  		final String fetchGroup = getEntityMetamodel().getBytecodeEnhancementMetadata()
  				.getLazyAttributesMetadata()
  				.getFetchGroupName( fieldName );
  		final List<LazyAttributeDescriptor> fetchGroupAttributeDescriptors = getEntityMetamodel().getBytecodeEnhancementMetadata()
  				.getLazyAttributesMetadata()
  				.getFetchGroupAttributeDescriptors( fetchGroup );
  
  		final Set<String> initializedLazyAttributeNames = interceptor.getInitializedLazyAttributeNames();
  
  		final String lazySelect = getSQLLazySelectString( fetchGroup );
  
  		try {
  			Object result = null;
  			PreparedStatement ps = null;
  			try {
  				ResultSet rs = null;
  				try {
  					if ( lazySelect != null ) {
  						// null sql means that the only lazy properties
  						// are shared PK one-to-one associations which are
  						// handled differently in the Type#nullSafeGet code...
  						ps = session.getJdbcCoordinator()
  								.getStatementPreparer()
  								.prepareStatement( lazySelect );
  						getIdentifierType().nullSafeSet( ps, id, 1, session );
  						rs = session.getJdbcCoordinator().getResultSetReturn().extract( ps );
  						rs.next();
  					}
  					final Object[] snapshot = entry.getLoadedState();
  					for ( LazyAttributeDescriptor fetchGroupAttributeDescriptor : fetchGroupAttributeDescriptors ) {
  						final boolean previousInitialized = initializedLazyAttributeNames.contains( fetchGroupAttributeDescriptor.getName() );
  						final Object loadedValue = fetchGroupAttributeDescriptor.getType().nullSafeGet(
  								rs,
  								lazyPropertyColumnAliases[fetchGroupAttributeDescriptor.getLazyIndex()],
  								session,
  								entity
  						);
  						final boolean set = initializeLazyProperty(
  								fieldName,
  								entity,
  								session,
  								snapshot,
  								fetchGroupAttributeDescriptor.getLazyIndex(),
  								loadedValue
  						);
  						if ( previousInitialized ) {
  							// its already been initialized (e.g. by a write) so we don't want to overwrite
  							continue;
  						}
  						if ( set ) {
  							result = loadedValue;
  							interceptor.attributeInitialized( fetchGroupAttributeDescriptor.getName() );
  						}
  
  					}
  				}
  				finally {
  					if ( rs != null ) {
  						session.getJdbcCoordinator().getResourceRegistry().release( rs, ps );
  					}
  				}
  			}
  			finally {
  				if ( ps != null ) {
  					session.getJdbcCoordinator().getResourceRegistry().release( ps );
  					session.getJdbcCoordinator().afterStatementExecution();
  				}
  			}
  
  			LOG.trace( "Done initializing lazy properties" );
  
  			return result;
  
  		}
  		catch (SQLException sqle) {
  			throw getFactory().getSQLExceptionHelper().convert(
  					sqle,
  					"could not initialize lazy properties: " + MessageHelper.infoString( this, id, getFactory() ),
  					lazySelect
  			);
  		}
  	}
  
  	private Object initializeLazyPropertiesFromCache(
  			final String fieldName,
  			final Object entity,
  			final SessionImplementor session,
  			final EntityEntry entry,
  			final CacheEntry cacheEntry) {
  
  		LOG.trace( "Initializing lazy properties from second-level cache" );
  
  		Object result = null;
  		Serializable[] disassembledValues = cacheEntry.getDisassembledState();
  		final Object[] snapshot = entry.getLoadedState();
  		for ( int j = 0; j < lazyPropertyNames.length; j++ ) {
  			final Object propValue = lazyPropertyTypes[j].assemble(
  					disassembledValues[lazyPropertyNumbers[j]],
  					session,
  					entity
  			);
  			if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) {
  				result = propValue;
  			}
  		}
  
  		LOG.trace( "Done initializing lazy properties" );
  
  		return result;
  	}
  
  	private boolean initializeLazyProperty(
  			final String fieldName,
  			final Object entity,
  			final SessionImplementor session,
  			final Object[] snapshot,
  			final int j,
  			final Object propValue) {
  		setPropertyValue( entity, lazyPropertyNumbers[j], propValue );
  		if ( snapshot != null ) {
  			// object have been loaded with setReadOnly(true); HHH-2236
  			snapshot[lazyPropertyNumbers[j]] = lazyPropertyTypes[j].deepCopy( propValue, factory );
  		}
  		return fieldName.equals( lazyPropertyNames[j] );
  	}
  
  	public boolean isBatchable() {
  		return optimisticLockStyle() == OptimisticLockStyle.NONE
  				|| ( !isVersioned() && optimisticLockStyle() == OptimisticLockStyle.VERSION )
  				|| getFactory().getSessionFactoryOptions().isJdbcBatchVersionedData();
  	}
  
  	public Serializable[] getQuerySpaces() {
  		return getPropertySpaces();
  	}
  
  	public boolean isBatchLoadable() {
  		return batchSize > 1;
  	}
  
  	public String[] getIdentifierColumnNames() {
  		return rootTableKeyColumnNames;
  	}
  
  	public String[] getIdentifierColumnReaders() {
  		return rootTableKeyColumnReaders;
  	}
  
  	public String[] getIdentifierColumnReaderTemplates() {
  		return rootTableKeyColumnReaderTemplates;
  	}
  
  	protected int getIdentifierColumnSpan() {
  		return identifierColumnSpan;
  	}
  
  	protected String[] getIdentifierAliases() {
  		return identifierAliases;
  	}
  
  	public String getVersionColumnName() {
  		return versionColumnName;
  	}
  
  	protected String getVersionedTableName() {
  		return getTableName( 0 );
  	}
  
  	protected boolean[] getSubclassColumnLazyiness() {
  		return subclassColumnLazyClosure;
  	}
  
  	protected boolean[] getSubclassFormulaLazyiness() {
  		return subclassFormulaLazyClosure;
  	}
  
  	/**
  	 * We can't immediately add to the cache if we have formulas
  	 * which must be evaluated, or if we have the possibility of
  	 * two concurrent updates to the same item being merged on
  	 * the database. This can happen if (a) the item is not
  	 * versioned and either (b) we have dynamic update enabled
  	 * or (c) we have multiple tables holding the state of the
  	 * item.
  	 */
  	public boolean isCacheInvalidationRequired() {
  		return hasFormulaProperties() ||
  				( !isVersioned() && ( entityMetamodel.isDynamicUpdate() || getTableSpan() > 1 ) );
  	}
  
  	public boolean isLazyPropertiesCacheable() {
  		return isLazyPropertiesCacheable;
  	}
  
  	public String selectFragment(String alias, String suffix) {
  		return identifierSelectFragment( alias, suffix ) +
  				propertySelectFragment( alias, suffix, false );
  	}
  
  	public String[] getIdentifierAliases(String suffix) {
  		// NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
  		// was toUnqotedAliasStrings( getIdentifierColumnNames() ) before - now tried
  		// to remove that unqoting and missing aliases..
  		return new Alias( suffix ).toAliasStrings( getIdentifierAliases() );
  	}
  
  	public String[] getPropertyAliases(String suffix, int i) {
  		// NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
  		return new Alias( suffix ).toUnquotedAliasStrings( propertyColumnAliases[i] );
  	}
  
  	public String getDiscriminatorAlias(String suffix) {
  		// NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
  		// was toUnqotedAliasStrings( getdiscriminatorColumnName() ) before - now tried
  		// to remove that unqoting and missing aliases..
  		return entityMetamodel.hasSubclasses() ?
  				new Alias( suffix ).toAliasString( getDiscriminatorAlias() ) :
  				null;
  	}
  
  	public String identifierSelectFragment(String name, String suffix) {
  		return new SelectFragment()
  				.setSuffix( suffix )
  				.addColumns( name, getIdentifierColumnNames(), getIdentifierAliases() )
  				.toFragmentString()
  				.substring( 2 ); //strip leading ", "
  	}
  
  
  	public String propertySelectFragment(String tableAlias, String suffix, boolean allProperties) {
  		return propertySelectFragmentFragment( tableAlias, suffix, allProperties ).toFragmentString();
  	}
  
  	public SelectFragment propertySelectFragmentFragment(
  			String tableAlias,
  			String suffix,
  			boolean allProperties) {
  		SelectFragment select = new SelectFragment()
  				.setSuffix( suffix )
  				.setUsedAliases( getIdentifierAliases() );
  
  		int[] columnTableNumbers = getSubclassColumnTableNumberClosure();
  		String[] columnAliases = getSubclassColumnAliasClosure();
  		String[] columnReaderTemplates = getSubclassColumnReaderTemplateClosure();
  		for ( int i = 0; i < getSubclassColumnClosure().length; i++ ) {
  			boolean selectable = ( allProperties || !subclassColumnLazyClosure[i] ) &&
  					!isSubclassTableSequentialSelect( columnTableNumbers[i] ) &&
  					subclassColumnSelectableClosure[i];
  			if ( selectable ) {
  				String subalias = generateTableAlias( tableAlias, columnTableNumbers[i] );
  				select.addColumnTemplate( subalias, columnReaderTemplates[i], columnAliases[i] );
  			}
  		}
  
  		int[] formulaTableNumbers = getSubclassFormulaTableNumberClosure();
  		String[] formulaTemplates = getSubclassFormulaTemplateClosure();
  		String[] formulaAliases = getSubclassFormulaAliasClosure();
  		for ( int i = 0; i < getSubclassFormulaTemplateClosure().length; i++ ) {
  			boolean selectable = ( allProperties || !subclassFormulaLazyClosure[i] )
  					&& !isSubclassTableSequentialSelect( formulaTableNumbers[i] );
  			if ( selectable ) {
  				String subalias = generateTableAlias( tableAlias, formulaTableNumbers[i] );
  				select.addFormula( subalias, formulaTemplates[i], formulaAliases[i] );
  			}
  		}
  
  		if ( entityMetamodel.hasSubclasses() ) {
  			addDiscriminatorToSelect( select, tableAlias, suffix );
  		}
  
  		if ( hasRowId() ) {
  			select.addColumn( tableAlias, rowIdName, ROWID_ALIAS );
  		}
  
  		return select;
  	}
  
  	public Object[] getDatabaseSnapshot(Serializable id, SessionImplementor session)
  			throws HibernateException {
  
  		if ( LOG.isTraceEnabled() ) {
  			LOG.tracev(
  					"Getting current persistent state for: {0}", MessageHelper.infoString(
  							this,
  							id,
  							getFactory()
  					)
  			);
  		}
  
  		try {
  			PreparedStatement ps = session
  					.getJdbcCoordinator()
  					.getStatementPreparer()
  					.prepareStatement( getSQLSnapshotSelectString() );
  			try {
  				getIdentifierType().nullSafeSet( ps, id, 1, session );
  				//if ( isVersioned() ) getVersionType().nullSafeSet( ps, version, getIdentifierColumnSpan()+1, session );
  				ResultSet rs = session.getJdbcCoordinator().getResultSetReturn().extract( ps );
  				try {
  					//if there is no resulting row, return null
  					if ( !rs.next() ) {
  						return null;
  					}
  					//otherwise return the "hydrated" state (ie. associations are not resolved)
  					Type[] types = getPropertyTypes();
  					Object[] values = new Object[types.length];
  					boolean[] includeProperty = getPropertyUpdateability();
  					for ( int i = 0; i < types.length; i++ ) {
  						if ( includeProperty[i] ) {
  							values[i] = types[i].hydrate(
  									rs,
  									getPropertyAliases( "", i ),
  									session,
  									null
  							); //null owner ok??
  						}
  					}
  					return values;
  				}
  				finally {
  					session.getJdbcCoordinator().getResourceRegistry().release( rs, ps );
  				}
  			}
  			finally {
  				session.getJdbcCoordinator().getResourceRegistry().release( ps );
  				session.getJdbcCoordinator().afterStatementExecution();
  			}
  		}
  		catch (SQLException e) {
  			throw getFactory().getSQLExceptionHelper().convert(
  					e,
  					"could not retrieve snapshot: " + MessageHelper.infoString( this, id, getFactory() ),
  					getSQLSnapshotSelectString()
  			);
  		}
  
  	}
  
  	@Override
  	public Serializable getIdByUniqueKey(Serializable key, String uniquePropertyName, SessionImplementor session)
  			throws HibernateException {
  		if ( LOG.isTraceEnabled() ) {
  			LOG.tracef(
  					"resolving unique key [%s] to identifier for entity [%s]",
  					key,
  					getEntityName()
  			);
  		}
  
  		int propertyIndex = getSubclassPropertyIndex( uniquePropertyName );
  		if ( propertyIndex < 0 ) {
  			throw new HibernateException(
  					"Could not determine Type for property [" + uniquePropertyName + "] on entity [" + getEntityName() + "]"
  			);
  		}
  		Type propertyType = getSubclassPropertyType( propertyIndex );
  
  		try {
  			PreparedStatement ps = session
  					.getJdbcCoordinator()
  					.getStatementPreparer()
  					.prepareStatement( generateIdByUniqueKeySelectString( uniquePropertyName ) );
  			try {
  				propertyType.nullSafeSet( ps, key, 1, session );
  				ResultSet rs = session.getJdbcCoordinator().getResultSetReturn().extract( ps );
  				try {
  					//if there is no resulting row, return null
  					if ( !rs.next() ) {
  						return null;
  					}
  					return (Serializable) getIdentifierType().nullSafeGet( rs, getIdentifierAliases(), session, null );
  				}
  				finally {
  					session.getJdbcCoordinator().getResourceRegistry().release( rs, ps );
  				}
  			}
  			finally {
  				session.getJdbcCoordinator().getResourceRegistry().release( ps );
  				session.getJdbcCoordinator().afterStatementExecution();
  			}
  		}
  		catch (SQLException e) {
  			throw getFactory().getSQLExceptionHelper().convert(
  					e,
  					String.format(
  							"could not resolve unique property [%s] to identifier for entity [%s]",
  							uniquePropertyName,
  							getEntityName()
  					),
  					getSQLSnapshotSelectString()
  			);
  		}
  
  	}
  
  	protected String generateIdByUniqueKeySelectString(String uniquePropertyName) {
  		Select select = new Select( getFactory().getDialect() );
  
  		if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {
  			select.setComment( "resolve id by unique property [" + getEntityName() + "." + uniquePropertyName + "]" );
  		}
  
  		final String rooAlias = getRootAlias();
  
  		select.setFromClause( fromTableFragment( rooAlias ) + fromJoinFragment( rooAlias, true, false ) );
  
  		SelectFragment selectFragment = new SelectFragment();
  		selectFragment.addColumns( rooAlias, getIdentifierColumnNames(), getIdentifierAliases() );
  		select.setSelectClause( selectFragment );
  
  		StringBuilder whereClauseBuffer = new StringBuilder();
  		final int uniquePropertyIndex = getSubclassPropertyIndex( uniquePropertyName );
  		final String uniquePropertyTableAlias = generateTableAlias(
  				rooAlias,
  				getSubclassPropertyTableNumber( uniquePropertyIndex )
  		);
  		String sep = "";
  		for ( String columnTemplate : getSubclassPropertyColumnReaderTemplateClosure()[uniquePropertyIndex] ) {
  			if ( columnTemplate == null ) {
  				continue;
  			}
  			final String columnReference = StringHelper.replace(
  					columnTemplate,
  					Template.TEMPLATE,
  					uniquePropertyTableAlias
  			);
  			whereClauseBuffer.append( sep ).append( columnReference ).append( "=?" );
  			sep = " and ";
  		}
  		for ( String formulaTemplate : getSubclassPropertyFormulaTemplateClosure()[uniquePropertyIndex] ) {
  			if ( formulaTemplate == null ) {
  				continue;
  			}
  			final String formulaReference = StringHelper.replace(
  					formulaTemplate,
  					Template.TEMPLATE,
  					uniquePropertyTableAlias
  			);
  			whereClauseBuffer.append( sep ).append( formulaReference ).append( "=?" );
  			sep = " and ";
  		}
  		whereClauseBuffer.append( whereJoinFragment( rooAlias, true, false ) );
  
  		select.setWhereClause( whereClauseBuffer.toString() );
  
  		return select.setOuterJoins( "", "" ).toStatementString();
  	}
  
  
  	/**
  	 * Generate the SQL that selects the version number by id
  	 */
  	protected String generateSelectVersionString() {
  		SimpleSelect select = new SimpleSelect( getFactory().getDialect() )
  				.setTableName( getVersionedTableName() );
  		if ( isVersioned() ) {
  			select.addColumn( versionColumnName );
  		}
  		else {
  			select.addColumns( rootTableKeyColumnNames );
  		}
  		if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {
  			select.setComment( "get version " + getEntityName() );
  		}
  		return select.addCondition( rootTableKeyColumnNames, "=?" ).toStatementString();
  	}
  
  	public boolean[] getPropertyUniqueness() {
  		return propertyUniqueness;
  	}
  
  	protected String generateInsertGeneratedValuesSelectString() {
  		return generateGeneratedValuesSelectString( GenerationTiming.INSERT );
  	}
  
  	protected String generateUpdateGeneratedValuesSelectString() {
  		return generateGeneratedValuesSelectString( GenerationTiming.ALWAYS );
  	}
  
  	private String generateGeneratedValuesSelectString(final GenerationTiming generationTimingToMatch) {
  		Select select = new Select( getFactory().getDialect() );
  
  		if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {
  			select.setComment( "get generated state " + getEntityName() );
  		}
  
  		String[] aliasedIdColumns = StringHelper.qualify( getRootAlias(), getIdentifierColumnNames() );
  
  		// Here we render the select column list based on the properties defined as being generated.
  		// For partial component generation, we currently just re-select the whole component
  		// rather than trying to handle the individual generated portions.
  		String selectClause = concretePropertySelectFragment(
  				getRootAlias(),
  				new InclusionChecker() {
  					@Override
  					public boolean includeProperty(int propertyNumber) {
  						final InDatabaseValueGenerationStrategy generationStrategy
  								= entityMetamodel.getInDatabaseValueGenerationStrategies()[propertyNumber];
  						return generationStrategy != null
  								&& timingsMatch( generationStrategy.getGenerationTiming(), generationTimingToMatch );
  					}
  				}
  		);
  		selectClause = selectClause.substring( 2 );
  
  		String fromClause = fromTableFragment( getRootAlias() ) +
  				fromJoinFragment( getRootAlias(), true, false );
  
  		String whereClause = new StringBuilder()
  				.append( StringHelper.join( "=? and ", aliasedIdColumns ) )
  				.append( "=?" )
  				.append( whereJoinFragment( getRootAlias(), true, false ) )
  				.toString();
  
  		return select.setSelectClause( selectClause )
  				.setFromClause( fromClause )
  				.setOuterJoins( "", "" )
  				.setWhereClause( whereClause )
  				.toStatementString();
  	}
  
  	protected static interface InclusionChecker {
  		public boolean includeProperty(int propertyNumber);
  	}
  
  	protected String concretePropertySelectFragment(String alias, final boolean[] includeProperty) {
  		return concretePropertySelectFragment(
  				alias,
  				new InclusionChecker() {
  					public boolean includeProperty(int propertyNumber) {
  						return includeProperty[propertyNumber];
  					}
  				}
  		);
  	}
  
  	protected String concretePropertySelectFragment(String alias, InclusionChecker inclusionChecker) {
  		int propertyCount = getPropertyNames().length;
  		int[] propertyTableNumbers = getPropertyTableNumbersInSelect();
  		SelectFragment frag = new SelectFragment();
  		for ( int i = 0; i < propertyCount; i++ ) {
  			if ( inclusionChecker.includeProperty( i ) ) {
  				frag.addColumnTemplates(
  						generateTableAlias( alias, propertyTableNumbers[i] ),
  						propertyColumnReaderTemplates[i],
  						propertyColumnAliases[i]
  				);
  				frag.addFormulas(
  						generateTableAlias( alias, propertyTableNumbers[i] ),
  						propertyColumnFormulaTemplates[i],
  						propertyColumnAliases[i]
  				);
  			}
  		}
  		return frag.toFragmentString();
  	}
  
  	protected String generateSnapshotSelectString() {
  
  		//TODO: should we use SELECT .. FOR UPDATE?
  
  		Select select = new Select( getFactory().getDialect() );
  
  		if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {
  			select.setComment( "get current state " + getEntityName() );
  		}
  
  		String[] aliasedIdColumns = StringHelper.qualify( getRootAlias(), getIdentifierColumnNames() );
  		String selectClause = StringHelper.join( ", ", aliasedIdColumns ) +
  				concretePropertySelectFragment( getRootAlias(), getPropertyUpdateability() );
  
  		String fromClause = fromTableFragment( getRootAlias() ) +
  				fromJoinFragment( getRootAlias(), true, false );
  
  		String whereClause = new StringBuilder()
  				.append(
  						StringHelper.join(
  								"=? and ",
  								aliasedIdColumns
  						)
  				)
  				.append( "=?" )
  				.append( whereJoinFragment( getRootAlias(), true, false ) )
  				.toString();
  
  		/*if ( isVersioned() ) {
  			where.append(" and ")
  				.append( getVersionColumnName() )
  				.append("=?");
  		}*/
  
  		return select.setSelectClause( selectClause )
  				.setFromClause( fromClause )
  				.setOuterJoins( "", "" )
  				.setWhereClause( whereClause )
  				.toStatementString();
  	}
  
  	public Object forceVersionIncrement(Serializable id, Object currentVersion, SessionImplementor session) {
  		if ( !isVersioned() ) {
  			throw new AssertionFailure( "cannot force version increment on non-versioned entity" );
  		}
  
  		if ( isVersionPropertyGenerated() ) {
  			// the difficulty here is exactly what do we update in order to
  			// force the version to be incremented in the db...
  			throw new HibernateException( "LockMode.FORCE is currently not supported for generated version properties" );
  		}
  
  		Object nextVersion = getVersionType().next( currentVersion, session );
  		if ( LOG.isTraceEnabled() ) {
  			LOG.trace(
  					"Forcing version increment [" + MessageHelper.infoString( this, id, getFactory() ) + "; "
  							+ getVersionType().toLoggableString( currentVersion, getFactory() ) + " -> "
  							+ getVersionType().toLoggableString( nextVersion, getFactory() ) + "]"
  			);
  		}
  
  		// todo : cache this sql...
  		String versionIncrementString = generateVersionIncrementUpdateString();
  		PreparedStatement st = null;
  		try {
  			st = session
  					.getJdbcCoordinator()
  					.getStatementPreparer()
  					.prepareStatement( versionIncrementString, false );
  			try {
  				getVersionType().nullSafeSet( st, nextVersion, 1, session );
  				getIdentifierType().nullSafeSet( st, id, 2, session );
  				getVersionType().nullSafeSet( st, currentVersion, 2 + getIdentifierColumnSpan(), session );
  				int rows = session.getJdbcCoordinator().getResultSetReturn().executeUpdate( st );
  				if ( rows != 1 ) {
  					throw new StaleObjectStateException( getEntityName(), id );
  				}
  			}
  			finally {
  				session.getJdbcCoordinator().getResourceRegistry().release( st );
  				session.getJdbcCoordinator().afterStatementExecution();
  			}
  		}
  		catch (SQLException sqle) {
  			throw getFactory().getSQLExceptionHelper().convert(
  					sqle,
  					"could not retrieve version: " +
  							MessageHelper.infoString( this, id, getFactory() ),
  					getVersionSelectString()
  			);
  		}
  
  		return nextVersion;
  	}
  
  	private String generateVersionIncrementUpdateString() {
  		Update update = new Update( getFactory().getDialect() );
  		update.setTableName( getTableName( 0 ) );
  		if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {
  			update.setComment( "forced version increment" );
  		}
  		update.addColumn( getVersionColumnName() );
  		update.addPrimaryKeyColumns( getIdentifierColumnNames() );
  		update.setVersionColumnName( getVersionColumnName() );
  		return update.toStatementString();
  	}
  
  	/**
  	 * Retrieve the version number
  	 */
  	public Object getCurrentVersion(Serializable id, SessionImplementor session) throws HibernateException {
  
  		if ( LOG.isTraceEnabled() ) {
  			LOG.tracev( "Getting version: {0}", MessageHelper.infoString( this, id, getFactory() ) );
  		}
  
  		try {
  			PreparedStatement st = session
  					.getJdbcCoordinator()
  					.getStatementPreparer()
  					.prepareStatement( getVersionSelectString() );
  			try {
  				getIdentifierType().nullSafeSet( st, id, 1, session );
  				ResultSet rs = session.getJdbcCoordinator().getResultSetReturn().extract( st );
  				try {
  					if ( !rs.next() ) {
  						return null;
  					}
  					if ( !isVersioned() ) {
  						return this;
  					}
  					return getVersionType().nullSafeGet( rs, getVersionColumnName(), session, null );
  				}
  				finally {
  					session.getJdbcCoordinator().getResourceRegistry().release( rs, st );
  				}
  			}
  			finally {
  				session.getJdbcCoordinator().getResourceRegistry().release( st );
  				session.getJdbcCoordinator().afterStatementExecution();
  			}
  		}
  		catch (SQLException e) {
  			throw getFactory().getSQLExceptionHelper().convert(
  					e,
  					"could not retrieve version: " + MessageHelper.infoString( this, id, getFactory() ),
  					getVersionSelectString()
  			);
  		}
  	}
  
  	protected void initLockers() {
  		lockers.put( LockMode.READ, generateLocker( LockMode.READ ) );
  		lockers.put( LockMode.UPGRADE, generateLocker( LockMode.UPGRADE ) );
  		lockers.put( LockMode.UPGRADE_NOWAIT, generateLocker( LockMode.UPGRADE_NOWAIT ) );
  		lockers.put( LockMode.UPGRADE_SKIPLOCKED, generateLocker( LockMode.UPGRADE_SKIPLOCKED ) );
  		lockers.put( LockMode.FORCE, generateLocker( LockMode.FORCE ) );
  		lockers.put( LockMode.PESSIMISTIC_READ, generateLocker( LockMode.PESSIMISTIC_READ ) );
  		lockers.put( LockMode.PESSIMISTIC_WRITE, generateLocker( LockMode.PESSIMISTIC_WRITE ) );
  		lockers.put( LockMode.PESSIMISTIC_FORCE_INCREMENT, generateLocker( LockMode.PESSIMISTIC_FORCE_INCREMENT ) );
  		lockers.put( LockMode.OPTIMISTIC, generateLocker( LockMode.OPTIMISTIC ) );
  		lockers.put( LockMode.OPTIMISTIC_FORCE_INCREMENT, generateLocker( LockMode.OPTIMISTIC_FORCE_INCREMENT ) );
  	}
  
  	protected LockingStrategy generateLocker(LockMode lockMode) {
  		return factory.getDialect().getLockingStrategy( this, lockMode );
  	}
  
  	private LockingStrategy getLocker(LockMode lockMode) {
  		return (LockingStrategy) lockers.get( lockMode );
  	}
  
  	public void lock(
  			Serializable id,
  			Object version,
  			Object object,
  			LockMode lockMode,
  			SessionImplementor session) throws HibernateException {
  		getLocker( lockMode ).lock( id, version, object, LockOptions.WAIT_FOREVER, session );
  	}
  
  	public void lock(
  			Serializable id,
  			Object version,
  			Object object,
  			LockOptions lockOptions,
  			SessionImplementor session) throws HibernateException {
  		getLocker( lockOptions.getLockMode() ).lock( id, version, object, lockOptions.getTimeOut(), session );
  	}
  
  	public String getRootTableName() {
  		return getSubclassTableName( 0 );
  	}
  
  	public String getRootTableAlias(String drivingAlias) {
  		return drivingAlias;
  	}
  
  	public String[] getRootTableIdentifierColumnNames() {
  		return getRootTableKeyColumnNames();
  	}
  
  	public String[] toColumns(String alias, String propertyName) throws QueryException {
  		return propertyMapping.toColumns( alias, propertyName );
  	}
  
  	public String[] toColumns(String propertyName) throws QueryException {
  		return propertyMapping.getColumnNames( propertyName );
  	}
  
  	public Type toType(String propertyName) throws QueryException {
  		return propertyMapping.toType( propertyName );
  	}
  
  	public String[] getPropertyColumnNames(String propertyName) {
  		return propertyMapping.getColumnNames( propertyName );
  	}
  
  	/**
  	 * Warning:
  	 * When there are duplicated property names in the subclasses
  	 * of the class, this method may return the wrong table
  	 * number for the duplicated subclass property (note that
  	 * SingleTableEntityPersister defines an overloaded form
  	 * which takes the entity name.
  	 */
  	public int getSubclassPropertyTableNumber(String propertyPath) {
  		String rootPropertyName = StringHelper.root( propertyPath );
  		Type type = propertyMapping.toType( rootPropertyName );
  		if ( type.isAssociationType() ) {
  			AssociationType assocType = (AssociationType) type;
  			if ( assocType.useLHSPrimaryKey() ) {
  				// performance op to avoid the array search
  				return 0;
  			}
  			else if ( type.isCollectionType() ) {
  				// properly handle property-ref-based associations
  				rootPropertyName = assocType.getLHSPropertyName();
  			}
  		}
  		//Enable for HHH-440, which we don't like:
  		/*if ( type.isComponentType() && !propertyName.equals(rootPropertyName) ) {
  			String unrooted = StringHelper.unroot(propertyName);
  			int idx = ArrayHelper.indexOf( getSubclassColumnClosure(), unrooted );
  			if ( idx != -1 ) {
  				return getSubclassColumnTableNumberClosure()[idx];
  			}
  		}*/
  		int index = ArrayHelper.indexOf(
  				getSubclassPropertyNameClosure(),
  				rootPropertyName
  		); //TODO: optimize this better!
  		return index == -1 ? 0 : getSubclassPropertyTableNumber( index );
  	}
  
  	public Declarer getSubclassPropertyDeclarer(String propertyPath) {
  		int tableIndex = getSubclassPropertyTableNumber( propertyPath );
  		if ( tableIndex == 0 ) {
  			return Declarer.CLASS;
  		}
  		else if ( isClassOrSuperclassTable( tableIndex ) ) {
  			return Declarer.SUPERCLASS;
  		}
  		else {
  			return Declarer.SUBCLASS;
  		}
  	}
  
  	private DiscriminatorMetadata discriminatorMetadata;
  
  	public DiscriminatorMetadata getTypeDiscriminatorMetadata() {
  		if ( discriminatorMetadata == null ) {
  			discriminatorMetadata = buildTypeDiscriminatorMetadata();
  		}
  		return discriminatorMetadata;
  	}
  
  	private DiscriminatorMetadata buildTypeDiscriminatorMetadata() {
  		return new DiscriminatorMetadata() {
  			public String getSqlFragment(String sqlQualificationAlias) {
  				return toColumns( sqlQualificationAlias, ENTITY_CLASS )[0];
  			}
  
  			public Type getResolutionType() {
  				return new DiscriminatorType( getDiscriminatorType(), AbstractEntityPersister.this );
  			}
  		};
  	}
  
  	public static String generateTableAlias(String rootAlias, int tableNumber) {
  		if ( tableNumber == 0 ) {
  			return rootAlias;
  		}
  