Open Source Repository

Home /hibernate/hibernate-3.2.6.ga | Repository Home



org/hibernate/persister/entity/AbstractEntityPersister.java
//$Id: AbstractEntityPersister.java 14102 2007-10-18 22:28:23Z gbadner $
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.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.Comparator;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.AssertionFailure;
import org.hibernate.EntityMode;
import org.hibernate.FetchMode;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.StaleObjectStateException;
import org.hibernate.StaleStateException;
import org.hibernate.jdbc.Expectation;
import org.hibernate.jdbc.Expectations;
import org.hibernate.jdbc.TooManyRowsAffectedException;
import org.hibernate.dialect.lock.LockingStrategy;
import org.hibernate.cache.CacheConcurrencyStrategy;
import org.hibernate.cache.CacheKey;
import org.hibernate.cache.entry.CacheEntry;
import org.hibernate.cache.entry.CacheEntryStructure;
import org.hibernate.cache.entry.StructuredCacheEntry;
import org.hibernate.cache.entry.UnstructuredCacheEntry;
import org.hibernate.engine.CascadeStyle;
import org.hibernate.engine.CascadingAction;
import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.Mapping;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.Versioning;
import org.hibernate.engine.ExecuteUpdateResultCheckStyle;
import org.hibernate.engine.EntityKey;
import org.hibernate.engine.ValueInclusion;
import org.hibernate.exception.JDBCExceptionHelper;
import org.hibernate.id.IdentifierGenerator;
import org.hibernate.id.PostInsertIdentifierGenerator;
import org.hibernate.id.PostInsertIdentityPersister;
import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
import org.hibernate.id.insert.Binder;
import org.hibernate.intercept.LazyPropertyInitializer;
import org.hibernate.intercept.FieldInterceptionHelper;
import org.hibernate.intercept.FieldInterceptor;
import org.hibernate.loader.entity.BatchingEntityLoader;
import org.hibernate.loader.entity.CascadeEntityLoader;
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.metadata.ClassMetadata;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.property.BackrefPropertyAccessor;
import org.hibernate.sql.Alias;
import org.hibernate.sql.Delete;
import org.hibernate.sql.Insert;
import org.hibernate.sql.JoinFragment;
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.entity.EntityMetamodel;
import org.hibernate.tuple.entity.EntityTuplizer;
import org.hibernate.tuple.Tuplizer;
import org.hibernate.type.AbstractComponentType;
import org.hibernate.type.AssociationType;
import org.hibernate.type.EntityType;
import org.hibernate.type.Type;
import org.hibernate.type.TypeFactory;
import org.hibernate.type.VersionType;
import org.hibernate.util.ArrayHelper;
import org.hibernate.util.CollectionHelper;
import org.hibernate.util.FilterHelper;
import org.hibernate.util.StringHelper;

/**
 * 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 Log log = LogFactory.getLogAbstractEntityPersister.class );

  public static final String ENTITY_CLASS = "class";

  // moved up from AbstractEntityPersister ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  private final SessionFactoryImplementor factory;
  private final CacheConcurrencyStrategy cache;
  private final boolean isLazyPropertiesCacheable;
  private final CacheEntryStructure cacheEntryStructure;
  private final EntityMetamodel entityMetamodel;
  private final Map entityNameBySubclass = new HashMap();
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  private final String[] rootTableKeyColumnNames;
  private final String[] identifierAliases;
  private final int identifierColumnSpan;
  private final String versionColumnName;
  private final boolean hasFormulaProperties;
  private final int batchSize;
  private final boolean hasSubselectLoadableCollections;
  protected final String rowIdName;

  private final Set lazyProperties;

  // 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 boolean[][] propertyColumnUpdateable;
  private final boolean[][] propertyColumnInsertable;
  private final boolean[] propertyUniqueness;
  private final boolean[] propertySelectable;

  //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 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[] 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 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 String sqlLazySelectString;

  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 String temporaryIdTableName;
  private final String temporaryIdTableDDL;

  private final Map subclassPropertyAliases = new HashMap();
  private final Map subclassPropertyColumnNames = new HashMap();

  protected final BasicEntityPropertyMapping propertyMapping;

  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 aliasthrows MappingException;

  private static final String DISCRIMINATOR_ALIAS = "clazz_";

  public String getDiscriminatorColumnName() {
    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.arraycopysqlUpdateStrings, 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;
    for int i = 1; i < getTableSpan(); i++ ) {
      result[i= sqlLazyUpdateStrings[i];
    }
    return result;
  }

  protected String getSQLSnapshotSelectString() {
    return sqlSnapshotSelectString;
  }

  protected String getSQLLazySelectString() {
    return sqlLazySelectString;
  }

  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 booleangetTableSpan() ];
      for int i = 0; i < dirtyProperties.length; i++ ) {
        int property = dirtyProperties[i];
        int table = propertyTableNumbers[property];
        tableUpdateNeeded[table= tableUpdateNeeded[table||
            getPropertyColumnSpan(property&& updateability[property] );
      }
      if isVersioned() ) {
        tableUpdateNeeded[0= tableUpdateNeeded[0||
          Versioning.isVersionIncrementRequireddirtyProperties, hasDirtyCollection, getPropertyVersionability() );
      }
      return tableUpdateNeeded;
    }
  }

  public boolean hasRowId() {
    return rowIdName != null;
  }

  public AbstractEntityPersister(
      final PersistentClass persistentClass,
      final CacheConcurrencyStrategy cache,
      final SessionFactoryImplementor factory)
  throws HibernateException {

    // moved up from AbstractEntityPersister ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    this.factory = factory;
    this.cache = cache;
    isLazyPropertiesCacheable = persistentClass.isLazyPropertiesCacheable();
    this.cacheEntryStructure = factory.getSettings().isStructuredCacheEntriesEnabled() ?
        (CacheEntryStructurenew StructuredCacheEntry(this:
        (CacheEntryStructurenew UnstructuredCacheEntry();

    this.entityMetamodel = new EntityMetamodelpersistentClass, factory );

    if persistentClass.hasPojoRepresentation() ) {
      //TODO: this is currently specific to pojos, but need to be available for all entity-modes
      Iterator iter = persistentClass.getSubclassIterator();
      while iter.hasNext() ) {
        PersistentClass pc = PersistentClass iter.next();
        entityNameBySubclass.putpc.getMappedClass(), pc.getEntityName() );
      }
    }
    // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    int batch = persistentClass.getBatchSize();
    if batch == -) {
      batch = factory.getSettings().getDefaultBatchFetchSize();
    }
    batchSize = batch;
    hasSubselectLoadableCollections = persistentClass.hasSubselectLoadableCollections();

    propertyMapping = new BasicEntityPropertyMappingthis );

    // IDENTIFIER

    identifierColumnSpan = persistentClass.getIdentifier().getColumnSpan();
    rootTableKeyColumnNames = 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.getQuotedNamefactory.getDialect() );
      identifierAliases[i= col.getAliasfactory.getDialect(), persistentClass.getRootTable() );
      i++;
    }

    // VERSION

    if persistentClass.isVersioned() ) {
      versionColumnName = ( ( Column persistentClass.getVersion().getColumnIterator().next() ).getQuotedNamefactory.getDialect() );
    }
    else {
      versionColumnName = null;
    }

    //WHERE STRING

    sqlWhereString = StringHelper.isNotEmptypersistentClass.getWhere() ) "( " + persistentClass.getWhere() ") " null;
    sqlWhereStringTemplate = sqlWhereString == null ?
        null :
        Template.renderWhereStringTemplatesqlWhereString, factory.getDialect(), factory.getSqlFunctionRegistry() );

    // PROPERTIES

    final boolean lazyAvailable = isInstrumented(EntityMode.POJO);

    int hydrateSpan = entityMetamodel.getPropertySpan();
    propertyColumnSpans = new int[hydrateSpan];
    propertySubclassNames = new String[hydrateSpan];
    propertyColumnAliases = new String[hydrateSpan][];
    propertyColumnNames = new String[hydrateSpan][];
    propertyColumnFormulaTemplates = new String[hydrateSpan][];
    propertyUniqueness = new boolean[hydrateSpan];
    propertySelectable = new boolean[hydrateSpan];
    propertyColumnUpdateable = new boolean[hydrateSpan][];
    propertyColumnInsertable = new boolean[hydrateSpan][];
    HashSet thisClassProperties = new HashSet();

    lazyProperties = 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.addprop );

      int span = prop.getColumnSpan();
      propertyColumnSpans[i= span;
      propertySubclassNames[i= prop.getPersistentClass().getEntityName();
      String[] colNames = new String[span];
      String[] colAliases = new String[span];
      String[] templates = new String[span];
      Iterator colIter = prop.getColumnIterator();
      int k = 0;
      while colIter.hasNext() ) {
        Selectable thing = Selectable colIter.next();
        colAliases[k= thing.getAliasfactory.getDialect() , prop.getValue().getTable() );
        if thing.isFormula() ) {
          foundFormula = true;
          templates[k= thing.getTemplatefactory.getDialect(), factory.getSqlFunctionRegistry() );
        }
        else {
          colNames[k= thing.getTemplatefactory.getDialect(), factory.getSqlFunctionRegistry() );
        }
        k++;
      }
      propertyColumnNames[i= colNames;
      propertyColumnFormulaTemplates[i= templates;
      propertyColumnAliases[i= colAliases;

      if lazyAvailable && prop.isLazy() ) {
        lazyProperties.addprop.getName() );
        lazyNames.addprop.getName() );
        lazyNumbers.addnew Integer) );
        lazyTypes.addprop.getValue().getType() );
        lazyColAliases.addcolAliases );
      }

      propertyColumnUpdateable[i= prop.getValue().getColumnUpdateability();
      propertyColumnInsertable[i= prop.getValue().getColumnInsertability();

      propertySelectable[i= prop.isSelectable();

      propertyUniqueness[i= prop.getValue().isAlternateUniqueKey();

      i++;

    }
    hasFormulaProperties = foundFormula;
    lazyPropertyColumnAliases = ArrayHelper.to2DStringArraylazyColAliases );
    lazyPropertyNames = ArrayHelper.toStringArraylazyNames );
    lazyPropertyNumbers = ArrayHelper.toIntArraylazyNumbers );
    lazyPropertyTypes = ArrayHelper.toTypeArraylazyTypes );

    // SUBCLASS PROPERTY CLOSURE

    ArrayList columns = new ArrayList();
    ArrayList columnsLazy = 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 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.addprop.getName() );
      classes.addprop.getPersistentClass().getEntityName() );
      boolean isDefinedBySubclass = !thisClassProperties.containsprop );
      definedBySubclass.addBoolean.valueOfisDefinedBySubclass ) );
      propNullables.addBoolean.valueOfprop.isOptional() || isDefinedBySubclass ) )//TODO: is this completely correct?
      types.addprop.getType() );

      Iterator colIter = prop.getColumnIterator();
      String[] cols = 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.valueOfprop.isLazy() && lazyAvailable );
      while colIter.hasNext() ) {
        Selectable thing = Selectable colIter.next();
        if thing.isFormula() ) {
          String template = thing.getTemplatefactory.getDialect(), factory.getSqlFunctionRegistry() );
          formnos[l= formulaTemplates.size();
          colnos[l= -1;
          formulaTemplates.addtemplate );
          forms[l= template;
          formulas.addthing.getTextfactory.getDialect() ) );
          formulaAliases.addthing.getAliasfactory.getDialect() ) );
          formulasLazy.addlazy );
        }
        else {
          String colName = thing.getTemplatefactory.getDialect(), factory.getSqlFunctionRegistry() );
          colnos[l= columns.size()//before add :-)
          formnos[l= -1;
          columns.addcolName );
          cols[l= colName;
          aliases.addthing.getAliasfactory.getDialect(), prop.getValue().getTable() ) );
          columnsLazy.addlazy );
          columnSelectables.addBoolean.valueOfprop.isSelectable() ) );
        }
        l++;
      }
      propColumns.addcols );
      templates.addforms );
      propColumnNumbers.addcolnos );
      propFormulaNumbers.addformnos );

      joinedFetchesList.addprop.getValue().getFetchMode() );
      cascades.addprop.getCascadeStyle() );
    }
    subclassColumnClosure = ArrayHelper.toStringArraycolumns );
    subclassColumnAliasClosure = ArrayHelper.toStringArrayaliases );
    subclassColumnLazyClosure = ArrayHelper.toBooleanArraycolumnsLazy );
    subclassColumnSelectableClosure = ArrayHelper.toBooleanArraycolumnSelectables );

    subclassFormulaClosure = ArrayHelper.toStringArrayformulas );
    subclassFormulaTemplateClosure = ArrayHelper.toStringArrayformulaTemplates );
    subclassFormulaAliasClosure = ArrayHelper.toStringArrayformulaAliases );
    subclassFormulaLazyClosure = ArrayHelper.toBooleanArrayformulasLazy );

    subclassPropertyNameClosure = ArrayHelper.toStringArraynames );
    subclassPropertySubclassNameClosure = ArrayHelper.toStringArrayclasses );
    subclassPropertyTypeClosure = ArrayHelper.toTypeArraytypes );
    subclassPropertyNullabilityClosure = ArrayHelper.toBooleanArraypropNullables );
    subclassPropertyFormulaTemplateClosure = ArrayHelper.to2DStringArraytemplates );
    subclassPropertyColumnNameClosure = ArrayHelper.to2DStringArraypropColumns );
    subclassPropertyColumnNumberClosure = ArrayHelper.to2DIntArraypropColumnNumbers );
    subclassPropertyFormulaNumberClosure = ArrayHelper.to2DIntArraypropFormulaNumbers );

    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() ).booleanValue();
    }

    // Handle any filters applied to the class level
    filterHelper = new FilterHelperpersistentClass.getFilterMap(), factory.getDialect(), factory.getSqlFunctionRegistry() );

    temporaryIdTableName = persistentClass.getTemporaryIdTableName();
    temporaryIdTableDDL = persistentClass.getTemporaryIdTableDDL();
  }

  protected String generateLazySelectString() {

    if !entityMetamodel.hasLazyProperties() ) {
      return null;
    }

    HashSet tableNumbers = new HashSet();
    ArrayList columnNumbers = new ArrayList();
    ArrayList formulaNumbers = new ArrayList();
    for int i = 0; i < lazyPropertyNames.length; i++ ) {
      // 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 = getSubclassPropertyIndexlazyPropertyNames[i] );

      int tableNumber = getSubclassPropertyTableNumberpropertyNumber );
      tableNumbers.addnew IntegertableNumber ) );

      int[] colNumbers = subclassPropertyColumnNumberClosure[propertyNumber];
      for int j = 0; j < colNumbers.length; j++ ) {
        if colNumbers[j]!=-) {
          columnNumbers.addnew IntegercolNumbers[j] ) );
        }
      }
      int[] formNumbers = subclassPropertyFormulaNumberClosure[propertyNumber];
      for int j = 0; j < formNumbers.length; j++ ) {
        if formNumbers[j]!=-) {
          formulaNumbers.addnew IntegerformNumbers[j] ) );
        }
      }
    }

    if columnNumbers.size()==&& formulaNumbers.size()==) {
      // only one-to-one is lazy fetched
      return null;
    }

    return renderSelectArrayHelper.toIntArraytableNumbers ),
        ArrayHelper.toIntArraycolumnNumbers ),
        ArrayHelper.toIntArrayformulaNumbers ) );

  }

  public Object initializeLazyProperty(String fieldName, Object entity, SessionImplementor session)
      throws HibernateException {

    final Serializable id = session.getContextEntityIdentifierentity );

    final EntityEntry entry = session.getPersistenceContext().getEntryentity );
    if entry == null ) {
      throw new HibernateException"entity is not associated with the session: " + id );
    }

    if log.isTraceEnabled() ) {
      log.trace(
          "initializing lazy properties of: " +
          MessageHelper.infoStringthis, id, getFactory() ) +
          ", field access: " + fieldName
        );
    }

    if hasCache() ) {
      CacheKey cacheKey = new CacheKey(id, getIdentifierType(), getEntityName(), session.getEntityMode(), getFactory() );
      Object ce = getCache().getcacheKey, session.getTimestamp() );
      if (ce!=null) {
        CacheEntry cacheEntry = (CacheEntrygetCacheEntryStructure().destructure(ce, factory);
        if !cacheEntry.areLazyPropertiesUnfetched() ) {
          //note early exit here:
          return initializeLazyPropertiesFromCachefieldName, entity, session, entry, cacheEntry );
        }
      }
    }

    return initializeLazyPropertiesFromDatastorefieldName, entity, session, id, entry );

  }

  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");
    }

    log.trace("initializing lazy properties from datastore");

    try {

      Object result = null;
      PreparedStatement ps = null;
      try {
        final String lazySelect = getSQLLazySelectString();
        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.getBatcher().prepareSelectStatement(lazySelect);
            getIdentifierType().nullSafeSetps, id, 1, session );
            rs = ps.executeQuery();
            rs.next();
          }
          final Object[] snapshot = entry.getLoadedState();
          for int j = 0; j < lazyPropertyNames.length; j++ ) {
            Object propValue = lazyPropertyTypes[j].nullSafeGetrs, lazyPropertyColumnAliases[j], session, entity );
            if initializeLazyPropertyfieldName, entity, session, snapshot, j, propValue ) ) {
              result = propValue;
            }
          }
        }
        finally {
          if rs != null ) {
            rs.close();
          }
        }
      }
      finally {
        if ps != null ) {
          session.getBatcher().closeStatementps );
        }
      }

      log.trace"done initializing lazy properties" );

      return result;

    }
    catch SQLException sqle ) {
      throw JDBCExceptionHelper.convert(
          getFactory().getSQLExceptionConverter(),
          sqle,
          "could not initialize lazy properties: " +
          MessageHelper.infoStringthis, id, getFactory() ),
          getSQLLazySelectString()
        );
    }
  }

  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(
          disassembledValueslazyPropertyNumbers[j] ],
          session,
          entity
        );
      if initializeLazyPropertyfieldName, 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) {
    setPropertyValueentity, lazyPropertyNumbers[j], propValue, session.getEntityMode() );
    if (snapshot != null) {
      // object have been loaded with setReadOnly(true); HHH-2236
      snapshotlazyPropertyNumbers[j] ] = lazyPropertyTypes[j].deepCopypropValue, session.getEntityMode(), factory );
    }
    return fieldName.equalslazyPropertyNames[j] );
  }

  public boolean isBatchable() {
    return optimisticLockMode()==Versioning.OPTIMISTIC_LOCK_NONE ||
      !isVersioned() && optimisticLockMode()==Versioning.OPTIMISTIC_LOCK_VERSION ||
      getFactory().getSettings().isJdbcBatchVersionedData();
  }

  public Serializable[] getQuerySpaces() {
    return getPropertySpaces();
  }

  protected Set getLazyProperties() {
    return lazyProperties;
  }

  public boolean isBatchLoadable() {
    return batchSize > 1;
  }

  public String[] getIdentifierColumnNames() {
    return rootTableKeyColumnNames;
  }

  protected int getIdentifierColumnSpan() {
    return identifierColumnSpan;
  }

  protected String[] getIdentifierAliases() {
    return identifierAliases;
  }

  public String getVersionColumnName() {
    return versionColumnName;
  }

  protected String getVersionedTableName() {
    return getTableName);
  }

  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() ) );
  }

  public boolean isLazyPropertiesCacheable() {
    return isLazyPropertiesCacheable;
  }

  public String selectFragment(String alias, String suffix) {
    return identifierSelectFragmentalias, suffix +
        propertySelectFragmentalias, suffix, false );
  }

  public String[] getIdentifierAliases(String suffix) {
    // NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
    // was toUnqotedAliasStrings( getIdentiferColumnNames() ) before - now tried
    // to remove that unqoting and missing aliases..
    return new Aliassuffix ).toAliasStringsgetIdentifierAliases() );
  }

  public String[] getPropertyAliases(String suffix, int i) {
    // NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
    return new Aliassuffix ).toUnquotedAliasStringspropertyColumnAliases[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 Aliassuffix ).toAliasStringgetDiscriminatorAlias() ) :
        null;
  }

  public String identifierSelectFragment(String name, String suffix) {
    return new SelectFragment()
        .setSuffixsuffix )
        .addColumnsname, getIdentifierColumnNames(), getIdentifierAliases() )
        .toFragmentString()
        .substring)//strip leading ", "
  }


  public String propertySelectFragment(String name, String suffix, boolean allProperties) {

    SelectFragment select = new SelectFragment()
        .setSuffixsuffix )
        .setUsedAliasesgetIdentifierAliases() );

    int[] columnTableNumbers = getSubclassColumnTableNumberClosure();
    String[] columnAliases = getSubclassColumnAliasClosure();
    String[] columns = getSubclassColumnClosure();
    for int i = 0; i < getSubclassColumnClosure().length; i++ ) {
      boolean selectable = allProperties || !subclassColumnLazyClosure[i] ) &&
        !isSubclassTableSequentialSelectcolumnTableNumbers[i] ) &&
        subclassColumnSelectableClosure[i];
      if selectable ) {
        String subalias = generateTableAliasname, columnTableNumbers[i] );
        select.addColumnsubalias, columns[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] )
        && !isSubclassTableSequentialSelectformulaTableNumbers[i] );
      if selectable ) {
        String subalias = generateTableAliasname, formulaTableNumbers[i] );
        select.addFormulasubalias, formulaTemplates[i], formulaAliases[i] );
      }
    }

    if entityMetamodel.hasSubclasses() ) {
      addDiscriminatorToSelectselect, name, suffix );
    }

    if hasRowId() ) {
      select.addColumnname, rowIdName, ROWID_ALIAS );
    }

    return select.toFragmentString();
  }

  public Object[] getDatabaseSnapshot(Serializable id, SessionImplementor session)
      throws HibernateException {

    if log.isTraceEnabled() ) {
      log.trace"Getting current persistent state for: " + MessageHelper.infoStringthis, id, getFactory() ) );
    }

    try {
      PreparedStatement ps = session.getBatcher().prepareSelectStatementgetSQLSnapshotSelectString() );
      try {
        getIdentifierType().nullSafeSetps, id, 1, session );
        //if ( isVersioned() ) getVersionType().nullSafeSet( ps, version, getIdentifierColumnSpan()+1, session );
        ResultSet rs = ps.executeQuery();
        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].hydraters, getPropertyAliases"", i ), session, null )//null owner ok??
            }
          }
          return values;
        }
        finally {
          rs.close();
        }
      }
      finally {
        session.getBatcher().closeStatementps );
      }
    }
    catch SQLException sqle ) {
      throw JDBCExceptionHelper.convert(
          getFactory().getSQLExceptionConverter(),
          sqle,
          "could not retrieve snapshot: " +
          MessageHelper.infoStringthis, id, getFactory() ),
              getSQLSnapshotSelectString()
        );
    }

  }

  /**
   * Generate the SQL that selects the version number by id
   */
  protected String generateSelectVersionString() {
    SimpleSelect select = new SimpleSelectgetFactory().getDialect() )
        .setTableNamegetVersionedTableName() );
    if isVersioned() ) {
      select.addColumnversionColumnName );
    }
    else {
      select.addColumnsrootTableKeyColumnNames );
    }
    if getFactory().getSettings().isCommentsEnabled() ) {
      select.setComment"get version " + getEntityName() );
    }
    return select.addConditionrootTableKeyColumnNames, "=?" ).toStatementString();
  }

  protected String generateInsertGeneratedValuesSelectString() {
    return generateGeneratedValuesSelectStringgetPropertyInsertGenerationInclusions() );
  }

  protected String generateUpdateGeneratedValuesSelectString() {
    return generateGeneratedValuesSelectStringgetPropertyUpdateGenerationInclusions() );
  }

  private String generateGeneratedValuesSelectString(ValueInclusion[] inclusions) {
    Select select = new SelectgetFactory().getDialect() );

    if getFactory().getSettings().isCommentsEnabled() ) {
      select.setComment"get generated state " + getEntityName() );
    }

    String[] aliasedIdColumns = StringHelper.qualifygetRootAlias(), 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 = concretePropertySelectFragmentgetRootAlias(), inclusions );
    selectClause = selectClause.substring);

    String fromClause = fromTableFragmentgetRootAlias() ) +
        fromJoinFragmentgetRootAlias(), true, false );

    String whereClause = new StringBuffer()
      .appendStringHelper.join"=? and ", aliasedIdColumns ) )
      .append"=?" )
      .appendwhereJoinFragmentgetRootAlias(), true, false ) )
      .toString();

    return select.setSelectClauseselectClause )
        .setFromClausefromClause )
        .setOuterJoins"""" )
        .setWhereClausewhereClause )
        .toStatementString();
  }

  protected static interface InclusionChecker {
    public boolean includeProperty(int propertyNumber);
  }

  protected String concretePropertySelectFragment(String alias, final ValueInclusion[] inclusions) {
    return concretePropertySelectFragment(
        alias,
        new InclusionChecker() {
          // TODO : currently we really do not handle ValueInclusion.PARTIAL...
          // ValueInclusion.PARTIAL would indicate parts of a component need to
          // be included in the select; currently we then just render the entire
          // component into the select clause in that case.
          public boolean includeProperty(int propertyNumber) {
            return inclusions[propertyNumber!= ValueInclusion.NONE;
          }
        }
    );
  }

  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) ) {
        frag.addColumns(
            generateTableAliasalias, propertyTableNumbers[i] ),
            propertyColumnNames[i],
            propertyColumnAliases[i]
        );
        frag.addFormulas(
            generateTableAliasalias, propertyTableNumbers[i] ),
            propertyColumnFormulaTemplates[i],
            propertyColumnAliases[i]
        );
      }
    }
    return frag.toFragmentString();
  }

  protected String generateSnapshotSelectString() {

    //TODO: should we use SELECT .. FOR UPDATE?

    Select select = new SelectgetFactory().getDialect() );

    if getFactory().getSettings().isCommentsEnabled() ) {
      select.setComment"get current state " + getEntityName() );
    }

    String[] aliasedIdColumns = StringHelper.qualifygetRootAlias(), getIdentifierColumnNames() );
    String selectClause = StringHelper.join", ", aliasedIdColumns +
        concretePropertySelectFragmentgetRootAlias(), getPropertyUpdateability() );

    String fromClause = fromTableFragmentgetRootAlias() ) +
        fromJoinFragmentgetRootAlias(), true, false );

    String whereClause = new StringBuffer()
      .appendStringHelper.join"=? and ",
          aliasedIdColumns ) )
      .append"=?" )
      .appendwhereJoinFragmentgetRootAlias(), true, false ) )
      .toString();

    /*if ( isVersioned() ) {
      where.append(" and ")
        .append( getVersionColumnName() )
        .append("=?");
    }*/

    return select.setSelectClauseselectClause )
        .setFromClausefromClause )
        .setOuterJoins"""" )
        .setWhereClausewhereClause )
        .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().nextcurrentVersion, session );
    if log.isTraceEnabled() ) {
      log.trace(
          "Forcing version increment [" + MessageHelper.infoStringthis, id, getFactory() ) +
          "; " + getVersionType().toLoggableStringcurrentVersion, getFactory() ) +
          " -> " + getVersionType().toLoggableStringnextVersion, getFactory() ) "]"
      );
    }

    // todo : cache this sql...
    String versionIncrementString = generateVersionIncrementUpdateString();
    PreparedStatement st = null;
    try {
      try {
        st = session.getBatcher().prepareStatementversionIncrementString );
        getVersionType().nullSafeSetst, nextVersion, 1, session );
        getIdentifierType().nullSafeSetst, id, 2, session );
        getVersionType().nullSafeSetst, currentVersion, + getIdentifierColumnSpan(), session );
        int rows = st.executeUpdate();
        if rows != ) {
          throw new StaleObjectStateExceptiongetEntityName(), id );
        }
      }
      finally {
        session.getBatcher().closeStatementst );
      }
    }
    catch SQLException sqle ) {
      throw JDBCExceptionHelper.convert(
          getFactory().getSQLExceptionConverter(),
          sqle,
          "could not retrieve version: " +
          MessageHelper.infoStringthis, id, getFactory() ),
          getVersionSelectString()
        );
    }

    return nextVersion;
  }

  private String generateVersionIncrementUpdateString() {
    Update update = new UpdategetFactory().getDialect() );
    update.setTableNamegetTableName) );
    if getFactory().getSettings().isCommentsEnabled() ) {
      update.setComment"forced version increment" );
    }
    update.addColumngetVersionColumnName() );
    update.setPrimaryKeyColumnNamesgetIdentifierColumnNames() );
    update.setVersionColumnNamegetVersionColumnName() );
    return update.toStatementString();
  }

  /**
   * Retrieve the version number
   */
  public Object getCurrentVersion(Serializable id, SessionImplementor sessionthrows HibernateException {

    if log.isTraceEnabled() ) {
      log.trace"Getting version: " + MessageHelper.infoStringthis, id, getFactory() ) );
    }

    try {

      PreparedStatement st = session.getBatcher().prepareSelectStatementgetVersionSelectString() );
      try {
        getIdentifierType().nullSafeSetst, id, 1, session );

        ResultSet rs = st.executeQuery();
        try {
          if !rs.next() ) {
            return null;
          }
          if !isVersioned() ) {
            return this;
          }
          return getVersionType().nullSafeGetrs, getVersionColumnName(), session, null );
        }
        finally {
          rs.close();
        }
      }
      finally {
        session.getBatcher().closeStatementst );
      }

    }
    catch SQLException sqle ) {
      throw JDBCExceptionHelper.convert(
          getFactory().getSQLExceptionConverter(),
          sqle,
          "could not retrieve version: " +
          MessageHelper.infoStringthis, id, getFactory() ),
          getVersionSelectString()
        );
    }

  }

  protected void initLockers() {
    lockers.putLockMode.READ, generateLockerLockMode.READ ) );
    lockers.putLockMode.UPGRADE, generateLockerLockMode.UPGRADE ) );
    lockers.putLockMode.UPGRADE_NOWAIT, generateLockerLockMode.UPGRADE_NOWAIT ) );
    lockers.putLockMode.FORCE, generateLockerLockMode.FORCE ) );
  }

  protected LockingStrategy generateLocker(LockMode lockMode) {
    return factory.getDialect().getLockingStrategythis, lockMode );
  }

  private LockingStrategy getLocker(LockMode lockMode) {
    return LockingStrategy lockers.getlockMode );
  }

  public void lock(
      Serializable id,
          Object version,
          Object object,
          LockMode lockMode,
          SessionImplementor sessionthrows HibernateException {
    getLockerlockMode ).lockid, version, object, session );
  }

  public String getRootTableName() {
    return getSubclassTableName);
  }

  public String getRootTableAlias(String drivingAlias) {
    return drivingAlias;
  }

  public String[] getRootTableIdentifierColumnNames() {
    return getRootTableKeyColumnNames();
  }

  public String[] toColumns(String alias, String propertyNamethrows QueryException {
    return propertyMapping.toColumnsalias, propertyName );
  }

  public String[] toColumns(String propertyNamethrows QueryException {
    return propertyMapping.getColumnNamespropertyName );
  }

  public Type toType(String propertyNamethrows QueryException {
    return propertyMapping.toTypepropertyName );
  }

  public String[] getPropertyColumnNames(String propertyName) {
    return propertyMapping.getColumnNamespropertyName );
  }

  /**
   * 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.indexOfgetSubclassPropertyNameClosure(), rootPropertyName)//TODO: optimize this better!
    return index==-: getSubclassPropertyTableNumber(index);
  }

  public Declarer getSubclassPropertyDeclarer(String propertyPath) {
    int tableIndex = getSubclassPropertyTableNumberpropertyPath );
    if tableIndex == ) {
      return Declarer.CLASS;
    }
    else if isClassOrSuperclassTabletableIndex ) ) {
      return Declarer.SUPERCLASS;
    }
    else {
      return Declarer.SUBCLASS;
    }
  }

  protected String generateTableAlias(String rootAlias, int tableNumber) {
    if tableNumber == ) {
      return rootAlias;
    }
    StringBuffer buf = new StringBuffer().appendrootAlias );
    if !rootAlias.endsWith"_" ) ) {
      buf.append'_' );
    }
    return buf.appendtableNumber ).append'_' ).toString();
  }

  public String[] toColumns(String name, final int i) {
    final String alias = generateTableAliasname, getSubclassPropertyTableNumber) );
    String[] cols = getSubclassPropertyColumnNames);
    String[] templates = getSubclassPropertyFormulaTemplateClosure()[i];
    String[] result = new String[cols.length];
    for int j = 0; j < cols.length; j++ ) {
      if cols[j== null ) {
        result[j= StringHelper.replacetemplates[j], Template.TEMPLATE, alias );
      }
      else {
        result[j= StringHelper.qualifyalias, cols[j] );
      }
    }
    return result;
  }

  private int getSubclassPropertyIndex(String propertyName) {
    return ArrayHelper.indexOf(subclassPropertyNameClosure, propertyName);
  }

  protected String[] getPropertySubclassNames() {
    return propertySubclassNames;
  }

  public String[] getPropertyColumnNames(int i) {
    return propertyColumnNames[i];
  }

  protected int getPropertyColumnSpan(int i) {
    return propertyColumnSpans[i];
  }

  protected boolean hasFormulaProperties() {
    return hasFormulaProperties;
  }

  public FetchMode getFetchMode(int i) {
    return subclassPropertyFetchModeClosure[i];
  }

  public CascadeStyle getCascadeStyle(int i) {
    return subclassPropertyCascadeStyleClosure[i];
  }

  public Type getSubclassPropertyType(int i) {
    return subclassPropertyTypeClosure[i];
  }

  public String getSubclassPropertyName(int i) {
    return subclassPropertyNameClosure[i];
  }

  public int countSubclassProperties() {
    return subclassPropertyTypeClosure.length;
  }

  public String[] getSubclassPropertyColumnNames(int i) {
    return subclassPropertyColumnNameClosure[i];
  }

  public boolean isDefinedOnSubclass(int i) {
    return propertyDefinedOnSubclass[i];
  }

  protected String[][] getSubclassPropertyFormulaTemplateClosure() {
    return subclassPropertyFormulaTemplateClosure;
  }

  protected Type[] getSubclassPropertyTypeClosure() {
    return subclassPropertyTypeClosure;
  }

  protected String[][] getSubclassPropertyColumnNameClosure() {
    return subclassPropertyColumnNameClosure;
  }

  protected String[] getSubclassPropertyNameClosure() {
    return subclassPropertyNameClosure;
  }

  protected String[] getSubclassPropertySubclassNameClosure() {
    return subclassPropertySubclassNameClosure;
  }

  protected String[] getSubclassColumnClosure() {
    return subclassColumnClosure;
  }

  protected String[] getSubclassColumnAliasClosure() {
    return subclassColumnAliasClosure;
  }

  protected String[] getSubclassFormulaClosure() {
    return subclassFormulaClosure;
  }

  protected String[] getSubclassFormulaTemplateClosure() {
    return subclassFormulaTemplateClosure;
  }

  protected String[] getSubclassFormulaAliasClosure() {
    return subclassFormulaAliasClosure;
  }

  public String[] getSubclassPropertyColumnAliases(String propertyName, String suffix) {
    String rawAliases[] String[] ) subclassPropertyAliases.getpropertyName );

    if rawAliases == null ) {
      return null;
    }

    String result[] new String[rawAliases.length];
    for int i = 0; i < rawAliases.length; i++ ) {
      result[inew Aliassuffix ).toUnquotedAliasStringrawAliases[i] );
    }
    return result;
  }

  public String[] getSubclassPropertyColumnNames(String propertyName) {
    //TODO: should we allow suffixes on these ?
    return String[] ) subclassPropertyColumnNames.getpropertyName );
  }



  //This is really ugly, but necessary:
  /**
   * Must be called by subclasses, at the end of their constructors
   */
  protected void initSubclassPropertyAliasesMap(PersistentClass modelthrows MappingException {

    // ALIASES
    internalInitSubclassPropertyAliasesMapnull, model.getSubclassPropertyClosureIterator() );

    // aliases for identifier ( alias.id ); skip if the entity defines a non-id property named 'id'
    if ! entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
      subclassPropertyAliases.putENTITY_ID, getIdentifierAliases() );
      subclassPropertyColumnNames.putENTITY_ID, getIdentifierColumnNames() );
    }

    // aliases named identifier ( alias.idname )
    if hasIdentifierProperty() ) {
      subclassPropertyAliases.putgetIdentifierPropertyName(), getIdentifierAliases() );
      subclassPropertyColumnNames.putgetIdentifierPropertyName(), getIdentifierColumnNames() );
    }

    // aliases for composite-id's
    if getIdentifierType().isComponentType() ) {
      // Fetch embedded identifiers propertynames from the "virtual" identifier component
      AbstractComponentType componentId = AbstractComponentType getIdentifierType();
      String[] idPropertyNames = componentId.getPropertyNames();
      String[] idAliases = getIdentifierAliases();
      String[] idColumnNames = getIdentifierColumnNames();

      for int i = 0; i < idPropertyNames.length; i++ ) {
        if entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
          subclassPropertyAliases.put(
              ENTITY_ID + "." + idPropertyNames[i],
              new String[] { idAliases[i] }
          );
          subclassPropertyColumnNames.put(
              ENTITY_ID + "." + getIdentifierPropertyName() "." + idPropertyNames[i],
              new String[] { idColumnNames[i] }
          );
        }
//        if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyName() ) ) {
        if hasIdentifierProperty() ) {
          subclassPropertyAliases.put(
              getIdentifierPropertyName() "." + idPropertyNames[i],
              new String[] { idAliases[i] }
          );
          subclassPropertyColumnNames.put(
              getIdentifierPropertyName() "." + idPropertyNames[i],
              new String[] { idColumnNames[i] }
          );
        }
        else {
          // embedded composite ids ( alias.idname1, alias.idname2 )
          subclassPropertyAliases.putidPropertyNames[i]new String[] { idAliases[i] } );
          subclassPropertyColumnNames.putidPropertyNames[i],  new String[] { idColumnNames[i] } );
        }
      }
    }

    if entityMetamodel.isPolymorphic() ) {
      subclassPropertyAliases.putENTITY_CLASS, new String[] { getDiscriminatorAlias() } );
      subclassPropertyColumnNames.putENTITY_CLASS, new String[] { getDiscriminatorColumnName() } );
    }

  }

  private void internalInitSubclassPropertyAliasesMap(String path, Iterator propertyIterator) {
    while propertyIterator.hasNext() ) {

      Property prop = Property propertyIterator.next();
      String propname = path == null ? prop.getName() : path + "." + prop.getName();
      if prop.isComposite() ) {
        Component component = Component prop.getValue();
        Iterator compProps = component.getPropertyIterator();
        internalInitSubclassPropertyAliasesMappropname, compProps );
      }
      else {
        String[] aliases = new String[prop.getColumnSpan()];
        String[] cols = new String[prop.getColumnSpan()];
        Iterator colIter = prop.getColumnIterator();
        int l = 0;
        while colIter.hasNext() ) {
          Selectable thing = Selectable colIter.next();
          aliases[l= thing.getAliasgetFactory().getDialect(), prop.getValue().getTable() );
          cols[l= thing.getTextgetFactory().getDialect() )// TODO: skip formulas?
          l++;
        }

        subclassPropertyAliases.putpropname, aliases );
        subclassPropertyColumnNames.putpropname, cols );
      }
    }

  }

  public Object loadByUniqueKey(String propertyName, Object uniqueKey, SessionImplementor session)
      throws HibernateException {
    return getAppropriateUniqueKeyLoaderpropertyName, session.getEnabledFilters() )
        .loadByUniqueKeysession, uniqueKey );
  }

  private EntityLoader getAppropriateUniqueKeyLoader(String propertyName, Map enabledFilters) {

    final boolean useStaticLoader = enabledFilters == null || enabledFilters.isEmpty() )
        && propertyName.indexOf('.')<0//ugly little workaround for fact that createUniqueKeyLoaders() does not handle component properties

    if useStaticLoader ) {
      return (EntityLoaderuniqueKeyLoaders.getpropertyName );
    }
    else {
      return createUniqueKeyLoader(
          propertyMapping.toType(propertyName),
          propertyMapping.toColumns(propertyName),
          enabledFilters
        );
    }
  }

  public int getPropertyIndex(String propertyName) {
    return entityMetamodel.getPropertyIndex(propertyName);
  }

  protected void createUniqueKeyLoaders() throws MappingException {
    Type[] propertyTypes = getPropertyTypes();
    String[] propertyNames = getPropertyNames();
    for int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
      if propertyUniqueness[i] ) {
        //don't need filters for the static loaders
        uniqueKeyLoaders.put(
            propertyNames[i],
            createUniqueKeyLoader(
                propertyTypes[i],
                getPropertyColumnNames),
                CollectionHelper.EMPTY_MAP
              )
          );
        //TODO: create uk loaders for component properties
      }
    }
  }

  private EntityLoader createUniqueKeyLoader(Type uniqueKeyType, String[] columns, Map enabledFilters) {
    if uniqueKeyType.isEntityType() ) {
      String className = ( ( EntityType uniqueKeyType ).getAssociatedEntityName();
      uniqueKeyType = getFactory().getEntityPersisterclassName ).getIdentifierType();
    }

    return new EntityLoaderthis, columns, uniqueKeyType, 1, LockMode.NONE, getFactory(), enabledFilters );
  }

  protected String getSQLWhereString(String alias) {
    return StringHelper.replacesqlWhereStringTemplate, Template.TEMPLATE, alias );
  }

  protected boolean hasWhere() {
    return sqlWhereString != null;
  }

  private void initOrdinaryPropertyPaths(Mapping mappingthrows MappingException {
    for int i = 0; i < getSubclassPropertyNameClosure().length; i++ ) {
      propertyMapping.initPropertyPathsgetSubclassPropertyNameClosure()[i],
          getSubclassPropertyTypeClosure()[i],
          getSubclassPropertyColumnNameClosure()[i],
          getSubclassPropertyFormulaTemplateClosure()[i],
          mapping );
    }
  }

  private void initIdentifierPropertyPaths(Mapping mappingthrows MappingException {
    String idProp = getIdentifierPropertyName();
    if idProp != null ) {
      propertyMapping.initPropertyPathsidProp, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
    }
    if entityMetamodel.getIdentifierProperty().isEmbedded() ) {
      propertyMapping.initPropertyPathsnull, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
    }
    if ! entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
      propertyMapping.initPropertyPathsENTITY_ID, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
    }
  }

  private void initDiscriminatorPropertyPath(Mapping mappingthrows MappingException {
    propertyMapping.initPropertyPathsENTITY_CLASS,
        getDiscriminatorType(),
        new String[]{getDiscriminatorColumnName()},
        new String[]{getDiscriminatorFormulaTemplate()},
        getFactory() );
  }

  protected void initPropertyPaths(Mapping mappingthrows MappingException {
    initOrdinaryPropertyPaths(mapping);
    initOrdinaryPropertyPaths(mapping)//do two passes, for collection property-ref!
    initIdentifierPropertyPaths(mapping);
    if entityMetamodel.isPolymorphic() ) {
      initDiscriminatorPropertyPathmapping );
    }
  }

  protected UniqueEntityLoader createEntityLoader(LockMode lockMode, Map enabledFiltersthrows MappingException {
    //TODO: disable batch loading if lockMode > READ?
    return BatchingEntityLoader.createBatchingEntityLoaderthis, batchSize, lockMode, getFactory(), enabledFilters );
  }

  protected UniqueEntityLoader createEntityLoader(LockMode lockModethrows MappingException {
    return createEntityLoaderlockMode, CollectionHelper.EMPTY_MAP );
  }

  protected boolean check(int rows, Serializable id, int tableNumber, Expectation expectation, PreparedStatement statementthrows HibernateException {
    try {
      expectation.verifyOutcomerows, statement, -);
    }
    catchStaleStateException e ) {
      if !isNullableTabletableNumber ) ) {
        if getFactory().getStatistics().isStatisticsEnabled() ) {
          getFactory().getStatisticsImplementor()
              .optimisticFailuregetEntityName() );
        }
        throw new StaleObjectStateExceptiongetEntityName(), id );
      }
      return false;
    }
    catchTooManyRowsAffectedException e ) {
      throw new HibernateException(
          "Duplicate identifier in table for: " +
          MessageHelper.infoStringthis, id, getFactory() )
      );
    }
    catch Throwable t ) {
      return false;
    }
    return true;
  }

  protected String generateUpdateString(boolean[] includeProperty, int j, boolean useRowId) {
    return generateUpdateStringincludeProperty, j, null, useRowId );
  }

  /**
   * Generate the SQL that updates a row by id (and version)
   */
  protected String generateUpdateString(final boolean[] includeProperty,
                      final int j,
                      final Object[] oldFields,
                      final boolean useRowId) {

    Update update = new UpdategetFactory().getDialect() ).setTableNamegetTableName) );

    // select the correct row by either pk or rowid
    if useRowId ) {
      update.setPrimaryKeyColumnNamesnew String[]{rowIdName} )//TODO: eventually, rowIdName[j]
    }
    else {
      update.setPrimaryKeyColumnNamesgetKeyColumns) );
    }

    boolean hasColumns = false;
    for int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
      if includeProperty[i&& isPropertyOfTablei, j ) ) {
        // this is a property of the table, which we are updating
        update.addColumnsgetPropertyColumnNames(i), propertyColumnUpdateable[i] );
        hasColumns = hasColumns || getPropertyColumnSpan0;
      }
    }

    if j == && isVersioned() && entityMetamodel.getOptimisticLockMode() == Versioning.OPTIMISTIC_LOCK_VERSION ) {
      // this is the root (versioned) table, and we are using version-based
      // optimistic locking;  if we are not updating the version, also don't
      // check it (unless this is a "generated" version column)!
      if checkVersionincludeProperty ) ) {
        update.setVersionColumnNamegetVersionColumnName() );
        hasColumns = true;
      }
    }
    else if entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION && oldFields != null ) {
      // we are using "all" or "dirty" property-based optimistic locking

      boolean[] includeInWhere = entityMetamodel.getOptimisticLockMode() == Versioning.OPTIMISTIC_LOCK_ALL ?
          getPropertyUpdateability() //optimistic-lock="all", include all updatable properties
          includeProperty; //optimistic-lock="dirty", include all properties we are updating this time

      boolean[] versionability = getPropertyVersionability();
      Type[] types = getPropertyTypes();
      for int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
        boolean include = includeInWhere[i&&
            isPropertyOfTablei, j &&
            versionability[i];
        if include ) {
          // this property belongs to the table, and it is not specifically
          // excluded from optimistic locking by optimistic-lock="false"
          String[] propertyColumnNames = getPropertyColumnNames);
          boolean[] propertyNullness = types[i].toColumnNullnessoldFields[i], getFactory() );
          for int k=0; k<propertyNullness.length; k++ ) {
            if propertyNullness[k] ) {
              update.addWhereColumnpropertyColumnNames[k] );
            }
            else {
              update.addWhereColumnpropertyColumnNames[k]" is null" );
            }
          }
        }
      }

    }

    if getFactory().getSettings().isCommentsEnabled() ) {
      update.setComment"update " + getEntityName() );
    }

    return hasColumns ? update.toStatementString() null;
  }

  private boolean checkVersion(final boolean[] includeProperty) {
        return includePropertygetVersionProperty() ] ||
        entityMetamodel.getPropertyUpdateGenerationInclusions()[ getVersionProperty() ] != ValueInclusion.NONE;
  }

  protected String generateInsertString(boolean[] includeProperty, int j) {
    return generateInsertStringfalse, includeProperty, j );
  }

  protected String generateInsertString(boolean identityInsert, boolean[] includeProperty) {
    return generateInsertStringidentityInsert, includeProperty, );
  }

  /**
   * Generate the SQL that inserts a row
   */
  protected String generateInsertString(boolean identityInsert, boolean[] includeProperty, int j) {

    // todo : remove the identityInsert param and variations;
    //   identity-insert strings are now generated from generateIdentityInsertString()

    Insert insert = new InsertgetFactory().getDialect() )
        .setTableNamegetTableName) );

    // add normal properties
    for int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
      if includeProperty[i&& isPropertyOfTablei, j ) ) {
        // this property belongs on the table and is to be inserted
        insert.addColumnsgetPropertyColumnNames(i), propertyColumnInsertable[i] );
      }
    }

    // add the discriminator
    if j == ) {
      addDiscriminatorToInsertinsert );
    }

    // add the primary key
    if j == && identityInsert ) {
      insert.addIdentityColumngetKeyColumns)[0] );
    }
    else {
      insert.addColumnsgetKeyColumns) );
    }

    if getFactory().getSettings().isCommentsEnabled() ) {
      insert.setComment"insert " + getEntityName() );
    }

    String result = insert.toStatementString();

    // append the SQL to return the generated identifier
    if j == && identityInsert && useInsertSelectIdentity() ) { //TODO: suck into Insert
      result = getFactory().getDialect().appendIdentitySelectToInsertresult );
    }

    return result;
  }

  /**
   * Used to generate an insery statement against the root table in the
   * case of identifier generation strategies where the insert statement
   * executions actually generates the identifier value.
   *
   @param includeProperty indices of the properties to include in the
   * insert statement.
   @return The insert SQL statement string
   */
  protected String generateIdentityInsertString(boolean[] includeProperty) {
    Insert insert = identityDelegate.prepareIdentifierGeneratingInsert();
    insert.setTableNamegetTableName) );

    // add normal properties
    for int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
      if includeProperty[i&& isPropertyOfTablei, ) ) {
        // this property belongs on the table and is to be inserted
        insert.addColumnsgetPropertyColumnNames(i), propertyColumnInsertable[i] );
      }
    }

    // add the discriminator
    addDiscriminatorToInsertinsert );

    // delegate already handles PK columns

    if getFactory().getSettings().isCommentsEnabled() ) {
      insert.setComment"insert " + getEntityName() );
    }

    return insert.toStatementString();
  }

  /**
   * Generate the SQL that deletes a row by id (and version)
   */
  protected String generateDeleteString(int j) {
    Delete delete = new Delete()
        .setTableNamegetTableName) )
        .setPrimaryKeyColumnNamesgetKeyColumns) );
    if j == ) {
      delete.setVersionColumnNamegetVersionColumnName() );
    }
    if getFactory().getSettings().isCommentsEnabled() ) {
      delete.setComment"delete " + getEntityName() );
    }
    return delete.toStatementString();
  }

  protected int dehydrate(
      Serializable id,
      Object[] fields,
      boolean[] includeProperty,
      boolean[][] includeColumns,
      int j,
      PreparedStatement st,
      SessionImplementor sessionthrows HibernateException, SQLException {
    return dehydrateid, fields, null, includeProperty, includeColumns, j, st, session, );
  }

  /**
   * Marshall the fields of a persistent instance to a prepared statement
   */
  protected int dehydrate(
      final Serializable id,
          final Object[] fields,
          final Object rowId,
          final boolean[] includeProperty,
          final boolean[][] includeColumns,
          final int j,
          final PreparedStatement ps,
          final SessionImplementor session,
          int indexthrows SQLException, HibernateException {

    if log.isTraceEnabled() ) {
      log.trace"Dehydrating entity: " + MessageHelper.infoStringthis, id, getFactory() ) );
    }

    for int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
      if includeProperty[i&& isPropertyOfTablei, j ) ) {
        getPropertyTypes()[i].nullSafeSetps, fields[i], index, includeColumns[i], session );
        //index += getPropertyColumnSpan( i );
        index += ArrayHelper.countTrueincludeColumns[i] )//TODO:  this is kinda slow...
      }
    }

    if rowId != null ) {
      ps.setObjectindex, rowId );
      index += 1;
    }
    else if id != null ) {
      getIdentifierType().nullSafeSetps, id, index, session );
      index += getIdentifierColumnSpan();
    }

    return index;

  }

  /**
   * Unmarshall the fields of a persistent instance from a result set,
   * without resolving associations or collections. Question: should
   * this really be here, or should it be sent back to Loader?
   */
  public Object[] hydrate(
      final ResultSet rs,
          final Serializable id,
          final Object object,
          final Loadable rootLoadable,
          final String[][] suffixedPropertyColumns,
          final boolean allProperties,
          final SessionImplementor sessionthrows SQLException, HibernateException {

    if log.isTraceEnabled() ) {
      log.trace"Hydrating entity: " + MessageHelper.infoStringthis, id, getFactory() ) );
    }

    final AbstractEntityPersister rootPersister = (AbstractEntityPersisterrootLoadable;

    final boolean hasDeferred = rootPersister.hasSequentialSelect();
    PreparedStatement sequentialSelect = null;
    ResultSet sequentialResultSet = null;
    boolean sequentialSelectEmpty = false;
    try {

      if hasDeferred ) {
        final String sql = rootPersister.getSequentialSelectgetEntityName() );
        if sql != null ) {
          //TODO: I am not so sure about the exception handling in this bit!
          sequentialSelect = session.getBatcher().prepareSelectStatementsql );
          rootPersister.getIdentifierType().nullSafeSetsequentialSelect, id, 1, session );
          sequentialResultSet = sequentialSelect.executeQuery();
          if !sequentialResultSet.next() ) {
            // TODO: Deal with the "optional" attribute in the <join> mapping;
            // this code assumes that optional defaults to "true" because it
            // doesn't actually seem to work in the fetch="join" code
            //
            // Note that actual proper handling of optional-ality here is actually
            // more involved than this patch assumes.  Remember that we might have
            // multiple <join/> mappings associated with a single entity.  Really
            // a couple of things need to happen to properly handle optional here:
            //  1) First and foremost, when handling multiple <join/>s, we really
            //      should be using the entity root table as the driving table;
            //      another option here would be to choose some non-optional joined
            //      table to use as the driving table.  In all likelihood, just using
            //      the root table is much simplier
            //  2) Need to add the FK columns corresponding to each joined table
            //      to the generated select list; these would then be used when
            //      iterating the result set to determine whether all non-optional
            //      data is present
            // My initial thoughts on the best way to deal with this would be
            // to introduce a new SequentialSelect abstraction that actually gets
            // generated in the persisters (ok, SingleTable...) and utilized here.
            // It would encapsulated all this required optional-ality checking...
            sequentialSelectEmpty = true;
          }
        }
      }

      final String[] propNames = getPropertyNames();
      final Type[] types = getPropertyTypes();
      final Object[] values = new Object[types.length];
      final boolean[] laziness = getPropertyLaziness();
      final String[] propSubclassNames = getSubclassPropertySubclassNameClosure();

      for int i = 0; i < types.length; i++ ) {
        if !propertySelectable[i] ) {
          values[i= BackrefPropertyAccessor.UNKNOWN;
        }
        else if allProperties || !laziness[i] ) {
          //decide which ResultSet to get the property value from:
          final boolean propertyIsDeferred = hasDeferred &&
              rootPersister.isSubclassPropertyDeferredpropNames[i], propSubclassNames[i] );
          if propertyIsDeferred && sequentialSelectEmpty ) {
            values[inull;
          }
          else {
            final ResultSet propertyResultSet = propertyIsDeferred ? sequentialResultSet : rs;
            final String[] cols = propertyIsDeferred ? propertyColumnAliases[i: suffixedPropertyColumns[i];
            values[i= types[i].hydratepropertyResultSet, cols, session, object );
          }
        }
        else {
          values[i= LazyPropertyInitializer.UNFETCHED_PROPERTY;
        }
      }

      if sequentialResultSet != null ) {
        sequentialResultSet.close();
      }

      return values;

    }
    finally {
      if sequentialSelect != null ) {
        session.getBatcher().closeStatementsequentialSelect );
      }
    }
  }

  protected boolean useInsertSelectIdentity() {
    return !useGetGeneratedKeys() && getFactory().getDialect().supportsInsertSelectIdentity();
  }

  protected boolean useGetGeneratedKeys() {
    return getFactory().getSettings().isGetGeneratedKeysEnabled();
  }

  protected String getSequentialSelect(String entityName) {
    throw new UnsupportedOperationException("no sequential selects");
  }

  /**
   * Perform an SQL INSERT, and then retrieve a generated identifier.
   <p/>
   * This form is used for PostInsertIdentifierGenerator-style ids (IDENTITY,
   * select, etc).
   */
  protected Serializable insert(
      final Object[] fields,
          final boolean[] notNull,
          String sql,
          final Object object,
          final SessionImplementor sessionthrows HibernateException {

    if log.isTraceEnabled() ) {
      log.trace"Inserting entity: " + getEntityName() " (native id)" );
      if isVersioned() ) {
        log.trace"Version: " + Versioning.getVersionfields, this ) );
      }
    }

    Binder binder = new Binder() {
      public void bindValues(PreparedStatement psthrows SQLException {
        dehydratenull, fields, notNull, propertyColumnInsertable, 0, ps, session );
      }
      public Object getEntity() {
        return object;
      }
    };
    return identityDelegate.performInsertsql, session, binder );
  }

  public String getIdentitySelectString() {
    //TODO: cache this in an instvar
    return getFactory().getDialect().getIdentitySelectString(
        getTableName(0),
        getKeyColumns(0)[0],
        getIdentifierType().sqlTypesgetFactory() )[0]
    );
  }

  public String getSelectByUniqueKeyString(String propertyName) {
    return new SimpleSelectgetFactory().getDialect() )
      .setTableNamegetTableName(0) )
      .addColumnsgetKeyColumns(0) )
      .addConditiongetPropertyColumnNames(propertyName)"=?" )
      .toStatementString();
  }

  /**
   * Perform an SQL INSERT.
   <p/>
   * This for is used for all non-root tables as well as the root table
   * in cases where the identifier value is known before the insert occurs.
   */
  protected void insert(
      final Serializable id,
          final Object[] fields,
          final boolean[] notNull,
          final int j,
          final String sql,
          final Object object,
          final SessionImplementor sessionthrows HibernateException {

    if isInverseTable) ) {
      return;
    }

    //note: it is conceptually possible that a UserType could map null to
    //    a non-null value, so the following is arguable:
    if isNullableTable&& isAllNullfields, j ) ) {
      return;
    }

    if log.isTraceEnabled() ) {
      log.trace"Inserting entity: " + MessageHelper.infoStringthis, id, getFactory() ) );
      if j == && isVersioned() ) {
        log.trace"Version: " + Versioning.getVersionfields, this ) );
      }
    }

    Expectation expectation = Expectations.appropriateExpectationinsertResultCheckStyles[j] );
    boolean callable = isInsertCallable);
    // we can't batch joined inserts, *especially* not if it is an identity insert;
    // nor can we batch statements where the expectation is based on an output param
    final boolean useBatch = j == && expectation.canBeBatched();
    try {

      // Render the SQL query
      final PreparedStatement insert;
      if useBatch ) {
        if callable ) {
          insert = session.getBatcher().prepareBatchCallableStatementsql );
        }
        else {
          insert = session.getBatcher().prepareBatchStatementsql );
        }
      }
      else {
        if callable ) {
          insert = session.getBatcher().prepareCallableStatementsql );
        }
        else {
          insert = session.getBatcher().prepareStatementsql );
        }
      }

      try {
        int index = 1;
        index += expectation.prepareinsert );

        // Write the values of fields onto the prepared statement - we MUST use the state at the time the
        // insert was issued (cos of foreign key constraints). Not necessarily the object's current state

        dehydrateid, fields, null, notNull, propertyColumnInsertable, j, insert, session, index );

        if useBatch ) {
          // TODO : shouldnt inserts be Expectations.NONE?
          session.getBatcher().addToBatchexpectation );
        }
        else {
          expectation.verifyOutcomeinsert.executeUpdate(), insert, -);
        }

      }
      catch SQLException sqle ) {
        if useBatch ) {
          session.getBatcher().abortBatchsqle );
        }
        throw sqle;
      }
      finally {
        if !useBatch ) {
          session.getBatcher().closeStatementinsert );
        }
      }
    }
    catch SQLException sqle ) {
      throw JDBCExceptionHelper.convert(
          getFactory().getSQLExceptionConverter(),
          sqle,
          "could not insert: " + MessageHelper.infoStringthis ),
          sql
        );
    }

  }

  /**
   * Perform an SQL UPDATE or SQL INSERT
   */
  protected void updateOrInsert(
      final Serializable id,
          final Object[] fields,
          final Object[] oldFields,
          final Object rowId,
          final boolean[] includeProperty,
          final int j,
          final Object oldVersion,
          final Object object,
          final String sql,
          final SessionImplementor sessionthrows HibernateException {

    if !isInverseTable) ) {

      final boolean isRowToUpdate;
      if isNullableTable&& oldFields != null && isAllNulloldFields, j ) ) {
        //don't bother trying to update, we know there is no row there yet
        isRowToUpdate = false;
      }
      else if isNullableTable&& isAllNullfields, j ) ) {
        //if all fields are null, we might need to delete existing row
        isRowToUpdate = true;
        deleteid, oldVersion, j, object, getSQLDeleteStrings()[j], session, null );
      }
      else {
        //there is probably a row there, so try to update
        //if no rows were updated, we will find out
        isRowToUpdate = updateid, fields, oldFields, rowId, includeProperty, j, oldVersion, object, sql, session );
      }

      if !isRowToUpdate && !isAllNullfields, j ) ) {
        // assume that the row was not there since it previously had only null
        // values, so do an INSERT instead
        //TODO: does not respect dynamic-insert
        insertid, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
      }

    }

  }

  protected boolean update(
      final Serializable id,
          final Object[] fields,
          final Object[] oldFields,
          final Object rowId,
          final boolean[] includeProperty,
          final int j,
          final Object oldVersion,
          final Object object,
          final String sql,
          final SessionImplementor sessionthrows HibernateException {

    final boolean useVersion = j == && isVersioned();
    final Expectation expectation = Expectations.appropriateExpectationupdateResultCheckStyles[j] );
    final boolean callable = isUpdateCallable);
    final boolean useBatch = j == && expectation.canBeBatched() && isBatchable()//note: updates to joined tables can't be batched...

    if log.isTraceEnabled() ) {
      log.trace"Updating entity: " + MessageHelper.infoStringthis, id, getFactory() ) );
      if useVersion ) {
        log.trace"Existing version: " + oldVersion + " -> New version: " + fields[getVersionProperty()] );
      }
    }

    try {

      int index = 1// starting index
      final PreparedStatement update;
      if useBatch ) {
        if callable ) {
          update = session.getBatcher().prepareBatchCallableStatementsql );
        }
        else {
          update = session.getBatcher().prepareBatchStatementsql );
        }
      }
      else {
        if callable ) {
          update = session.getBatcher().prepareCallableStatementsql );
        }
        else {
          update = session.getBatcher().prepareStatementsql );
        }
      }

      try {

        index+= expectation.prepareupdate );

        //Now write the values of fields onto the prepared statement
        index = dehydrateid, fields, rowId, includeProperty, propertyColumnUpdateable, j, update, session, index );

        // Write any appropriate versioning conditional parameters
        if useVersion && Versioning.OPTIMISTIC_LOCK_VERSION == entityMetamodel.getOptimisticLockMode() ) {
          if checkVersionincludeProperty ) ) {
            getVersionType().nullSafeSetupdate, oldVersion, index, session );
          }
        }
        else if entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION && oldFields != null ) {
          boolean[] versionability = getPropertyVersionability()//TODO: is this really necessary????
          boolean[] includeOldField = entityMetamodel.getOptimisticLockMode() == Versioning.OPTIMISTIC_LOCK_ALL ?
              getPropertyUpdateability() : includeProperty;
          Type[] types = getPropertyTypes();
          for int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
            boolean include = includeOldField[i&&
                isPropertyOfTablei, j &&
                versionability[i]//TODO: is this really necessary????
            if include ) {
              boolean[] settable = types[i].toColumnNullnessoldFields[i], getFactory() );
              types[i].nullSafeSet(
                  update,
                  oldFields[i],
                  index,
                  settable,
                  session
                );
              index += ArrayHelper.countTrue(settable);
            }
          }
        }

        if useBatch ) {
          session.getBatcher().addToBatchexpectation );
          return true;
        }
        else {
          return checkupdate.executeUpdate(), id, j, expectation, update );
        }

      }
      catch SQLException sqle ) {
        if useBatch ) {
          session.getBatcher().abortBatchsqle );
        }
        throw sqle;
      }
      finally {
        if !useBatch ) {
          session.getBatcher().closeStatementupdate );
        }
      }

    }
    catch SQLException sqle ) {
      throw JDBCExceptionHelper.convert(
          getFactory().getSQLExceptionConverter(),
          sqle,
          "could not update: " + MessageHelper.infoStringthis, id, getFactory() ),
          sql
        );
    }
  }

  /**
   * Perform an SQL DELETE
   */
  protected void delete(
      final Serializable id,
      final Object version,
      final int j,
      final Object object,
      final String sql,
      final SessionImplementor session,
      final Object[] loadedStatethrows HibernateException {

    if isInverseTable) ) {
      return;
    }

    final boolean useVersion = j == && isVersioned();
    final boolean callable = isDeleteCallable);
    final Expectation expectation = Expectations.appropriateExpectationdeleteResultCheckStyles[j] );
    final boolean useBatch = j == && isBatchable() && expectation.canBeBatched();

    if log.isTraceEnabled() ) {
      log.trace"Deleting entity: " + MessageHelper.infoStringthis, id, getFactory() ) );
      if useVersion ) {
        log.trace"Version: " + version );
      }
    }

    if isTableCascadeDeleteEnabled) ) {
      if log.isTraceEnabled() ) {
        log.trace"delete handled by foreign key constraint: " + getTableName) );
      }
      return//EARLY EXIT!
    }

    try {

      //Render the SQL query
      PreparedStatement delete;
      int index = 1;
      if useBatch ) {
        if callable ) {
          delete = session.getBatcher().prepareBatchCallableStatementsql );
        }
        else {
          delete = session.getBatcher().prepareBatchStatementsql );
        }
      }
      else {
        if callable ) {
          delete = session.getBatcher().prepareCallableStatementsql );
        }
        else {
          delete = session.getBatcher().prepareStatementsql );
        }
      }

      try {

        index += expectation.preparedelete );

        // Do the key. The key is immutable so we can use the _current_ object state - not necessarily
        // the state at the time the delete was issued
        getIdentifierType().nullSafeSetdelete, id, index, session );
        index += getIdentifierColumnSpan();

        // We should use the _current_ object state (ie. after any updates that occurred during flush)

        if useVersion ) {
          getVersionType().nullSafeSetdelete, version, index, session );
        }
        else if entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION && loadedState != null ) {
          boolean[] versionability = getPropertyVersionability();
          Type[] types = getPropertyTypes();
          for int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
            if isPropertyOfTablei, j && versionability[i] ) {
              // this property belongs to the table and it is not specifically
              // excluded from optimistic locking by optimistic-lock="false"
              boolean[] settable = types[i].toColumnNullnessloadedState[i], getFactory() );
              types[i].nullSafeSetdelete, loadedState[i], index, settable, session );
              index += ArrayHelper.countTruesettable );
            }
          }
        }

        if useBatch ) {
          session.getBatcher().addToBatchexpectation );
        }
        else {
          checkdelete.executeUpdate(), id, j, expectation, delete );
        }

      }
      catch SQLException sqle ) {
        if useBatch ) {
          session.getBatcher().abortBatchsqle );
        }
        throw sqle;
      }
      finally {
        if !useBatch ) {
          session.getBatcher().closeStatementdelete );
        }
      }

    }
    catch SQLException sqle ) {
      throw JDBCExceptionHelper.convert(
          getFactory().getSQLExceptionConverter(),
          sqle,
          "could not delete: " +
          MessageHelper.infoStringthis, id, getFactory() ),
          sql
        );

    }

  }

  private String[] getUpdateStrings(boolean byRowId, boolean lazy) {
    if byRowId ) {
      return lazy ? getSQLLazyUpdateByRowIdStrings() : getSQLUpdateByRowIdStrings();
    }
    else {
      return lazy ? getSQLLazyUpdateStrings() : getSQLUpdateStrings();
    }
  }

  /**
   * Update an object
   */
  public void update(
      final Serializable id,
          final Object[] fields,
          final int[] dirtyFields,
          final boolean hasDirtyCollection,
          final Object[] oldFields,
          final Object oldVersion,
          final Object object,
          final Object rowId,
          final SessionImplementor sessionthrows HibernateException {

    //note: dirtyFields==null means we had no snapshot, and we couldn't get one using select-before-update
    //    oldFields==null just means we had no snapshot to begin with (we might have used select-before-update to get the dirtyFields)

    final boolean[] tableUpdateNeeded = getTableUpdateNeededdirtyFields, hasDirtyCollection );
    final int span = getTableSpan();

    final boolean[] propsToUpdate;
    final String[] updateStrings;
    if entityMetamodel.isDynamicUpdate() && dirtyFields != null ) {
      // For the case of dynamic-update="true", we need to generate the UPDATE SQL
      propsToUpdate = getPropertiesToUpdatedirtyFields, hasDirtyCollection );
      // don't need to check laziness (dirty checking algorithm handles that)
      updateStrings = new String[span];
      for int j = 0; j < span; j++ ) {
        updateStrings[j= tableUpdateNeeded[j?
            generateUpdateStringpropsToUpdate, j, oldFields, j == && rowId != null :
            null;
      }
    }
    else {
      // For the case of dynamic-update="false", or no snapshot, we use the static SQL
      updateStrings = getUpdateStrings(
          rowId != null,
          hasUninitializedLazyPropertiesobject, session.getEntityMode() )
        );
      propsToUpdate = getPropertyUpdateabilityobject, session.getEntityMode() );
    }

    for int j = 0; j < span; j++ ) {
      // Now update only the tables with dirty properties (and the table with the version number)
      if tableUpdateNeeded[j] ) {
        updateOrInsert(
            id,
            fields,
            oldFields,
            j == ? rowId : null,
            propsToUpdate,
            j,
            oldVersion,
            object,
            updateStrings[j],
            session
          );
      }
    }
  }

  public Serializable insert(Object[] fields, Object object, SessionImplementor session)
      throws HibernateException {

    final int span = getTableSpan();
    final Serializable id;
    if entityMetamodel.isDynamicInsert() ) {
      // For the case of dynamic-insert="true", we need to generate the INSERT SQL
      boolean[] notNull = getPropertiesToInsertfields );
      id = insertfields, notNull, generateInsertStringtrue, notNull ), object, session );
      for int j = 1; j < span; j++ ) {
        insertid, fields, notNull, j, generateInsertStringnotNull, j ), object, session );
      }
    }
    else {
      // For the case of dynamic-insert="false", use the static SQL
      id = insertfields, getPropertyInsertability(), getSQLIdentityInsertString(), object, session );
      for int j = 1; j < span; j++ ) {
        insertid, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
      }
    }
    return id;
  }

  public void insert(Serializable id, Object[] fields, Object object, SessionImplementor session)
      throws HibernateException {

    final int span = getTableSpan();
    if entityMetamodel.isDynamicInsert() ) {
      // For the case of dynamic-insert="true", we need to generate the INSERT SQL
      boolean[] notNull = getPropertiesToInsertfields );
      for int j = 0; j < span; j++ ) {
        insertid, fields, notNull, j, generateInsertStringnotNull, j ), object, session );
      }
    }
    else {
      // For the case of dynamic-insert="false", use the static SQL
      for int j = 0; j < span; j++ ) {
        insertid, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
      }
    }
  }

  /**
   * Delete an object
   */
  public void delete(Serializable id, Object version, Object object, SessionImplementor session)
      throws HibernateException {
    final int span = getTableSpan();
    boolean isImpliedOptimisticLocking = !entityMetamodel.isVersioned() && entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION;
    Object[] loadedState = null;
    if isImpliedOptimisticLocking ) {
      // need to treat this as if it where optimistic-lock="all" (dirty does *not* make sense);
      // first we need to locate the "loaded" state
      //
      // Note, it potentially could be a proxy, so perform the location the safe way...
      EntityKey key = new EntityKeyid, this, session.getEntityMode() );
      Object entity = session.getPersistenceContext().getEntitykey );
      if entity != null ) {
        EntityEntry entry = session.getPersistenceContext().getEntryentity );
        loadedState = entry.getLoadedState();
      }
    }

    final String[] deleteStrings;
    if isImpliedOptimisticLocking && loadedState != null ) {
      // we need to utilize dynamic delete statements
      deleteStrings = generateSQLDeletStringsloadedState );
    }
    else {
      // otherwise, utilize the static delete statements
      deleteStrings = getSQLDeleteStrings();
    }

    for int j = span - 1; j >= 0; j-- ) {
      deleteid, version, j, object, deleteStrings[j], session, loadedState );
    }

  }

  private String[] generateSQLDeletStrings(Object[] loadedState) {
    int span = getTableSpan();
    String[] deleteStrings = new String[span];
    for int j = span - 1; j >= 0; j-- ) {
      Delete delete = new Delete()
          .setTableNamegetTableName) )
          .setPrimaryKeyColumnNamesgetKeyColumns) );
      if getFactory().getSettings().isCommentsEnabled() ) {
        delete.setComment"delete " + getEntityName() " [" + j + "]" );
      }

      boolean[] versionability = getPropertyVersionability();
      Type[] types = getPropertyTypes();
      for int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
        if isPropertyOfTablei, j && versionability[i] ) {
          // this property belongs to the table and it is not specifically
          // excluded from optimistic locking by optimistic-lock="false"
          String[] propertyColumnNames = getPropertyColumnNames);
          boolean[] propertyNullness = types[i].toColumnNullnessloadedState[i], getFactory() );
          for int k = 0; k < propertyNullness.length; k++ ) {
            if propertyNullness[k] ) {
              delete.addWhereFragmentpropertyColumnNames[k" = ?" );
            }
            else {
              delete.addWhereFragmentpropertyColumnNames[k" is null" );
            }
          }
        }
      }
      deleteStrings[j= delete.toStatementString();
    }
    return deleteStrings;
  }

  protected void logStaticSQL() {
    if log.isDebugEnabled() ) {
      log.debug"Static SQL for entity: " + getEntityName() );
      if sqlLazySelectString != null ) {
        log.debug" Lazy select: " + sqlLazySelectString );
      }
      if sqlVersionSelectString != null ) {
        log.debug" Version select: " + sqlVersionSelectString );
      }
      if sqlSnapshotSelectString != null ) {
        log.debug" Snapshot select: " + sqlSnapshotSelectString );
      }
      for int j = 0; j < getTableSpan(); j++ ) {
        log.debug" Insert " + j + ": " + getSQLInsertStrings()[j] );
        log.debug" Update " + j + ": " + getSQLUpdateStrings()[j] );
        log.debug" Delete " + j + ": " + getSQLDeleteStrings()[j] );

      }
      if sqlIdentityInsertString != null ) {
        log.debug" Identity insert: " + sqlIdentityInsertString );
      }
      if sqlUpdateByRowIdString != null ) {
        log.debug" Update by row id (all fields): " + sqlUpdateByRowIdString );
      }
      if sqlLazyUpdateByRowIdString != null ) {
        log.debug" Update by row id (non-lazy fields): " + sqlLazyUpdateByRowIdString );
      }
      if sqlInsertGeneratedValuesSelectString != null ) {
        log.debug"Insert-generated property select: " + sqlInsertGeneratedValuesSelectString );
      }
      if sqlUpdateGeneratedValuesSelectString != null ) {
        log.debug"Update-generated property select: " + sqlUpdateGeneratedValuesSelectString );
      }
    }
  }

  public String filterFragment(String alias, Map enabledFiltersthrows MappingException {
    final StringBuffer sessionFilterFragment = new StringBuffer();
    filterHelper.rendersessionFilterFragment, generateFilterConditionAliasalias ), enabledFilters );

    return sessionFilterFragment.appendfilterFragmentalias ) ).toString();
  }

  public String generateFilterConditionAlias(String rootAlias) {
    return rootAlias;
  }

  public String oneToManyFilterFragment(String aliasthrows MappingException {
    return "";
  }

  public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
    return getSubclassTableSpan() == ?
        "" //just a performance opt!
        createJoinalias, innerJoin, includeSubclasses ).toFromFragmentString();
  }

  public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
    return getSubclassTableSpan() == ?
        "" //just a performance opt!
        createJoinalias, innerJoin, includeSubclasses ).toWhereFragmentString();
  }

  protected boolean isSubclassTableLazy(int j) {
    return false;
  }

  protected JoinFragment createJoin(String name, boolean innerJoin, boolean includeSubclasses) {
    final String[] idCols = StringHelper.qualifyname, getIdentifierColumnNames() )//all joins join to the pk of the driving table
    final JoinFragment join = getFactory().getDialect().createOuterJoinFragment();
    final int tableSpan = getSubclassTableSpan();
    for int j = 1; j < tableSpan; j++ ) { //notice that we skip the first table; it is the driving table!
      final boolean joinIsIncluded = isClassOrSuperclassTable||
          includeSubclasses && !isSubclassTableSequentialSelect&& !isSubclassTableLazy) );
      if joinIsIncluded ) {
        join.addJoingetSubclassTableName),
            generateTableAliasname, j ),
            idCols,
            getSubclassTableKeyColumns),
            innerJoin && isClassOrSuperclassTable&& !isInverseTable&& !isNullableTable?
            JoinFragment.INNER_JOIN : //we can inner join to superclass tables (the row MUST be there)
            JoinFragment.LEFT_OUTER_JOIN //we can never inner join to subclass tables
          );
      }
    }
    return join;
  }

  protected JoinFragment createJoin(int[] tableNumbers, String drivingAlias) {
    final String[] keyCols = StringHelper.qualifydrivingAlias, getSubclassTableKeyColumnstableNumbers[0] ) );
    final JoinFragment jf = getFactory().getDialect().createOuterJoinFragment();
    for int i = 1; i < tableNumbers.length; i++ ) { //skip the driving table
      final int j = tableNumbers[i];
      jf.addJoingetSubclassTableName),
          generateTableAliasgetRootAlias(), j ),
          keyCols,
          getSubclassTableKeyColumns),
          isInverseSubclassTable|| isNullableSubclassTable?
          JoinFragment.LEFT_OUTER_JOIN :
          JoinFragment.INNER_JOIN );
    }
    return jf;
  }

  protected SelectFragment createSelect(final int[] subclassColumnNumbers,
                      final int[] subclassFormulaNumbers) {

    SelectFragment selectFragment = new SelectFragment();

    int[] columnTableNumbers = getSubclassColumnTableNumberClosure();
    String[] columnAliases = getSubclassColumnAliasClosure();
    String[] columns = getSubclassColumnClosure();
    for int i = 0; i < subclassColumnNumbers.length; i++ ) {
      if subclassColumnSelectableClosure[i] ) {
        int columnNumber = subclassColumnNumbers[i];
        final String subalias = generateTableAliasgetRootAlias(), columnTableNumbers[columnNumber] );
        selectFragment.addColumnsubalias, columns[columnNumber], columnAliases[columnNumber] );
      }
    }

    int[] formulaTableNumbers = getSubclassFormulaTableNumberClosure();
    String[] formulaTemplates = getSubclassFormulaTemplateClosure();
    String[] formulaAliases = getSubclassFormulaAliasClosure();
    for int i = 0; i < subclassFormulaNumbers.length; i++ ) {
      int formulaNumber = subclassFormulaNumbers[i];
      final String subalias = generateTableAliasgetRootAlias(), formulaTableNumbers[formulaNumber] );
      selectFragment.addFormulasubalias, formulaTemplates[formulaNumber], formulaAliases[formulaNumber] );
    }

    return selectFragment;
  }

  protected String createFrom(int tableNumber, String alias) {
    return getSubclassTableNametableNumber ' ' + alias;
  }

  protected String createWhereByKey(int tableNumber, String alias) {
    //TODO: move to .sql package, and refactor with similar things!
    return StringHelper.join"=? and ",
        StringHelper.qualifyalias, getSubclassTableKeyColumnstableNumber ) ) ) "=?";
  }

  protected String renderSelect(
      final int[] tableNumbers,
          final int[] columnNumbers,
          final int[] formulaNumbers) {

    Arrays.sorttableNumbers )//get 'em in the right order (not that it really matters)

    //render the where and from parts
    int drivingTable = tableNumbers[0];
    final String drivingAlias = generateTableAliasgetRootAlias(), drivingTable )//we *could* regerate this inside each called method!
    final String where = createWhereByKeydrivingTable, drivingAlias );
    final String from = createFromdrivingTable, drivingAlias );

    //now render the joins
    JoinFragment jf = createJointableNumbers, drivingAlias );

    //now render the select clause
    SelectFragment selectFragment = createSelectcolumnNumbers, formulaNumbers );

    //now tie it all together
    Select select = new SelectgetFactory().getDialect() );
    select.setSelectClauseselectFragment.toFragmentString().substring) );
    select.setFromClausefrom );
    select.setWhereClausewhere );
    select.setOuterJoinsjf.toFromFragmentString(), jf.toWhereFragmentString() );
    if getFactory().getSettings().isCommentsEnabled() ) {
      select.setComment"sequential select " + getEntityName() );
    }
    return select.toStatementString();
  }

  private String getRootAlias() {
    return StringHelper.generateAliasgetEntityName() );
  }

  protected void postConstruct(Mapping mappingthrows MappingException {
    initPropertyPaths(mapping);

    //insert/update/delete SQL
    final int joinSpan = getTableSpan();
    sqlDeleteStrings = new String[joinSpan];
    sqlInsertStrings = new String[joinSpan];
    sqlUpdateStrings = new String[joinSpan];
    sqlLazyUpdateStrings = new String[joinSpan];

    sqlUpdateByRowIdString = rowIdName == null ?
        null :
        generateUpdateStringgetPropertyUpdateability()0true );
    sqlLazyUpdateByRowIdString = rowIdName == null ?
        null :
        generateUpdateStringgetNonLazyPropertyUpdateability()0true );

    for int j = 0; j < joinSpan; j++ ) {
      sqlInsertStrings[j= customSQLInsert[j== null ?
          generateInsertStringgetPropertyInsertability(), j :
          customSQLInsert[j];
      sqlUpdateStrings[j= customSQLUpdate[j== null ?
          generateUpdateStringgetPropertyUpdateability(), j, false :
          customSQLUpdate[j];
      sqlLazyUpdateStrings[j= customSQLUpdate[j== null ?
          generateUpdateStringgetNonLazyPropertyUpdateability(), j, false :
          customSQLUpdate[j];
      sqlDeleteStrings[j= customSQLDelete[j== null ?
          generateDeleteString:
          customSQLDelete[j];
    }

    tableHasColumns = new boolean[joinSpan];
    for int j = 0; j < joinSpan; j++ ) {
      tableHasColumns[j= sqlUpdateStrings[j!= null;
    }

    //select SQL
    sqlSnapshotSelectString = generateSnapshotSelectString();
    sqlLazySelectString = generateLazySelectString();
    sqlVersionSelectString = generateSelectVersionString();
    if hasInsertGeneratedProperties() ) {
      sqlInsertGeneratedValuesSelectString = generateInsertGeneratedValuesSelectString();
    }
    if hasUpdateGeneratedProperties() ) {
      sqlUpdateGeneratedValuesSelectString = generateUpdateGeneratedValuesSelectString();
    }
    if isIdentifierAssignedByInsert() ) {
      identityDelegate = ( ( PostInsertIdentifierGenerator getIdentifierGenerator() )
          .getInsertGeneratedIdentifierDelegatethis, getFactory().getDialect(), useGetGeneratedKeys() );
      sqlIdentityInsertString = customSQLInsert[0== null
          ? generateIdentityInsertStringgetPropertyInsertability() )
          : customSQLInsert[0];
    }
    else {
      sqlIdentityInsertString = null;
    }

    logStaticSQL();

  }

  public void postInstantiate() throws MappingException {

    createLoaders();
    createUniqueKeyLoaders();
    createQueryLoader();

  }

  private void createLoaders() {
    loaders.putLockMode.NONE, createEntityLoaderLockMode.NONE ) );

    UniqueEntityLoader readLoader = createEntityLoaderLockMode.READ );
    loaders.putLockMode.READ, readLoader );

    //TODO: inexact, what we really need to know is: are any outer joins used?
    boolean disableForUpdate = getSubclassTableSpan() &&
        hasSubclasses() &&
        !getFactory().getDialect().supportsOuterJoinForUpdate();

    loaders.put(
        LockMode.UPGRADE,
        disableForUpdate ?
            readLoader :
            createEntityLoaderLockMode.UPGRADE )
      );
    loaders.put(
        LockMode.UPGRADE_NOWAIT,
        disableForUpdate ?
            readLoader :
            createEntityLoaderLockMode.UPGRADE_NOWAIT )
      );
    loaders.put(
        LockMode.FORCE,
        disableForUpdate ?
            readLoader :
            createEntityLoaderLockMode.FORCE )
      );

    loaders.put(
        "merge",
        new CascadeEntityLoaderthis, CascadingAction.MERGE, getFactory() )
      );
    loaders.put(
        "refresh",
        new CascadeEntityLoaderthis, CascadingAction.REFRESH, getFactory() )
      );
  }

  protected void createQueryLoader() {
    if loaderName != null ) {
      queryLoader = new NamedQueryLoaderloaderName, this );
    }
  }

  /**
   * Load an instance using either the <tt>forUpdateLoader</tt> or the outer joining <tt>loader</tt>,
   * depending upon the value of the <tt>lock</tt> parameter
   */
  public Object load(Serializable id, Object optionalObject, LockMode lockMode, SessionImplementor session)
      throws HibernateException {

    if log.isTraceEnabled() ) {
      log.trace(
          "Fetching entity: " +
          MessageHelper.infoStringthis, id, getFactory() )
        );
    }

    final UniqueEntityLoader loader = getAppropriateLoaderlockMode, session );
    return loader.loadid, optionalObject, session );
  }

  private UniqueEntityLoader getAppropriateLoader(LockMode lockMode, SessionImplementor session) {
    final Map enabledFilters = session.getEnabledFilters();
    if queryLoader != null ) {
      return queryLoader;
    }
    else if enabledFilters == null || enabledFilters.isEmpty() ) {
      if session.getFetchProfile()!=null && LockMode.UPGRADE.greaterThan(lockMode) ) {
        return (UniqueEntityLoaderloaders.getsession.getFetchProfile() );
      }
      else {
        return (UniqueEntityLoaderloaders.getlockMode );
      }
    }
    else {
      return createEntityLoaderlockMode, enabledFilters );
    }
  }

  private boolean isAllNull(Object[] array, int tableNumber) {
    for int i = 0; i < array.length; i++ ) {
      if isPropertyOfTablei, tableNumber && array[i!= null ) {
        return false;
      }
    }
    return true;
  }

  public boolean isSubclassPropertyNullable(int i) {
    return subclassPropertyNullabilityClosure[i];
  }

  /**
   * Transform the array of property indexes to an array of booleans,
   * true when the property is dirty
   */
  protected final boolean[] getPropertiesToUpdate(final int[] dirtyProperties, final boolean hasDirtyCollection) {
    final boolean[] propsToUpdate = new booleanentityMetamodel.getPropertySpan() ];
    final boolean[] updateability = getPropertyUpdateability()//no need to check laziness, dirty checking handles that
    for int j = 0; j < dirtyProperties.length; j++ ) {
      int property = dirtyProperties[j];
      if updateability[property] ) {
        propsToUpdate[propertytrue;
      }
    }
    if isVersioned() ) {
      propsToUpdategetVersionProperty() ] =
        Versioning.isVersionIncrementRequireddirtyProperties, hasDirtyCollection, getPropertyVersionability() );
    }
    return propsToUpdate;
  }

  /**
   * Transform the array of property indexes to an array of booleans,
   * true when the property is insertable and non-null
   */
  protected boolean[] getPropertiesToInsert(Object[] fields) {
    boolean[] notNull = new boolean[fields.length];
    boolean[] insertable = getPropertyInsertability();
    for int i = 0; i < fields.length; i++ ) {
      notNull[i= insertable[i&& fields[i!= null;
    }
    return notNull;
  }

  /**
   * Locate the property-indices of all properties considered to be dirty.
   *
   @param currentState The current state of the entity (the state to be checked).
   @param previousState The previous state of the entity (the state to be checked against).
   @param entity The entity for which we are checking state dirtiness.
   @param session The session in which the check is ccurring.
   @return <tt>null</tt> or the indices of the dirty properties
   @throws HibernateException
   */
  public int[] findDirty(Object[] currentState, Object[] previousState, Object entity, SessionImplementor session)
  throws HibernateException {
    int[] props = TypeFactory.findDirty(
        entityMetamodel.getProperties(),
        currentState,
        previousState,
        propertyColumnUpdateable,
        hasUninitializedLazyPropertiesentity, session.getEntityMode() ),
        session
      );
    if props == null ) {
      return null;
    }
    else {
      logDirtyPropertiesprops );
      return props;
    }
  }

  /**
   * Locate the property-indices of all properties considered to be dirty.
   *
   @param old The old state of the entity.
   @param current The current state of the entity.
   @param entity The entity for which we are checking state modification.
   @param session The session in which the check is ccurring.
   @return <tt>null</tt> or the indices of the modified properties
   @throws HibernateException
   */
  public int[] findModified(Object[] old, Object[] current, Object entity, SessionImplementor session)
  throws HibernateException {
    int[] props = TypeFactory.findModified(
        entityMetamodel.getProperties(),
        current,
        old,
        propertyColumnUpdateable,
        hasUninitializedLazyPropertiesentity, session.getEntityMode() ),
        session
      );
    if props == null ) {
      return null;
    }
    else {
      logDirtyPropertiesprops );
      return props;
    }
  }

  /**
   * Which properties appear in the SQL update?
   * (Initialized, updateable ones!)
   */
  protected boolean[] getPropertyUpdateability(Object entity, EntityMode entityMode) {
    return hasUninitializedLazyPropertiesentity, entityMode ?
        getNonLazyPropertyUpdateability() :
        getPropertyUpdateability();
  }

  private void logDirtyProperties(int[] props) {
    if log.isTraceEnabled() ) {
      for int i = 0; i < props.length; i++ ) {
        String propertyName = entityMetamodel.getProperties()[ props[i] ].getName();
        log.traceStringHelper.qualifygetEntityName(), propertyName " is dirty" );
      }
    }
  }

  protected EntityTuplizer getTuplizer(SessionImplementor session) {
    return getTuplizersession.getEntityMode() );
  }

  protected EntityTuplizer getTuplizer(EntityMode entityMode) {
    return entityMetamodel.getTuplizerentityMode );
  }

  public SessionFactoryImplementor getFactory() {
    return factory;
  }

  public EntityMetamodel getEntityMetamodel() {
    return entityMetamodel;
  }

  public boolean hasCache() {
    return cache != null;
  }

  public CacheConcurrencyStrategy getCache() {
    return cache;
  }

  public CacheEntryStructure getCacheEntryStructure() {
    return cacheEntryStructure;
  }

  public Comparator getVersionComparator() {
    return isVersioned() ? getVersionType().getComparator() null;
  }

  // temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  public final String getEntityName() {
    return entityMetamodel.getName();
  }

  public EntityType getEntityType() {
    return entityMetamodel.getEntityType();
  }

  private String getSubclassEntityName(Class clazz) {
    return String entityNameBySubclass.getclazz );
  }

  public boolean isPolymorphic() {
    return entityMetamodel.isPolymorphic();
  }

  public boolean isInherited() {
    return entityMetamodel.isInherited();
  }

  public boolean hasCascades() {
    return entityMetamodel.hasCascades();
  }

  public boolean hasIdentifierProperty() {
    return !entityMetamodel.getIdentifierProperty().isVirtual();
  }

  public VersionType getVersionType() {
    return VersionType locateVersionType();
  }

  private Type locateVersionType() {
    return entityMetamodel.getVersionProperty() == null ?
        null :
        entityMetamodel.getVersionProperty().getType();
  }

  public int getVersionProperty() {
    return entityMetamodel.getVersionPropertyIndex();
  }

  public boolean isVersioned() {
    return entityMetamodel.isVersioned();
  }

  public boolean isIdentifierAssignedByInsert() {
    return entityMetamodel.getIdentifierProperty().isIdentifierAssignedByInsert();
  }

  public boolean hasLazyProperties() {
    return entityMetamodel.hasLazyProperties();
  }

//  public boolean hasUninitializedLazyProperties(Object entity) {
//    if ( hasLazyProperties() ) {
//      InterceptFieldCallback callback = ( ( InterceptFieldEnabled ) entity ).getInterceptFieldCallback();
//      return callback != null && !( ( FieldInterceptor ) callback ).isInitialized();
//    }
//    else {
//      return false;
//    }
//  }

  public void afterReassociate(Object entity, SessionImplementor session) {
    //if ( hasLazyProperties() ) {
    if FieldInterceptionHelper.isInstrumentedentity ) ) {
      FieldInterceptor interceptor = FieldInterceptionHelper.extractFieldInterceptorentity );
      if interceptor != null ) {
        interceptor.setSessionsession );
      }
      else {
        FieldInterceptor fieldInterceptor = FieldInterceptionHelper.injectFieldInterceptor(
            entity,
            getEntityName(),
            null,
            session
        );
        fieldInterceptor.dirty();
      }
    }
  }

  public Boolean isTransient(Object entity, SessionImplementor sessionthrows HibernateException {
    final Serializable id;
    if canExtractIdOutOfEntity() ) {
      id = getIdentifierentity, session.getEntityMode() );
    }
    else {
      id = null;
    }
    // we *always* assume an instance with a null
    // identifier or no identifier property is unsaved!
    if id == null ) {
      return Boolean.TRUE;
    }

    // check the version unsaved-value, if appropriate
    final Object version = getVersionentity, session.getEntityMode() );
    if isVersioned() ) {
      // let this take precedence if defined, since it works for
      // assigned identifiers
      Boolean result = entityMetamodel.getVersionProperty()
          .getUnsavedValue().isUnsavedversion );
      if result != null ) {
        return result;
      }
    }

    // check the id unsaved-value
    Boolean result = entityMetamodel.getIdentifierProperty()
        .getUnsavedValue().isUnsavedid );
    if result != null ) {
      return result;
    }

    // check to see if it is in the second-level cache
    if hasCache() ) {
      CacheKey ck = new CacheKey(
          id,
          getIdentifierType(),
          getRootEntityName(),
          session.getEntityMode(),
          session.getFactory()
        );
      if getCache().getck, session.getTimestamp() ) != null ) {
        return Boolean.FALSE;
      }
    }

    return null;
  }

  public boolean hasCollections() {
    return entityMetamodel.hasCollections();
  }

  public boolean hasMutableProperties() {
    return entityMetamodel.hasMutableProperties();
  }

  public boolean isMutable() {
    return entityMetamodel.isMutable();
  }

  public boolean isAbstract() {
    return entityMetamodel.isAbstract();
  }

  public boolean hasSubclasses() {
    return entityMetamodel.hasSubclasses();
  }

  public boolean hasProxy() {
    return entityMetamodel.isLazy();
  }

  public IdentifierGenerator getIdentifierGenerator() throws HibernateException {
    return entityMetamodel.getIdentifierProperty().getIdentifierGenerator();
  }

  public String getRootEntityName() {
    return entityMetamodel.getRootName();
  }

  public ClassMetadata getClassMetadata() {
    return this;
  }

  public String getMappedSuperclass() {
    return entityMetamodel.getSuperclass();
  }

  public boolean isExplicitPolymorphism() {
    return entityMetamodel.isExplicitPolymorphism();
  }

  protected boolean useDynamicUpdate() {
    return entityMetamodel.isDynamicUpdate();
  }

  protected boolean useDynamicInsert() {
    return entityMetamodel.isDynamicInsert();
  }

  protected boolean hasEmbeddedCompositeIdentifier() {
    return entityMetamodel.getIdentifierProperty().isEmbedded();
  }

  public boolean canExtractIdOutOfEntity() {
    return hasIdentifierProperty() || hasEmbeddedCompositeIdentifier() || hasIdentifierMapper();
  }

  private boolean hasIdentifierMapper() {
    return entityMetamodel.getIdentifierProperty().hasIdentifierMapper();
  }

  public String[] getKeyColumnNames() {
    return getIdentifierColumnNames();
  }

  public String getName() {
    return getEntityName();
  }

  public boolean isCollection() {
    return false;
  }

  public boolean consumesEntityAlias() {
    return true;
  }

  public boolean consumesCollectionAlias() {
    return false;
  }

  public Type getPropertyType(String propertyNamethrows MappingException {
    return propertyMapping.toType(propertyName);
  }

  public Type getType() {
    return entityMetamodel.getEntityType();
  }

  public boolean isSelectBeforeUpdateRequired() {
    return entityMetamodel.isSelectBeforeUpdate();
  }

  protected final int optimisticLockMode() {
    return entityMetamodel.getOptimisticLockMode();
  }

  public Object createProxy(Serializable id, SessionImplementor sessionthrows HibernateException {
    return entityMetamodel.getTuplizersession.getEntityMode() )
        .createProxyid, session );
  }

  public String toString() {
    return StringHelper.unqualifygetClass().getName() ) +
        '(' + entityMetamodel.getName() ')';
  }

  public final String selectFragment(
      Joinable rhs,
      String rhsAlias,
      String lhsAlias,
      String entitySuffix,
      String collectionSuffix,
      boolean includeCollectionColumns) {
    return selectFragmentlhsAlias, entitySuffix );
  }

  public boolean isInstrumented(EntityMode entityMode) {
    EntityTuplizer tuplizer = entityMetamodel.getTuplizerOrNull(entityMode);
    return tuplizer!=null && tuplizer.isInstrumented();
  }

  public boolean hasInsertGeneratedProperties() {
    return entityMetamodel.hasInsertGeneratedValues();
  }

  public boolean hasUpdateGeneratedProperties() {
    return entityMetamodel.hasUpdateGeneratedValues();
  }

  public boolean isVersionPropertyGenerated() {
    return isVersioned() && getPropertyUpdateGenerationInclusions() [ getVersionProperty() ] != ValueInclusion.NONE );
  }

  public boolean isVersionPropertyInsertable() {
    return isVersioned() && getPropertyInsertability() [ getVersionProperty() ];
  }

  public void afterInitialize(Object entity, boolean lazyPropertiesAreUnfetched, SessionImplementor session) {
    getTuplizersession ).afterInitializeentity, lazyPropertiesAreUnfetched, session );
  }

  public String[] getPropertyNames() {
    return entityMetamodel.getPropertyNames();
  }

  public Type[] getPropertyTypes() {
    return entityMetamodel.getPropertyTypes();
  }

  public boolean[] getPropertyLaziness() {
    return entityMetamodel.getPropertyLaziness();
  }

  public boolean[] getPropertyUpdateability() {
    return entityMetamodel.getPropertyUpdateability();
  }

  public boolean[] getPropertyCheckability() {
    return entityMetamodel.getPropertyCheckability();
  }

  public boolean[] getNonLazyPropertyUpdateability() {
    return entityMetamodel.getNonlazyPropertyUpdateability();
  }

  public boolean[] getPropertyInsertability() {
    return entityMetamodel.getPropertyInsertability();
  }

  public ValueInclusion[] getPropertyInsertGenerationInclusions() {
    return entityMetamodel.getPropertyInsertGenerationInclusions();
  }

  public ValueInclusion[] getPropertyUpdateGenerationInclusions() {
    return entityMetamodel.getPropertyUpdateGenerationInclusions();
  }

  public boolean[] getPropertyNullability() {
    return entityMetamodel.getPropertyNullability();
  }

  public boolean[] getPropertyVersionability() {
    return entityMetamodel.getPropertyVersionability();
  }

  public CascadeStyle[] getPropertyCascadeStyles() {
    return entityMetamodel.getCascadeStyles();
  }

  public final Class getMappedClass(EntityMode entityMode) {
    Tuplizer tup = entityMetamodel.getTuplizerOrNull(entityMode);
    return tup==null null : tup.getMappedClass();
  }

  public boolean implementsLifecycle(EntityMode entityMode) {
    return getTuplizerentityMode ).isLifecycleImplementor();
  }

  public boolean implementsValidatable(EntityMode entityMode) {
    return getTuplizerentityMode ).isValidatableImplementor();
  }

  public Class getConcreteProxyClass(EntityMode entityMode) {
    return getTuplizerentityMode ).getConcreteProxyClass();
  }

  public void setPropertyValues(Object object, Object[] values, EntityMode entityMode)
      throws HibernateException {
    getTuplizerentityMode ).setPropertyValuesobject, values );
  }

  public void setPropertyValue(Object object, int i, Object value, EntityMode entityMode)
      throws HibernateException {
    getTuplizerentityMode ).setPropertyValueobject, i, value );
  }

  public Object[] getPropertyValues(Object object, EntityMode entityMode)
      throws HibernateException {
    return getTuplizerentityMode ).getPropertyValuesobject );
  }

  public Object getPropertyValue(Object object, int i, EntityMode entityMode)
      throws HibernateException {
    return getTuplizerentityMode ).getPropertyValueobject , i );
  }

  public Object getPropertyValue(Object object, String propertyName, EntityMode entityMode)
      throws HibernateException {
    return getTuplizerentityMode ).getPropertyValueobject, propertyName );
  }

  public Serializable getIdentifier(Object object, EntityMode entityMode)
      throws HibernateException {
    return getTuplizerentityMode ).getIdentifierobject );
  }

  public void setIdentifier(Object object, Serializable id, EntityMode entityMode)
      throws HibernateException {
    getTuplizerentityMode ).setIdentifierobject, id );
  }

  public Object getVersion(Object object, EntityMode entityMode)
      throws HibernateException {
    return getTuplizerentityMode ).getVersionobject );
  }

  public Object instantiate(Serializable id, EntityMode entityMode)
      throws HibernateException {
    return getTuplizerentityMode ).instantiateid );
  }

  public boolean isInstance(Object object, EntityMode entityMode) {
    return getTuplizerentityMode ).isInstanceobject );
  }

  public boolean hasUninitializedLazyProperties(Object object, EntityMode entityMode) {
    return getTuplizerentityMode ).hasUninitializedLazyPropertiesobject );
  }

  public void resetIdentifier(Object entity, Serializable currentId, Object currentVersion, EntityMode entityMode) {
    getTuplizerentityMode ).resetIdentifierentity, currentId, currentVersion );
  }

  public EntityPersister getSubclassEntityPersister(Object instance, SessionFactoryImplementor factory, EntityMode entityMode) {
    if !hasSubclasses() ) {
      return this;
    }
    else {
      // TODO : really need a way to do something like :
      //      getTuplizer(entityMode).determineConcreteSubclassEntityName(instance)
      Class clazz = instance.getClass();
      if clazz == getMappedClassentityMode ) ) {
        return this;
      }
      else {
        String subclassEntityName = getSubclassEntityNameclazz );
        if subclassEntityName == null ) {
          throw new HibernateException(
              "instance not of expected entity type: " + clazz.getName() +
              " is not a: " + getEntityName()
            );
        }
        else {
          return factory.getEntityPersistersubclassEntityName );
        }
      }
    }
  }

  public EntityMode guessEntityMode(Object object) {
    return entityMetamodel.guessEntityMode(object);
  }

  public boolean isMultiTable() {
    return false;
  }

  public String getTemporaryIdTableName() {
    return temporaryIdTableName;
  }

  public String getTemporaryIdTableDDL() {
    return temporaryIdTableDDL;
  }

  protected int getPropertySpan() {
    return entityMetamodel.getPropertySpan();
  }

  public Object[] getPropertyValuesToInsert(Object object, Map mergeMap, SessionImplementor sessionthrows HibernateException {
    return getTuplizersession.getEntityMode() ).getPropertyValuesToInsertobject, mergeMap, session );
  }

  public void processInsertGeneratedProperties(Serializable id, Object entity, Object[] state, SessionImplementor session) {
    if !hasInsertGeneratedProperties() ) {
      throw new AssertionFailure("no insert-generated properties");
    }
    processGeneratedPropertiesid, entity, state, session, sqlInsertGeneratedValuesSelectString, getPropertyInsertGenerationInclusions() );
  }

  public void processUpdateGeneratedProperties(Serializable id, Object entity, Object[] state, SessionImplementor session) {
    if !hasUpdateGeneratedProperties() ) {
      throw new AssertionFailure("no update-generated properties");
    }
    processGeneratedPropertiesid, entity, state, session, sqlUpdateGeneratedValuesSelectString, getPropertyUpdateGenerationInclusions() );
  }

  private void processGeneratedProperties(
      Serializable id,
          Object entity,
          Object[] state,
          SessionImplementor session,
          String selectionSQL,
          ValueInclusion[] includeds) {

    session.getBatcher().executeBatch()//force immediate execution of the insert

    try {
      PreparedStatement ps = session.getBatcher().prepareSelectStatementselectionSQL );
      try {
        getIdentifierType().nullSafeSetps, id, 1, session );
        ResultSet rs = ps.executeQuery();
        try {
          if !rs.next() ) {
            throw new HibernateException(
                "Unable to locate row for retrieval of generated properties: " +
                MessageHelper.infoStringthis, id, getFactory() )
              );
          }
          for int i = 0; i < getPropertySpan(); i++ ) {
            if includeds[i!= ValueInclusion.NONE ) {
              Object hydratedState = getPropertyTypes()[i].hydraters, getPropertyAliases"", i ), session, entity );
              state[i= getPropertyTypes()[i].resolvehydratedState, session, entity );
              setPropertyValueentity, i, state[i], session.getEntityMode() );
            }
          }
        }
        finally {
          if rs != null ) {
            rs.close();
          }
        }
      }
      finally {
        session.getBatcher().closeStatementps );
      }
    }
    catchSQLException sqle ) {
      throw JDBCExceptionHelper.convert(
          getFactory().getSQLExceptionConverter(),
          sqle,
          "unable to select generated column values",
          selectionSQL
      );
    }

  }

  public String getIdentifierPropertyName() {
    return entityMetamodel.getIdentifierProperty().getName();
  }

  public Type getIdentifierType() {
    return entityMetamodel.getIdentifierProperty().getType();
  }

  public boolean hasSubselectLoadableCollections() {
    return hasSubselectLoadableCollections;
  }

  public int[] getNaturalIdentifierProperties() {
    return entityMetamodel.getNaturalIdentifierProperties();
  }

  public Object[] getNaturalIdentifierSnapshot(Serializable id, SessionImplementor sessionthrows HibernateException {
    if !hasNaturalIdentifier() ) {
      throw new MappingException"persistent class did not define a natural-id : " + MessageHelper.infoStringthis ) );
    }
    if log.isTraceEnabled() ) {
      log.trace"Getting current natural-id snapshot state for: " + MessageHelper.infoStringthis, id, getFactory() ) );
    }

    int[] naturalIdPropertyIndexes = getNaturalIdentifierProperties();
    int naturalIdPropertyCount = naturalIdPropertyIndexes.length;
    boolean[] naturalIdMarkers = new booleangetPropertySpan() ];
    Type[] extractionTypes = new TypenaturalIdPropertyCount ];
    for int i = 0; i < naturalIdPropertyCount; i++ ) {
      extractionTypes[i= getPropertyTypes()[ naturalIdPropertyIndexes[i] ];
      naturalIdMarkersnaturalIdPropertyIndexes[i] ] true;
    }

    ///////////////////////////////////////////////////////////////////////
    // TODO : look at perhaps caching this...
    Select select = new SelectgetFactory().getDialect() );
    if getFactory().getSettings().isCommentsEnabled() ) {
      select.setComment"get current natural-id state " + getEntityName() );
    }
    select.setSelectClauseconcretePropertySelectFragmentSansLeadingCommagetRootAlias(), naturalIdMarkers ) );
    select.setFromClausefromTableFragmentgetRootAlias() ) + fromJoinFragmentgetRootAlias(), true, false ) );

    String[] aliasedIdColumns = StringHelper.qualifygetRootAlias(), getIdentifierColumnNames() );
    String whereClause = new StringBuffer()
      .appendStringHelper.join"=? and ",
          aliasedIdColumns ) )
      .append"=?" )
      .appendwhereJoinFragmentgetRootAlias(), true, false ) )
      .toString();

    String sql = select.setOuterJoins"""" )
        .setWhereClausewhereClause )
        .toStatementString();
    ///////////////////////////////////////////////////////////////////////

    Object[] snapshot = new ObjectnaturalIdPropertyCount ];
    try {
      PreparedStatement ps = session.getBatcher().prepareSelectStatementsql );
      try {
        getIdentifierType().nullSafeSetps, id, 1, session );
        ResultSet rs = ps.executeQuery();
        try {
          //if there is no resulting row, return null
          if !rs.next() ) {
            return null;
          }

          for int i = 0; i < naturalIdPropertyCount; i++ ) {
            snapshot[i= extractionTypes[i].hydraters, getPropertyAliases"", naturalIdPropertyIndexes[i] ), session, null );
          }
          return snapshot;
        }
        finally {
          rs.close();
        }
      }
      finally {
        session.getBatcher().closeStatementps );
      }
    }
    catch SQLException sqle ) {
      throw JDBCExceptionHelper.convert(
          getFactory().getSQLExceptionConverter(),
          sqle,
          "could not retrieve snapshot: " +
          MessageHelper.infoStringthis, id, getFactory() ),
              sql
        );
    }
  }

  protected String concretePropertySelectFragmentSansLeadingComma(String alias, boolean[] include) {
    String concretePropertySelectFragment = concretePropertySelectFragmentalias, include );
    int firstComma = concretePropertySelectFragment.indexOf", " );
    if firstComma == ) {
      concretePropertySelectFragment = concretePropertySelectFragment.substring);
    }
    return concretePropertySelectFragment;
  }
  public boolean hasNaturalIdentifier() {
    return entityMetamodel.hasNaturalIdentifier();
  }

  public void setPropertyValue(Object object, String propertyName, Object value, EntityMode entityMode)
      throws HibernateException {
    getTuplizerentityMode ).setPropertyValueobject, propertyName, value );
  }
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

}