Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » persister » entity » [javadoc | source]
    1   /*
    2    * Hibernate, Relational Persistence for Idiomatic Java
    3    *
    4    * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
    5    * indicated by the @author tags or express copyright attribution
    6    * statements applied by the authors.  All third-party contributions are
    7    * distributed under license by Red Hat Middleware LLC.
    8    *
    9    * This copyrighted material is made available to anyone wishing to use, modify,
   10    * copy, or redistribute it subject to the terms and conditions of the GNU
   11    * Lesser General Public License, as published by the Free Software Foundation.
   12    *
   13    * This program is distributed in the hope that it will be useful,
   14    * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   15    * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
   16    * for more details.
   17    *
   18    * You should have received a copy of the GNU Lesser General Public License
   19    * along with this distribution; if not, write to:
   20    * Free Software Foundation, Inc.
   21    * 51 Franklin Street, Fifth Floor
   22    * Boston, MA  02110-1301  USA
   23    *
   24    */
   25   package org.hibernate.persister.entity;
   26   
   27   import java.io.Serializable;
   28   import java.sql.PreparedStatement;
   29   import java.sql.ResultSet;
   30   import java.sql.SQLException;
   31   import java.util.ArrayList;
   32   import java.util.Arrays;
   33   import java.util.HashMap;
   34   import java.util.HashSet;
   35   import java.util.Iterator;
   36   import java.util.Map;
   37   import java.util.Set;
   38   import java.util.Comparator;
   39   
   40   import org.slf4j.Logger;
   41   import org.slf4j.LoggerFactory;
   42   import org.hibernate.AssertionFailure;
   43   import org.hibernate.EntityMode;
   44   import org.hibernate.FetchMode;
   45   import org.hibernate.HibernateException;
   46   import org.hibernate.LockMode;
   47   import org.hibernate.MappingException;
   48   import org.hibernate.QueryException;
   49   import org.hibernate.StaleObjectStateException;
   50   import org.hibernate.StaleStateException;
   51   import org.hibernate.jdbc.Expectation;
   52   import org.hibernate.jdbc.Expectations;
   53   import org.hibernate.jdbc.TooManyRowsAffectedException;
   54   import org.hibernate.dialect.lock.LockingStrategy;
   55   import org.hibernate.cache.CacheConcurrencyStrategy;
   56   import org.hibernate.cache.CacheKey;
   57   import org.hibernate.cache.access.EntityRegionAccessStrategy;
   58   import org.hibernate.cache.entry.CacheEntry;
   59   import org.hibernate.cache.entry.CacheEntryStructure;
   60   import org.hibernate.cache.entry.StructuredCacheEntry;
   61   import org.hibernate.cache.entry.UnstructuredCacheEntry;
   62   import org.hibernate.engine.CascadeStyle;
   63   import org.hibernate.engine.CascadingAction;
   64   import org.hibernate.engine.EntityEntry;
   65   import org.hibernate.engine.Mapping;
   66   import org.hibernate.engine.SessionFactoryImplementor;
   67   import org.hibernate.engine.SessionImplementor;
   68   import org.hibernate.engine.Versioning;
   69   import org.hibernate.engine.ExecuteUpdateResultCheckStyle;
   70   import org.hibernate.engine.EntityKey;
   71   import org.hibernate.engine.ValueInclusion;
   72   import org.hibernate.exception.JDBCExceptionHelper;
   73   import org.hibernate.id.IdentifierGenerator;
   74   import org.hibernate.id.PostInsertIdentifierGenerator;
   75   import org.hibernate.id.PostInsertIdentityPersister;
   76   import org.hibernate.id.insert.InsertGeneratedIdentifierDelegate;
   77   import org.hibernate.id.insert.Binder;
   78   import org.hibernate.intercept.LazyPropertyInitializer;
   79   import org.hibernate.intercept.FieldInterceptionHelper;
   80   import org.hibernate.intercept.FieldInterceptor;
   81   import org.hibernate.loader.entity.BatchingEntityLoader;
   82   import org.hibernate.loader.entity.CascadeEntityLoader;
   83   import org.hibernate.loader.entity.EntityLoader;
   84   import org.hibernate.loader.entity.UniqueEntityLoader;
   85   import org.hibernate.mapping.Column;
   86   import org.hibernate.mapping.Component;
   87   import org.hibernate.mapping.PersistentClass;
   88   import org.hibernate.mapping.Property;
   89   import org.hibernate.mapping.Selectable;
   90   import org.hibernate.metadata.ClassMetadata;
   91   import org.hibernate.pretty.MessageHelper;
   92   import org.hibernate.property.BackrefPropertyAccessor;
   93   import org.hibernate.sql.Alias;
   94   import org.hibernate.sql.Delete;
   95   import org.hibernate.sql.Insert;
   96   import org.hibernate.sql.JoinFragment;
   97   import org.hibernate.sql.Select;
   98   import org.hibernate.sql.SelectFragment;
   99   import org.hibernate.sql.SimpleSelect;
  100   import org.hibernate.sql.Template;
  101   import org.hibernate.sql.Update;
  102   import org.hibernate.tuple.entity.EntityMetamodel;
  103   import org.hibernate.tuple.entity.EntityTuplizer;
  104   import org.hibernate.tuple.Tuplizer;
  105   import org.hibernate.type.AbstractComponentType;
  106   import org.hibernate.type.AssociationType;
  107   import org.hibernate.type.EntityType;
  108   import org.hibernate.type.Type;
  109   import org.hibernate.type.TypeFactory;
  110   import org.hibernate.type.VersionType;
  111   import org.hibernate.util.ArrayHelper;
  112   import org.hibernate.util.CollectionHelper;
  113   import org.hibernate.util.FilterHelper;
  114   import org.hibernate.util.StringHelper;
  115   
  116   /**
  117    * Basic functionality for persisting an entity via JDBC
  118    * through either generated or custom SQL
  119    *
  120    * @author Gavin King
  121    */
  122   public abstract class AbstractEntityPersister
  123   		implements OuterJoinLoadable, Queryable, ClassMetadata, UniqueKeyLoadable,
  124   		SQLLoadable, LazyPropertyInitializer, PostInsertIdentityPersister, Lockable {
  125   
  126   	private static final Logger log = LoggerFactory.getLogger( AbstractEntityPersister.class );
  127   
  128   	public static final String ENTITY_CLASS = "class";
  129   
  130   	// moved up from AbstractEntityPersister ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  131   	private final SessionFactoryImplementor factory;
  132   	private final EntityRegionAccessStrategy cacheAccessStrategy;
  133   	private final boolean isLazyPropertiesCacheable;
  134   	private final CacheEntryStructure cacheEntryStructure;
  135   	private final EntityMetamodel entityMetamodel;
  136   	private final Map entityNameBySubclass = new HashMap();
  137   	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  138   
  139   	private final String[] rootTableKeyColumnNames;
  140   	private final String[] identifierAliases;
  141   	private final int identifierColumnSpan;
  142   	private final String versionColumnName;
  143   	private final boolean hasFormulaProperties;
  144   	private final int batchSize;
  145   	private final boolean hasSubselectLoadableCollections;
  146   	protected final String rowIdName;
  147   
  148   	private final Set lazyProperties;
  149   
  150   	// The optional SQL string defined in the where attribute
  151   	private final String sqlWhereString;
  152   	private final String sqlWhereStringTemplate;
  153   
  154   	//information about properties of this class,
  155   	//including inherited properties
  156   	//(only really needed for updatable/insertable properties)
  157   	private final int[] propertyColumnSpans;
  158   	private final String[] propertySubclassNames;
  159   	private final String[][] propertyColumnAliases;
  160   	private final String[][] propertyColumnNames;
  161   	private final String[][] propertyColumnFormulaTemplates;
  162   	private final boolean[][] propertyColumnUpdateable;
  163   	private final boolean[][] propertyColumnInsertable;
  164   	private final boolean[] propertyUniqueness;
  165   	private final boolean[] propertySelectable;
  166   
  167   	//information about lazy properties of this class
  168   	private final String[] lazyPropertyNames;
  169   	private final int[] lazyPropertyNumbers;
  170   	private final Type[] lazyPropertyTypes;
  171   	private final String[][] lazyPropertyColumnAliases;
  172   
  173   	//information about all properties in class hierarchy
  174   	private final String[] subclassPropertyNameClosure;
  175   	private final String[] subclassPropertySubclassNameClosure;
  176   	private final Type[] subclassPropertyTypeClosure;
  177   	private final String[][] subclassPropertyFormulaTemplateClosure;
  178   	private final String[][] subclassPropertyColumnNameClosure;
  179   	private final FetchMode[] subclassPropertyFetchModeClosure;
  180   	private final boolean[] subclassPropertyNullabilityClosure;
  181   	private final boolean[] propertyDefinedOnSubclass;
  182   	private final int[][] subclassPropertyColumnNumberClosure;
  183   	private final int[][] subclassPropertyFormulaNumberClosure;
  184   	private final CascadeStyle[] subclassPropertyCascadeStyleClosure;
  185   
  186   	//information about all columns/formulas in class hierarchy
  187   	private final String[] subclassColumnClosure;
  188   	private final boolean[] subclassColumnLazyClosure;
  189   	private final String[] subclassColumnAliasClosure;
  190   	private final boolean[] subclassColumnSelectableClosure;
  191   	private final String[] subclassFormulaClosure;
  192   	private final String[] subclassFormulaTemplateClosure;
  193   	private final String[] subclassFormulaAliasClosure;
  194   	private final boolean[] subclassFormulaLazyClosure;
  195   
  196   	// dynamic filters attached to the class-level
  197   	private final FilterHelper filterHelper;
  198   
  199   	private final Map uniqueKeyLoaders = new HashMap();
  200   	private final Map lockers = new HashMap();
  201   	private final Map loaders = new HashMap();
  202   
  203   	// SQL strings
  204   	private String sqlVersionSelectString;
  205   	private String sqlSnapshotSelectString;
  206   	private String sqlLazySelectString;
  207   
  208   	private String sqlIdentityInsertString;
  209   	private String sqlUpdateByRowIdString;
  210   	private String sqlLazyUpdateByRowIdString;
  211   
  212   	private String[] sqlDeleteStrings;
  213   	private String[] sqlInsertStrings;
  214   	private String[] sqlUpdateStrings;
  215   	private String[] sqlLazyUpdateStrings;
  216   
  217   	private String sqlInsertGeneratedValuesSelectString;
  218   	private String sqlUpdateGeneratedValuesSelectString;
  219   
  220   	//Custom SQL (would be better if these were private)
  221   	protected boolean[] insertCallable;
  222   	protected boolean[] updateCallable;
  223   	protected boolean[] deleteCallable;
  224   	protected String[] customSQLInsert;
  225   	protected String[] customSQLUpdate;
  226   	protected String[] customSQLDelete;
  227   	protected ExecuteUpdateResultCheckStyle[] insertResultCheckStyles;
  228   	protected ExecuteUpdateResultCheckStyle[] updateResultCheckStyles;
  229   	protected ExecuteUpdateResultCheckStyle[] deleteResultCheckStyles;
  230   
  231   	private InsertGeneratedIdentifierDelegate identityDelegate;
  232   
  233   	private boolean[] tableHasColumns;
  234   
  235   	private final String loaderName;
  236   
  237   	private UniqueEntityLoader queryLoader;
  238   
  239   	private final String temporaryIdTableName;
  240   	private final String temporaryIdTableDDL;
  241   
  242   	private final Map subclassPropertyAliases = new HashMap();
  243   	private final Map subclassPropertyColumnNames = new HashMap();
  244   
  245   	protected final BasicEntityPropertyMapping propertyMapping;
  246   
  247   	protected void addDiscriminatorToInsert(Insert insert) {}
  248   
  249   	protected void addDiscriminatorToSelect(SelectFragment select, String name, String suffix) {}
  250   
  251   	protected abstract int[] getSubclassColumnTableNumberClosure();
  252   
  253   	protected abstract int[] getSubclassFormulaTableNumberClosure();
  254   
  255   	public abstract String getSubclassTableName(int j);
  256   
  257   	protected abstract String[] getSubclassTableKeyColumns(int j);
  258   
  259   	protected abstract boolean isClassOrSuperclassTable(int j);
  260   
  261   	protected abstract int getSubclassTableSpan();
  262   
  263   	protected abstract int getTableSpan();
  264   
  265   	protected abstract boolean isTableCascadeDeleteEnabled(int j);
  266   
  267   	protected abstract String getTableName(int j);
  268   
  269   	protected abstract String[] getKeyColumns(int j);
  270   
  271   	protected abstract boolean isPropertyOfTable(int property, int j);
  272   
  273   	protected abstract int[] getPropertyTableNumbersInSelect();
  274   
  275   	protected abstract int[] getPropertyTableNumbers();
  276   
  277   	protected abstract int getSubclassPropertyTableNumber(int i);
  278   
  279   	protected abstract String filterFragment(String alias) throws MappingException;
  280   
  281   	private static final String DISCRIMINATOR_ALIAS = "clazz_";
  282   
  283   	public String getDiscriminatorColumnName() {
  284   		return DISCRIMINATOR_ALIAS;
  285   	}
  286   
  287   	protected String getDiscriminatorAlias() {
  288   		return DISCRIMINATOR_ALIAS;
  289   	}
  290   
  291   	protected String getDiscriminatorFormulaTemplate() {
  292   		return null;
  293   	}
  294   
  295   	protected boolean isInverseTable(int j) {
  296   		return false;
  297   	}
  298   
  299   	protected boolean isNullableTable(int j) {
  300   		return false;
  301   	}
  302   
  303   	protected boolean isNullableSubclassTable(int j) {
  304   		return false;
  305   	}
  306   
  307   	protected boolean isInverseSubclassTable(int j) {
  308   		return false;
  309   	}
  310   
  311   	public boolean isSubclassEntityName(String entityName) {
  312   		return entityMetamodel.getSubclassEntityNames().contains(entityName);
  313   	}
  314   
  315   	private boolean[] getTableHasColumns() {
  316   		return tableHasColumns;
  317   	}
  318   
  319   	public String[] getRootTableKeyColumnNames() {
  320   		return rootTableKeyColumnNames;
  321   	}
  322   
  323   	protected String[] getSQLUpdateByRowIdStrings() {
  324   		if ( sqlUpdateByRowIdString == null ) {
  325   			throw new AssertionFailure( "no update by row id" );
  326   		}
  327   		String[] result = new String[getTableSpan() + 1];
  328   		result[0] = sqlUpdateByRowIdString;
  329   		System.arraycopy( sqlUpdateStrings, 0, result, 1, getTableSpan() );
  330   		return result;
  331   	}
  332   
  333   	protected String[] getSQLLazyUpdateByRowIdStrings() {
  334   		if ( sqlLazyUpdateByRowIdString == null ) {
  335   			throw new AssertionFailure( "no update by row id" );
  336   		}
  337   		String[] result = new String[getTableSpan()];
  338   		result[0] = sqlLazyUpdateByRowIdString;
  339   		for ( int i = 1; i < getTableSpan(); i++ ) {
  340   			result[i] = sqlLazyUpdateStrings[i];
  341   		}
  342   		return result;
  343   	}
  344   
  345   	protected String getSQLSnapshotSelectString() {
  346   		return sqlSnapshotSelectString;
  347   	}
  348   
  349   	protected String getSQLLazySelectString() {
  350   		return sqlLazySelectString;
  351   	}
  352   
  353   	protected String[] getSQLDeleteStrings() {
  354   		return sqlDeleteStrings;
  355   	}
  356   
  357   	protected String[] getSQLInsertStrings() {
  358   		return sqlInsertStrings;
  359   	}
  360   
  361   	protected String[] getSQLUpdateStrings() {
  362   		return sqlUpdateStrings;
  363   	}
  364   
  365   	protected String[] getSQLLazyUpdateStrings() {
  366   		return sqlLazyUpdateStrings;
  367   	}
  368   
  369   	/**
  370   	 * The query that inserts a row, letting the database generate an id
  371   	 * 
  372   	 * @return The IDENTITY-based insertion query.
  373   	 */
  374   	protected String getSQLIdentityInsertString() {
  375   		return sqlIdentityInsertString;
  376   	}
  377   
  378   	protected String getVersionSelectString() {
  379   		return sqlVersionSelectString;
  380   	}
  381   
  382   	protected boolean isInsertCallable(int j) {
  383   		return insertCallable[j];
  384   	}
  385   
  386   	protected boolean isUpdateCallable(int j) {
  387   		return updateCallable[j];
  388   	}
  389   
  390   	protected boolean isDeleteCallable(int j) {
  391   		return deleteCallable[j];
  392   	}
  393   
  394   	protected boolean isSubclassPropertyDeferred(String propertyName, String entityName) {
  395   		return false;
  396   	}
  397   
  398   	protected boolean isSubclassTableSequentialSelect(int j) {
  399   		return false;
  400   	}
  401   
  402   	public boolean hasSequentialSelect() {
  403   		return false;
  404   	}
  405   
  406   	/**
  407   	 * Decide which tables need to be updated.
  408   	 * <p/>
  409   	 * The return here is an array of boolean values with each index corresponding
  410   	 * to a given table in the scope of this persister.
  411   	 *
  412   	 * @param dirtyProperties The indices of all the entity properties considered dirty.
  413   	 * @param hasDirtyCollection Whether any collections owned by the entity which were considered dirty.
  414   	 *
  415   	 * @return Array of booleans indicating which table require updating.
  416   	 */
  417   	protected boolean[] getTableUpdateNeeded(final int[] dirtyProperties, boolean hasDirtyCollection) {
  418   
  419   		if ( dirtyProperties == null ) {
  420   			return getTableHasColumns(); // for objects that came in via update()
  421   		}
  422   		else {
  423   			boolean[] updateability = getPropertyUpdateability();
  424   			int[] propertyTableNumbers = getPropertyTableNumbers();
  425   			boolean[] tableUpdateNeeded = new boolean[ getTableSpan() ];
  426   			for ( int i = 0; i < dirtyProperties.length; i++ ) {
  427   				int property = dirtyProperties[i];
  428   				int table = propertyTableNumbers[property];
  429   				tableUpdateNeeded[table] = tableUpdateNeeded[table] ||
  430   						( getPropertyColumnSpan(property) > 0 && updateability[property] );
  431   			}
  432   			if ( isVersioned() ) {
  433   				tableUpdateNeeded[0] = tableUpdateNeeded[0] ||
  434   					Versioning.isVersionIncrementRequired( dirtyProperties, hasDirtyCollection, getPropertyVersionability() );
  435   			}
  436   			return tableUpdateNeeded;
  437   		}
  438   	}
  439   
  440   	public boolean hasRowId() {
  441   		return rowIdName != null;
  442   	}
  443   
  444   	public AbstractEntityPersister(
  445   			final PersistentClass persistentClass,
  446   			final EntityRegionAccessStrategy cacheAccessStrategy,
  447   			final SessionFactoryImplementor factory) throws HibernateException {
  448   
  449   		// moved up from AbstractEntityPersister ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  450   		this.factory = factory;
  451   		this.cacheAccessStrategy = cacheAccessStrategy;
  452   		isLazyPropertiesCacheable = persistentClass.isLazyPropertiesCacheable();
  453   		this.cacheEntryStructure = factory.getSettings().isStructuredCacheEntriesEnabled() ?
  454   				(CacheEntryStructure) new StructuredCacheEntry(this) :
  455   				(CacheEntryStructure) new UnstructuredCacheEntry();
  456   
  457   		this.entityMetamodel = new EntityMetamodel( persistentClass, factory );
  458   
  459   		if ( persistentClass.hasPojoRepresentation() ) {
  460   			//TODO: this is currently specific to pojos, but need to be available for all entity-modes
  461   			Iterator iter = persistentClass.getSubclassIterator();
  462   			while ( iter.hasNext() ) {
  463   				PersistentClass pc = ( PersistentClass ) iter.next();
  464   				entityNameBySubclass.put( pc.getMappedClass(), pc.getEntityName() );
  465   			}
  466   		}
  467   		// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  468   
  469   		int batch = persistentClass.getBatchSize();
  470   		if ( batch == -1 ) {
  471   			batch = factory.getSettings().getDefaultBatchFetchSize();
  472   		}
  473   		batchSize = batch;
  474   		hasSubselectLoadableCollections = persistentClass.hasSubselectLoadableCollections();
  475   
  476   		propertyMapping = new BasicEntityPropertyMapping( this );
  477   
  478   		// IDENTIFIER
  479   
  480   		identifierColumnSpan = persistentClass.getIdentifier().getColumnSpan();
  481   		rootTableKeyColumnNames = new String[identifierColumnSpan];
  482   		identifierAliases = new String[identifierColumnSpan];
  483   
  484   		rowIdName = persistentClass.getRootTable().getRowId();
  485   
  486   		loaderName = persistentClass.getLoaderName();
  487   
  488   		Iterator iter = persistentClass.getIdentifier().getColumnIterator();
  489   		int i = 0;
  490   		while ( iter.hasNext() ) {
  491   			Column col = ( Column ) iter.next();
  492   			rootTableKeyColumnNames[i] = col.getQuotedName( factory.getDialect() );
  493   			identifierAliases[i] = col.getAlias( factory.getDialect(), persistentClass.getRootTable() );
  494   			i++;
  495   		}
  496   
  497   		// VERSION
  498   
  499   		if ( persistentClass.isVersioned() ) {
  500   			versionColumnName = ( ( Column ) persistentClass.getVersion().getColumnIterator().next() ).getQuotedName( factory.getDialect() );
  501   		}
  502   		else {
  503   			versionColumnName = null;
  504   		}
  505   
  506   		//WHERE STRING
  507   
  508   		sqlWhereString = StringHelper.isNotEmpty( persistentClass.getWhere() ) ? "( " + persistentClass.getWhere() + ") " : null;
  509   		sqlWhereStringTemplate = sqlWhereString == null ?
  510   				null :
  511   				Template.renderWhereStringTemplate( sqlWhereString, factory.getDialect(), factory.getSqlFunctionRegistry() );
  512   
  513   		// PROPERTIES
  514   
  515   		final boolean lazyAvailable = isInstrumented(EntityMode.POJO);
  516   
  517   		int hydrateSpan = entityMetamodel.getPropertySpan();
  518   		propertyColumnSpans = new int[hydrateSpan];
  519   		propertySubclassNames = new String[hydrateSpan];
  520   		propertyColumnAliases = new String[hydrateSpan][];
  521   		propertyColumnNames = new String[hydrateSpan][];
  522   		propertyColumnFormulaTemplates = new String[hydrateSpan][];
  523   		propertyUniqueness = new boolean[hydrateSpan];
  524   		propertySelectable = new boolean[hydrateSpan];
  525   		propertyColumnUpdateable = new boolean[hydrateSpan][];
  526   		propertyColumnInsertable = new boolean[hydrateSpan][];
  527   		HashSet thisClassProperties = new HashSet();
  528   
  529   		lazyProperties = new HashSet();
  530   		ArrayList lazyNames = new ArrayList();
  531   		ArrayList lazyNumbers = new ArrayList();
  532   		ArrayList lazyTypes = new ArrayList();
  533   		ArrayList lazyColAliases = new ArrayList();
  534   
  535   		iter = persistentClass.getPropertyClosureIterator();
  536   		i = 0;
  537   		boolean foundFormula = false;
  538   		while ( iter.hasNext() ) {
  539   			Property prop = ( Property ) iter.next();
  540   			thisClassProperties.add( prop );
  541   
  542   			int span = prop.getColumnSpan();
  543   			propertyColumnSpans[i] = span;
  544   			propertySubclassNames[i] = prop.getPersistentClass().getEntityName();
  545   			String[] colNames = new String[span];
  546   			String[] colAliases = new String[span];
  547   			String[] templates = new String[span];
  548   			Iterator colIter = prop.getColumnIterator();
  549   			int k = 0;
  550   			while ( colIter.hasNext() ) {
  551   				Selectable thing = ( Selectable ) colIter.next();
  552   				colAliases[k] = thing.getAlias( factory.getDialect() , prop.getValue().getTable() );
  553   				if ( thing.isFormula() ) {
  554   					foundFormula = true;
  555   					templates[k] = thing.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
  556   				}
  557   				else {
  558   					colNames[k] = thing.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
  559   				}
  560   				k++;
  561   			}
  562   			propertyColumnNames[i] = colNames;
  563   			propertyColumnFormulaTemplates[i] = templates;
  564   			propertyColumnAliases[i] = colAliases;
  565   
  566   			if ( lazyAvailable && prop.isLazy() ) {
  567   				lazyProperties.add( prop.getName() );
  568   				lazyNames.add( prop.getName() );
  569   				lazyNumbers.add( new Integer( i ) );
  570   				lazyTypes.add( prop.getValue().getType() );
  571   				lazyColAliases.add( colAliases );
  572   			}
  573   
  574   			propertyColumnUpdateable[i] = prop.getValue().getColumnUpdateability();
  575   			propertyColumnInsertable[i] = prop.getValue().getColumnInsertability();
  576   
  577   			propertySelectable[i] = prop.isSelectable();
  578   
  579   			propertyUniqueness[i] = prop.getValue().isAlternateUniqueKey();
  580   
  581   			i++;
  582   
  583   		}
  584   		hasFormulaProperties = foundFormula;
  585   		lazyPropertyColumnAliases = ArrayHelper.to2DStringArray( lazyColAliases );
  586   		lazyPropertyNames = ArrayHelper.toStringArray( lazyNames );
  587   		lazyPropertyNumbers = ArrayHelper.toIntArray( lazyNumbers );
  588   		lazyPropertyTypes = ArrayHelper.toTypeArray( lazyTypes );
  589   
  590   		// SUBCLASS PROPERTY CLOSURE
  591   
  592   		ArrayList columns = new ArrayList();
  593   		ArrayList columnsLazy = new ArrayList();
  594   		ArrayList aliases = new ArrayList();
  595   		ArrayList formulas = new ArrayList();
  596   		ArrayList formulaAliases = new ArrayList();
  597   		ArrayList formulaTemplates = new ArrayList();
  598   		ArrayList formulasLazy = new ArrayList();
  599   		ArrayList types = new ArrayList();
  600   		ArrayList names = new ArrayList();
  601   		ArrayList classes = new ArrayList();
  602   		ArrayList templates = new ArrayList();
  603   		ArrayList propColumns = new ArrayList();
  604   		ArrayList joinedFetchesList = new ArrayList();
  605   		ArrayList cascades = new ArrayList();
  606   		ArrayList definedBySubclass = new ArrayList();
  607   		ArrayList propColumnNumbers = new ArrayList();
  608   		ArrayList propFormulaNumbers = new ArrayList();
  609   		ArrayList columnSelectables = new ArrayList();
  610   		ArrayList propNullables = new ArrayList();
  611   
  612   		iter = persistentClass.getSubclassPropertyClosureIterator();
  613   		while ( iter.hasNext() ) {
  614   			Property prop = ( Property ) iter.next();
  615   			names.add( prop.getName() );
  616   			classes.add( prop.getPersistentClass().getEntityName() );
  617   			boolean isDefinedBySubclass = !thisClassProperties.contains( prop );
  618   			definedBySubclass.add( Boolean.valueOf( isDefinedBySubclass ) );
  619   			propNullables.add( Boolean.valueOf( prop.isOptional() || isDefinedBySubclass ) ); //TODO: is this completely correct?
  620   			types.add( prop.getType() );
  621   
  622   			Iterator colIter = prop.getColumnIterator();
  623   			String[] cols = new String[prop.getColumnSpan()];
  624   			String[] forms = new String[prop.getColumnSpan()];
  625   			int[] colnos = new int[prop.getColumnSpan()];
  626   			int[] formnos = new int[prop.getColumnSpan()];
  627   			int l = 0;
  628   			Boolean lazy = Boolean.valueOf( prop.isLazy() && lazyAvailable );
  629   			while ( colIter.hasNext() ) {
  630   				Selectable thing = ( Selectable ) colIter.next();
  631   				if ( thing.isFormula() ) {
  632   					String template = thing.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
  633   					formnos[l] = formulaTemplates.size();
  634   					colnos[l] = -1;
  635   					formulaTemplates.add( template );
  636   					forms[l] = template;
  637   					formulas.add( thing.getText( factory.getDialect() ) );
  638   					formulaAliases.add( thing.getAlias( factory.getDialect() ) );
  639   					formulasLazy.add( lazy );
  640   				}
  641   				else {
  642   					String colName = thing.getTemplate( factory.getDialect(), factory.getSqlFunctionRegistry() );
  643   					colnos[l] = columns.size(); //before add :-)
  644   					formnos[l] = -1;
  645   					columns.add( colName );
  646   					cols[l] = colName;
  647   					aliases.add( thing.getAlias( factory.getDialect(), prop.getValue().getTable() ) );
  648   					columnsLazy.add( lazy );
  649   					columnSelectables.add( Boolean.valueOf( prop.isSelectable() ) );
  650   				}
  651   				l++;
  652   			}
  653   			propColumns.add( cols );
  654   			templates.add( forms );
  655   			propColumnNumbers.add( colnos );
  656   			propFormulaNumbers.add( formnos );
  657   
  658   			joinedFetchesList.add( prop.getValue().getFetchMode() );
  659   			cascades.add( prop.getCascadeStyle() );
  660   		}
  661   		subclassColumnClosure = ArrayHelper.toStringArray( columns );
  662   		subclassColumnAliasClosure = ArrayHelper.toStringArray( aliases );
  663   		subclassColumnLazyClosure = ArrayHelper.toBooleanArray( columnsLazy );
  664   		subclassColumnSelectableClosure = ArrayHelper.toBooleanArray( columnSelectables );
  665   
  666   		subclassFormulaClosure = ArrayHelper.toStringArray( formulas );
  667   		subclassFormulaTemplateClosure = ArrayHelper.toStringArray( formulaTemplates );
  668   		subclassFormulaAliasClosure = ArrayHelper.toStringArray( formulaAliases );
  669   		subclassFormulaLazyClosure = ArrayHelper.toBooleanArray( formulasLazy );
  670   
  671   		subclassPropertyNameClosure = ArrayHelper.toStringArray( names );
  672   		subclassPropertySubclassNameClosure = ArrayHelper.toStringArray( classes );
  673   		subclassPropertyTypeClosure = ArrayHelper.toTypeArray( types );
  674   		subclassPropertyNullabilityClosure = ArrayHelper.toBooleanArray( propNullables );
  675   		subclassPropertyFormulaTemplateClosure = ArrayHelper.to2DStringArray( templates );
  676   		subclassPropertyColumnNameClosure = ArrayHelper.to2DStringArray( propColumns );
  677   		subclassPropertyColumnNumberClosure = ArrayHelper.to2DIntArray( propColumnNumbers );
  678   		subclassPropertyFormulaNumberClosure = ArrayHelper.to2DIntArray( propFormulaNumbers );
  679   
  680   		subclassPropertyCascadeStyleClosure = new CascadeStyle[cascades.size()];
  681   		iter = cascades.iterator();
  682   		int j = 0;
  683   		while ( iter.hasNext() ) {
  684   			subclassPropertyCascadeStyleClosure[j++] = ( CascadeStyle ) iter.next();
  685   		}
  686   		subclassPropertyFetchModeClosure = new FetchMode[joinedFetchesList.size()];
  687   		iter = joinedFetchesList.iterator();
  688   		j = 0;
  689   		while ( iter.hasNext() ) {
  690   			subclassPropertyFetchModeClosure[j++] = ( FetchMode ) iter.next();
  691   		}
  692   
  693   		propertyDefinedOnSubclass = new boolean[definedBySubclass.size()];
  694   		iter = definedBySubclass.iterator();
  695   		j = 0;
  696   		while ( iter.hasNext() ) {
  697   			propertyDefinedOnSubclass[j++] = ( ( Boolean ) iter.next() ).booleanValue();
  698   		}
  699   
  700   		// Handle any filters applied to the class level
  701   		filterHelper = new FilterHelper( persistentClass.getFilterMap(), factory.getDialect(), factory.getSqlFunctionRegistry() );
  702   
  703   		temporaryIdTableName = persistentClass.getTemporaryIdTableName();
  704   		temporaryIdTableDDL = persistentClass.getTemporaryIdTableDDL();
  705   	}
  706   
  707   	protected String generateLazySelectString() {
  708   
  709   		if ( !entityMetamodel.hasLazyProperties() ) {
  710   			return null;
  711   		}
  712   
  713   		HashSet tableNumbers = new HashSet();
  714   		ArrayList columnNumbers = new ArrayList();
  715   		ArrayList formulaNumbers = new ArrayList();
  716   		for ( int i = 0; i < lazyPropertyNames.length; i++ ) {
  717   			// all this only really needs to consider properties
  718   			// of this class, not its subclasses, but since we
  719   			// are reusing code used for sequential selects, we
  720   			// use the subclass closure
  721   			int propertyNumber = getSubclassPropertyIndex( lazyPropertyNames[i] );
  722   
  723   			int tableNumber = getSubclassPropertyTableNumber( propertyNumber );
  724   			tableNumbers.add( new Integer( tableNumber ) );
  725   
  726   			int[] colNumbers = subclassPropertyColumnNumberClosure[propertyNumber];
  727   			for ( int j = 0; j < colNumbers.length; j++ ) {
  728   				if ( colNumbers[j]!=-1 ) {
  729   					columnNumbers.add( new Integer( colNumbers[j] ) );
  730   				}
  731   			}
  732   			int[] formNumbers = subclassPropertyFormulaNumberClosure[propertyNumber];
  733   			for ( int j = 0; j < formNumbers.length; j++ ) {
  734   				if ( formNumbers[j]!=-1 ) {
  735   					formulaNumbers.add( new Integer( formNumbers[j] ) );
  736   				}
  737   			}
  738   		}
  739   
  740   		if ( columnNumbers.size()==0 && formulaNumbers.size()==0 ) {
  741   			// only one-to-one is lazy fetched
  742   			return null;
  743   		}
  744   
  745   		return renderSelect( ArrayHelper.toIntArray( tableNumbers ),
  746   				ArrayHelper.toIntArray( columnNumbers ),
  747   				ArrayHelper.toIntArray( formulaNumbers ) );
  748   
  749   	}
  750   
  751   	public Object initializeLazyProperty(String fieldName, Object entity, SessionImplementor session)
  752   			throws HibernateException {
  753   
  754   		final Serializable id = session.getContextEntityIdentifier( entity );
  755   
  756   		final EntityEntry entry = session.getPersistenceContext().getEntry( entity );
  757   		if ( entry == null ) {
  758   			throw new HibernateException( "entity is not associated with the session: " + id );
  759   		}
  760   
  761   		if ( log.isTraceEnabled() ) {
  762   			log.trace(
  763   					"initializing lazy properties of: " +
  764   					MessageHelper.infoString( this, id, getFactory() ) +
  765   					", field access: " + fieldName
  766   				);
  767   		}
  768   
  769   		if ( hasCache() ) {
  770   			CacheKey cacheKey = new CacheKey(id, getIdentifierType(), getEntityName(), session.getEntityMode(), getFactory() );
  771   			Object ce = getCacheAccessStrategy().get( cacheKey, session.getTimestamp() );
  772   			if (ce!=null) {
  773   				CacheEntry cacheEntry = (CacheEntry) getCacheEntryStructure().destructure(ce, factory);
  774   				if ( !cacheEntry.areLazyPropertiesUnfetched() ) {
  775   					//note early exit here:
  776   					return initializeLazyPropertiesFromCache( fieldName, entity, session, entry, cacheEntry );
  777   				}
  778   			}
  779   		}
  780   
  781   		return initializeLazyPropertiesFromDatastore( fieldName, entity, session, id, entry );
  782   
  783   	}
  784   
  785   	private Object initializeLazyPropertiesFromDatastore(
  786   			final String fieldName,
  787   			final Object entity,
  788   			final SessionImplementor session,
  789   			final Serializable id,
  790   			final EntityEntry entry) {
  791   
  792   		if ( !hasLazyProperties() ) {
  793   			throw new AssertionFailure("no lazy properties");
  794   		}
  795   
  796   		log.trace("initializing lazy properties from datastore");
  797   
  798   		try {
  799   
  800   			Object result = null;
  801   			PreparedStatement ps = null;
  802   			try {
  803   				final String lazySelect = getSQLLazySelectString();
  804   				ResultSet rs = null;
  805   				try {
  806   					if ( lazySelect != null ) {
  807   						// null sql means that the only lazy properties
  808   						// are shared PK one-to-one associations which are
  809   						// handled differently in the Type#nullSafeGet code...
  810   						ps = session.getBatcher().prepareSelectStatement(lazySelect);
  811   						getIdentifierType().nullSafeSet( ps, id, 1, session );
  812   						rs = ps.executeQuery();
  813   						rs.next();
  814   					}
  815   					final Object[] snapshot = entry.getLoadedState();
  816   					for ( int j = 0; j < lazyPropertyNames.length; j++ ) {
  817   						Object propValue = lazyPropertyTypes[j].nullSafeGet( rs, lazyPropertyColumnAliases[j], session, entity );
  818   						if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) {
  819   							result = propValue;
  820   						}
  821   					}
  822   				}
  823   				finally {
  824   					if ( rs != null ) {
  825   						rs.close();
  826   					}
  827   				}
  828   			}
  829   			finally {
  830   				if ( ps != null ) {
  831   					session.getBatcher().closeStatement( ps );
  832   				}
  833   			}
  834   
  835   			log.trace( "done initializing lazy properties" );
  836   
  837   			return result;
  838   
  839   		}
  840   		catch ( SQLException sqle ) {
  841   			throw JDBCExceptionHelper.convert(
  842   					getFactory().getSQLExceptionConverter(),
  843   					sqle,
  844   					"could not initialize lazy properties: " +
  845   					MessageHelper.infoString( this, id, getFactory() ),
  846   					getSQLLazySelectString()
  847   				);
  848   		}
  849   	}
  850   
  851   	private Object initializeLazyPropertiesFromCache(
  852   			final String fieldName,
  853   			final Object entity,
  854   			final SessionImplementor session,
  855   			final EntityEntry entry,
  856   			final CacheEntry cacheEntry
  857   	) {
  858   
  859   		log.trace("initializing lazy properties from second-level cache");
  860   
  861   		Object result = null;
  862   		Serializable[] disassembledValues = cacheEntry.getDisassembledState();
  863   		final Object[] snapshot = entry.getLoadedState();
  864   		for ( int j = 0; j < lazyPropertyNames.length; j++ ) {
  865   			final Object propValue = lazyPropertyTypes[j].assemble(
  866   					disassembledValues[ lazyPropertyNumbers[j] ],
  867   					session,
  868   					entity
  869   				);
  870   			if ( initializeLazyProperty( fieldName, entity, session, snapshot, j, propValue ) ) {
  871   				result = propValue;
  872   			}
  873   		}
  874   
  875   		log.trace( "done initializing lazy properties" );
  876   
  877   		return result;
  878   	}
  879   
  880   	private boolean initializeLazyProperty(
  881   			final String fieldName,
  882   			final Object entity,
  883   			final SessionImplementor session,
  884   			final Object[] snapshot,
  885   			final int j,
  886   			final Object propValue) {
  887   		setPropertyValue( entity, lazyPropertyNumbers[j], propValue, session.getEntityMode() );
  888   		if (snapshot != null) {
  889   			// object have been loaded with setReadOnly(true); HHH-2236
  890   			snapshot[ lazyPropertyNumbers[j] ] = lazyPropertyTypes[j].deepCopy( propValue, session.getEntityMode(), factory );
  891   		}
  892   		return fieldName.equals( lazyPropertyNames[j] );
  893   	}
  894   
  895   	public boolean isBatchable() {
  896   		return optimisticLockMode()==Versioning.OPTIMISTIC_LOCK_NONE ||
  897   			( !isVersioned() && optimisticLockMode()==Versioning.OPTIMISTIC_LOCK_VERSION ) ||
  898   			getFactory().getSettings().isJdbcBatchVersionedData();
  899   	}
  900   
  901   	public Serializable[] getQuerySpaces() {
  902   		return getPropertySpaces();
  903   	}
  904   
  905   	protected Set getLazyProperties() {
  906   		return lazyProperties;
  907   	}
  908   
  909   	public boolean isBatchLoadable() {
  910   		return batchSize > 1;
  911   	}
  912   
  913   	public String[] getIdentifierColumnNames() {
  914   		return rootTableKeyColumnNames;
  915   	}
  916   
  917   	protected int getIdentifierColumnSpan() {
  918   		return identifierColumnSpan;
  919   	}
  920   
  921   	protected String[] getIdentifierAliases() {
  922   		return identifierAliases;
  923   	}
  924   
  925   	public String getVersionColumnName() {
  926   		return versionColumnName;
  927   	}
  928   
  929   	protected String getVersionedTableName() {
  930   		return getTableName( 0 );
  931   	}
  932   
  933   	protected boolean[] getSubclassColumnLazyiness() {
  934   		return subclassColumnLazyClosure;
  935   	}
  936   
  937   	protected boolean[] getSubclassFormulaLazyiness() {
  938   		return subclassFormulaLazyClosure;
  939   	}
  940   
  941   	/**
  942   	 * We can't immediately add to the cache if we have formulas
  943   	 * which must be evaluated, or if we have the possibility of
  944   	 * two concurrent updates to the same item being merged on
  945   	 * the database. This can happen if (a) the item is not
  946   	 * versioned and either (b) we have dynamic update enabled
  947   	 * or (c) we have multiple tables holding the state of the
  948   	 * item.
  949   	 */
  950   	public boolean isCacheInvalidationRequired() {
  951   		return hasFormulaProperties() ||
  952   				( !isVersioned() && ( entityMetamodel.isDynamicUpdate() || getTableSpan() > 1 ) );
  953   	}
  954   
  955   	public boolean isLazyPropertiesCacheable() {
  956   		return isLazyPropertiesCacheable;
  957   	}
  958   
  959   	public String selectFragment(String alias, String suffix) {
  960   		return identifierSelectFragment( alias, suffix ) +
  961   				propertySelectFragment( alias, suffix, false );
  962   	}
  963   
  964   	public String[] getIdentifierAliases(String suffix) {
  965   		// NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
  966   		// was toUnqotedAliasStrings( getIdentiferColumnNames() ) before - now tried
  967   		// to remove that unqoting and missing aliases..
  968   		return new Alias( suffix ).toAliasStrings( getIdentifierAliases() );
  969   	}
  970   
  971   	public String[] getPropertyAliases(String suffix, int i) {
  972   		// NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
  973   		return new Alias( suffix ).toUnquotedAliasStrings( propertyColumnAliases[i] );
  974   	}
  975   
  976   	public String getDiscriminatorAlias(String suffix) {
  977   		// NOTE: this assumes something about how propertySelectFragment is implemented by the subclass!
  978   		// was toUnqotedAliasStrings( getdiscriminatorColumnName() ) before - now tried
  979   		// to remove that unqoting and missing aliases..
  980   		return entityMetamodel.hasSubclasses() ?
  981   				new Alias( suffix ).toAliasString( getDiscriminatorAlias() ) :
  982   				null;
  983   	}
  984   
  985   	public String identifierSelectFragment(String name, String suffix) {
  986   		return new SelectFragment()
  987   				.setSuffix( suffix )
  988   				.addColumns( name, getIdentifierColumnNames(), getIdentifierAliases() )
  989   				.toFragmentString()
  990   				.substring( 2 ); //strip leading ", "
  991   	}
  992   
  993   
  994   	public String propertySelectFragment(String name, String suffix, boolean allProperties) {
  995   
  996   		SelectFragment select = new SelectFragment()
  997   				.setSuffix( suffix )
  998   				.setUsedAliases( getIdentifierAliases() );
  999   
 1000   		int[] columnTableNumbers = getSubclassColumnTableNumberClosure();
 1001   		String[] columnAliases = getSubclassColumnAliasClosure();
 1002   		String[] columns = getSubclassColumnClosure();
 1003   		for ( int i = 0; i < getSubclassColumnClosure().length; i++ ) {
 1004   			boolean selectable = ( allProperties || !subclassColumnLazyClosure[i] ) &&
 1005   				!isSubclassTableSequentialSelect( columnTableNumbers[i] ) &&
 1006   				subclassColumnSelectableClosure[i];
 1007   			if ( selectable ) {
 1008   				String subalias = generateTableAlias( name, columnTableNumbers[i] );
 1009   				select.addColumn( subalias, columns[i], columnAliases[i] );
 1010   			}
 1011   		}
 1012   
 1013   		int[] formulaTableNumbers = getSubclassFormulaTableNumberClosure();
 1014   		String[] formulaTemplates = getSubclassFormulaTemplateClosure();
 1015   		String[] formulaAliases = getSubclassFormulaAliasClosure();
 1016   		for ( int i = 0; i < getSubclassFormulaTemplateClosure().length; i++ ) {
 1017   			boolean selectable = ( allProperties || !subclassFormulaLazyClosure[i] )
 1018   				&& !isSubclassTableSequentialSelect( formulaTableNumbers[i] );
 1019   			if ( selectable ) {
 1020   				String subalias = generateTableAlias( name, formulaTableNumbers[i] );
 1021   				select.addFormula( subalias, formulaTemplates[i], formulaAliases[i] );
 1022   			}
 1023   		}
 1024   
 1025   		if ( entityMetamodel.hasSubclasses() ) {
 1026   			addDiscriminatorToSelect( select, name, suffix );
 1027   		}
 1028   
 1029   		if ( hasRowId() ) {
 1030   			select.addColumn( name, rowIdName, ROWID_ALIAS );
 1031   		}
 1032   
 1033   		return select.toFragmentString();
 1034   	}
 1035   
 1036   	public Object[] getDatabaseSnapshot(Serializable id, SessionImplementor session)
 1037   			throws HibernateException {
 1038   
 1039   		if ( log.isTraceEnabled() ) {
 1040   			log.trace( "Getting current persistent state for: " + MessageHelper.infoString( this, id, getFactory() ) );
 1041   		}
 1042   
 1043   		try {
 1044   			PreparedStatement ps = session.getBatcher().prepareSelectStatement( getSQLSnapshotSelectString() );
 1045   			try {
 1046   				getIdentifierType().nullSafeSet( ps, id, 1, session );
 1047   				//if ( isVersioned() ) getVersionType().nullSafeSet( ps, version, getIdentifierColumnSpan()+1, session );
 1048   				ResultSet rs = ps.executeQuery();
 1049   				try {
 1050   					//if there is no resulting row, return null
 1051   					if ( !rs.next() ) {
 1052   						return null;
 1053   					}
 1054   
 1055   					//otherwise return the "hydrated" state (ie. associations are not resolved)
 1056   					Type[] types = getPropertyTypes();
 1057   					Object[] values = new Object[types.length];
 1058   					boolean[] includeProperty = getPropertyUpdateability();
 1059   					for ( int i = 0; i < types.length; i++ ) {
 1060   						if ( includeProperty[i] ) {
 1061   							values[i] = types[i].hydrate( rs, getPropertyAliases( "", i ), session, null ); //null owner ok??
 1062   						}
 1063   					}
 1064   					return values;
 1065   				}
 1066   				finally {
 1067   					rs.close();
 1068   				}
 1069   			}
 1070   			finally {
 1071   				session.getBatcher().closeStatement( ps );
 1072   			}
 1073   		}
 1074   		catch ( SQLException sqle ) {
 1075   			throw JDBCExceptionHelper.convert(
 1076   					getFactory().getSQLExceptionConverter(),
 1077   					sqle,
 1078   					"could not retrieve snapshot: " +
 1079   					MessageHelper.infoString( this, id, getFactory() ),
 1080   			        getSQLSnapshotSelectString()
 1081   				);
 1082   		}
 1083   
 1084   	}
 1085   
 1086   	/**
 1087   	 * Generate the SQL that selects the version number by id
 1088   	 */
 1089   	protected String generateSelectVersionString() {
 1090   		SimpleSelect select = new SimpleSelect( getFactory().getDialect() )
 1091   				.setTableName( getVersionedTableName() );
 1092   		if ( isVersioned() ) {
 1093   			select.addColumn( versionColumnName );
 1094   		}
 1095   		else {
 1096   			select.addColumns( rootTableKeyColumnNames );
 1097   		}
 1098   		if ( getFactory().getSettings().isCommentsEnabled() ) {
 1099   			select.setComment( "get version " + getEntityName() );
 1100   		}
 1101   		return select.addCondition( rootTableKeyColumnNames, "=?" ).toStatementString();
 1102   	}
 1103   
 1104   	protected String generateInsertGeneratedValuesSelectString() {
 1105   		return generateGeneratedValuesSelectString( getPropertyInsertGenerationInclusions() );
 1106   	}
 1107   
 1108   	protected String generateUpdateGeneratedValuesSelectString() {
 1109   		return generateGeneratedValuesSelectString( getPropertyUpdateGenerationInclusions() );
 1110   	}
 1111   
 1112   	private String generateGeneratedValuesSelectString(ValueInclusion[] inclusions) {
 1113   		Select select = new Select( getFactory().getDialect() );
 1114   
 1115   		if ( getFactory().getSettings().isCommentsEnabled() ) {
 1116   			select.setComment( "get generated state " + getEntityName() );
 1117   		}
 1118   
 1119   		String[] aliasedIdColumns = StringHelper.qualify( getRootAlias(), getIdentifierColumnNames() );
 1120   
 1121   		// Here we render the select column list based on the properties defined as being generated.
 1122   		// For partial component generation, we currently just re-select the whole component
 1123   		// rather than trying to handle the individual generated portions.
 1124   		String selectClause = concretePropertySelectFragment( getRootAlias(), inclusions );
 1125   		selectClause = selectClause.substring( 2 );
 1126   
 1127   		String fromClause = fromTableFragment( getRootAlias() ) +
 1128   				fromJoinFragment( getRootAlias(), true, false );
 1129   
 1130   		String whereClause = new StringBuffer()
 1131   			.append( StringHelper.join( "=? and ", aliasedIdColumns ) )
 1132   			.append( "=?" )
 1133   			.append( whereJoinFragment( getRootAlias(), true, false ) )
 1134   			.toString();
 1135   
 1136   		return select.setSelectClause( selectClause )
 1137   				.setFromClause( fromClause )
 1138   				.setOuterJoins( "", "" )
 1139   				.setWhereClause( whereClause )
 1140   				.toStatementString();
 1141   	}
 1142   
 1143   	protected static interface InclusionChecker {
 1144   		public boolean includeProperty(int propertyNumber);
 1145   	}
 1146   
 1147   	protected String concretePropertySelectFragment(String alias, final ValueInclusion[] inclusions) {
 1148   		return concretePropertySelectFragment(
 1149   				alias,
 1150   				new InclusionChecker() {
 1151   					// TODO : currently we really do not handle ValueInclusion.PARTIAL...
 1152   					// ValueInclusion.PARTIAL would indicate parts of a component need to
 1153   					// be included in the select; currently we then just render the entire
 1154   					// component into the select clause in that case.
 1155   					public boolean includeProperty(int propertyNumber) {
 1156   						return inclusions[propertyNumber] != ValueInclusion.NONE;
 1157   					}
 1158   				}
 1159   		);
 1160   	}
 1161   
 1162   	protected String concretePropertySelectFragment(String alias, final boolean[] includeProperty) {
 1163   		return concretePropertySelectFragment(
 1164   				alias,
 1165   				new InclusionChecker() {
 1166   					public boolean includeProperty(int propertyNumber) {
 1167   						return includeProperty[propertyNumber];
 1168   					}
 1169   				}
 1170   		);
 1171   	}
 1172   
 1173   	protected String concretePropertySelectFragment(String alias, InclusionChecker inclusionChecker) {
 1174   		int propertyCount = getPropertyNames().length;
 1175   		int[] propertyTableNumbers = getPropertyTableNumbersInSelect();
 1176   		SelectFragment frag = new SelectFragment();
 1177   		for ( int i = 0; i < propertyCount; i++ ) {
 1178   			if ( inclusionChecker.includeProperty( i ) ) {
 1179   				frag.addColumns(
 1180   						generateTableAlias( alias, propertyTableNumbers[i] ),
 1181   						propertyColumnNames[i],
 1182   						propertyColumnAliases[i]
 1183   				);
 1184   				frag.addFormulas(
 1185   						generateTableAlias( alias, propertyTableNumbers[i] ),
 1186   						propertyColumnFormulaTemplates[i],
 1187   						propertyColumnAliases[i]
 1188   				);
 1189   			}
 1190   		}
 1191   		return frag.toFragmentString();
 1192   	}
 1193   
 1194   	protected String generateSnapshotSelectString() {
 1195   
 1196   		//TODO: should we use SELECT .. FOR UPDATE?
 1197   
 1198   		Select select = new Select( getFactory().getDialect() );
 1199   
 1200   		if ( getFactory().getSettings().isCommentsEnabled() ) {
 1201   			select.setComment( "get current state " + getEntityName() );
 1202   		}
 1203   
 1204   		String[] aliasedIdColumns = StringHelper.qualify( getRootAlias(), getIdentifierColumnNames() );
 1205   		String selectClause = StringHelper.join( ", ", aliasedIdColumns ) +
 1206   				concretePropertySelectFragment( getRootAlias(), getPropertyUpdateability() );
 1207   
 1208   		String fromClause = fromTableFragment( getRootAlias() ) +
 1209   				fromJoinFragment( getRootAlias(), true, false );
 1210   
 1211   		String whereClause = new StringBuffer()
 1212   			.append( StringHelper.join( "=? and ",
 1213   					aliasedIdColumns ) )
 1214   			.append( "=?" )
 1215   			.append( whereJoinFragment( getRootAlias(), true, false ) )
 1216   			.toString();
 1217   
 1218   		/*if ( isVersioned() ) {
 1219   			where.append(" and ")
 1220   				.append( getVersionColumnName() )
 1221   				.append("=?");
 1222   		}*/
 1223   
 1224   		return select.setSelectClause( selectClause )
 1225   				.setFromClause( fromClause )
 1226   				.setOuterJoins( "", "" )
 1227   				.setWhereClause( whereClause )
 1228   				.toStatementString();
 1229   	}
 1230   
 1231   	public Object forceVersionIncrement(Serializable id, Object currentVersion, SessionImplementor session) {
 1232   		if ( !isVersioned() ) {
 1233   			throw new AssertionFailure( "cannot force version increment on non-versioned entity" );
 1234   		}
 1235   
 1236   		if ( isVersionPropertyGenerated() ) {
 1237   			// the difficulty here is exactly what do we update in order to
 1238   			// force the version to be incremented in the db...
 1239   			throw new HibernateException( "LockMode.FORCE is currently not supported for generated version properties" );
 1240   		}
 1241   
 1242   		Object nextVersion = getVersionType().next( currentVersion, session );
 1243   		if ( log.isTraceEnabled() ) {
 1244   			log.trace(
 1245   					"Forcing version increment [" + MessageHelper.infoString( this, id, getFactory() ) +
 1246   					"; " + getVersionType().toLoggableString( currentVersion, getFactory() ) +
 1247   					" -> " + getVersionType().toLoggableString( nextVersion, getFactory() ) + "]"
 1248   			);
 1249   		}
 1250   
 1251   		// todo : cache this sql...
 1252   		String versionIncrementString = generateVersionIncrementUpdateString();
 1253   		PreparedStatement st = null;
 1254   		try {
 1255   			try {
 1256   				st = session.getBatcher().prepareStatement( versionIncrementString );
 1257   				getVersionType().nullSafeSet( st, nextVersion, 1, session );
 1258   				getIdentifierType().nullSafeSet( st, id, 2, session );
 1259   				getVersionType().nullSafeSet( st, currentVersion, 2 + getIdentifierColumnSpan(), session );
 1260   				int rows = st.executeUpdate();
 1261   				if ( rows != 1 ) {
 1262   					throw new StaleObjectStateException( getEntityName(), id );
 1263   				}
 1264   			}
 1265   			finally {
 1266   				session.getBatcher().closeStatement( st );
 1267   			}
 1268   		}
 1269   		catch ( SQLException sqle ) {
 1270   			throw JDBCExceptionHelper.convert(
 1271   					getFactory().getSQLExceptionConverter(),
 1272   					sqle,
 1273   					"could not retrieve version: " +
 1274   					MessageHelper.infoString( this, id, getFactory() ),
 1275   					getVersionSelectString()
 1276   				);
 1277   		}
 1278   
 1279   		return nextVersion;
 1280   	}
 1281   
 1282   	private String generateVersionIncrementUpdateString() {
 1283   		Update update = new Update( getFactory().getDialect() );
 1284   		update.setTableName( getTableName( 0 ) );
 1285   		if ( getFactory().getSettings().isCommentsEnabled() ) {
 1286   			update.setComment( "forced version increment" );
 1287   		}
 1288   		update.addColumn( getVersionColumnName() );
 1289   		update.setPrimaryKeyColumnNames( getIdentifierColumnNames() );
 1290   		update.setVersionColumnName( getVersionColumnName() );
 1291   		return update.toStatementString();
 1292   	}
 1293   
 1294   	/**
 1295   	 * Retrieve the version number
 1296   	 */
 1297   	public Object getCurrentVersion(Serializable id, SessionImplementor session) throws HibernateException {
 1298   
 1299   		if ( log.isTraceEnabled() ) {
 1300   			log.trace( "Getting version: " + MessageHelper.infoString( this, id, getFactory() ) );
 1301   		}
 1302   
 1303   		try {
 1304   
 1305   			PreparedStatement st = session.getBatcher().prepareSelectStatement( getVersionSelectString() );
 1306   			try {
 1307   				getIdentifierType().nullSafeSet( st, id, 1, session );
 1308   
 1309   				ResultSet rs = st.executeQuery();
 1310   				try {
 1311   					if ( !rs.next() ) {
 1312   						return null;
 1313   					}
 1314   					if ( !isVersioned() ) {
 1315   						return this;
 1316   					}
 1317   					return getVersionType().nullSafeGet( rs, getVersionColumnName(), session, null );
 1318   				}
 1319   				finally {
 1320   					rs.close();
 1321   				}
 1322   			}
 1323   			finally {
 1324   				session.getBatcher().closeStatement( st );
 1325   			}
 1326   
 1327   		}
 1328   		catch ( SQLException sqle ) {
 1329   			throw JDBCExceptionHelper.convert(
 1330   					getFactory().getSQLExceptionConverter(),
 1331   					sqle,
 1332   					"could not retrieve version: " +
 1333   					MessageHelper.infoString( this, id, getFactory() ),
 1334   					getVersionSelectString()
 1335   				);
 1336   		}
 1337   
 1338   	}
 1339   
 1340   	protected void initLockers() {
 1341   		lockers.put( LockMode.READ, generateLocker( LockMode.READ ) );
 1342   		lockers.put( LockMode.UPGRADE, generateLocker( LockMode.UPGRADE ) );
 1343   		lockers.put( LockMode.UPGRADE_NOWAIT, generateLocker( LockMode.UPGRADE_NOWAIT ) );
 1344   		lockers.put( LockMode.FORCE, generateLocker( LockMode.FORCE ) );
 1345   	}
 1346   
 1347   	protected LockingStrategy generateLocker(LockMode lockMode) {
 1348   		return factory.getDialect().getLockingStrategy( this, lockMode );
 1349   	}
 1350   
 1351   	private LockingStrategy getLocker(LockMode lockMode) {
 1352   		return ( LockingStrategy ) lockers.get( lockMode );
 1353   	}
 1354   
 1355   	public void lock(
 1356   			Serializable id,
 1357   	        Object version,
 1358   	        Object object,
 1359   	        LockMode lockMode,
 1360   	        SessionImplementor session) throws HibernateException {
 1361   		getLocker( lockMode ).lock( id, version, object, session );
 1362   	}
 1363   
 1364   	public String getRootTableName() {
 1365   		return getSubclassTableName( 0 );
 1366   	}
 1367   
 1368   	public String getRootTableAlias(String drivingAlias) {
 1369   		return drivingAlias;
 1370   	}
 1371   
 1372   	public String[] getRootTableIdentifierColumnNames() {
 1373   		return getRootTableKeyColumnNames();
 1374   	}
 1375   
 1376   	public String[] toColumns(String alias, String propertyName) throws QueryException {
 1377   		return propertyMapping.toColumns( alias, propertyName );
 1378   	}
 1379   
 1380   	public String[] toColumns(String propertyName) throws QueryException {
 1381   		return propertyMapping.getColumnNames( propertyName );
 1382   	}
 1383   
 1384   	public Type toType(String propertyName) throws QueryException {
 1385   		return propertyMapping.toType( propertyName );
 1386   	}
 1387   
 1388   	public String[] getPropertyColumnNames(String propertyName) {
 1389   		return propertyMapping.getColumnNames( propertyName );
 1390   	}
 1391   
 1392   	/**
 1393   	 * Warning:
 1394   	 * When there are duplicated property names in the subclasses
 1395   	 * of the class, this method may return the wrong table
 1396   	 * number for the duplicated subclass property (note that
 1397   	 * SingleTableEntityPersister defines an overloaded form
 1398   	 * which takes the entity name.
 1399   	 */
 1400   	public int getSubclassPropertyTableNumber(String propertyPath) {
 1401   		String rootPropertyName = StringHelper.root(propertyPath);
 1402   		Type type = propertyMapping.toType(rootPropertyName);
 1403   		if ( type.isAssociationType() ) {
 1404   			AssociationType assocType = ( AssociationType ) type;
 1405   			if ( assocType.useLHSPrimaryKey() ) {
 1406   				// performance op to avoid the array search
 1407   				return 0;
 1408   			}
 1409   			else if ( type.isCollectionType() ) {
 1410   				// properly handle property-ref-based associations
 1411   				rootPropertyName = assocType.getLHSPropertyName();
 1412   			}
 1413   		}
 1414   		//Enable for HHH-440, which we don't like:
 1415   		/*if ( type.isComponentType() && !propertyName.equals(rootPropertyName) ) {
 1416   			String unrooted = StringHelper.unroot(propertyName);
 1417   			int idx = ArrayHelper.indexOf( getSubclassColumnClosure(), unrooted );
 1418   			if ( idx != -1 ) {
 1419   				return getSubclassColumnTableNumberClosure()[idx];
 1420   			}
 1421   		}*/
 1422   		int index = ArrayHelper.indexOf( getSubclassPropertyNameClosure(), rootPropertyName); //TODO: optimize this better!
 1423   		return index==-1 ? 0 : getSubclassPropertyTableNumber(index);
 1424   	}
 1425   
 1426   	public Declarer getSubclassPropertyDeclarer(String propertyPath) {
 1427   		int tableIndex = getSubclassPropertyTableNumber( propertyPath );
 1428   		if ( tableIndex == 0 ) {
 1429   			return Declarer.CLASS;
 1430   		}
 1431   		else if ( isClassOrSuperclassTable( tableIndex ) ) {
 1432   			return Declarer.SUPERCLASS;
 1433   		}
 1434   		else {
 1435   			return Declarer.SUBCLASS;
 1436   		}
 1437   	}
 1438   
 1439   	protected String generateTableAlias(String rootAlias, int tableNumber) {
 1440   		if ( tableNumber == 0 ) {
 1441   			return rootAlias;
 1442   		}
 1443   		StringBuffer buf = new StringBuffer().append( rootAlias );
 1444   		if ( !rootAlias.endsWith( "_" ) ) {
 1445   			buf.append( '_' );
 1446   		}
 1447   		return buf.append( tableNumber ).append( '_' ).toString();
 1448   	}
 1449   
 1450   	public String[] toColumns(String name, final int i) {
 1451   		final String alias = generateTableAlias( name, getSubclassPropertyTableNumber( i ) );
 1452   		String[] cols = getSubclassPropertyColumnNames( i );
 1453   		String[] templates = getSubclassPropertyFormulaTemplateClosure()[i];
 1454   		String[] result = new String[cols.length];
 1455   		for ( int j = 0; j < cols.length; j++ ) {
 1456   			if ( cols[j] == null ) {
 1457   				result[j] = StringHelper.replace( templates[j], Template.TEMPLATE, alias );
 1458   			}
 1459   			else {
 1460   				result[j] = StringHelper.qualify( alias, cols[j] );
 1461   			}
 1462   		}
 1463   		return result;
 1464   	}
 1465   
 1466   	private int getSubclassPropertyIndex(String propertyName) {
 1467   		return ArrayHelper.indexOf(subclassPropertyNameClosure, propertyName);
 1468   	}
 1469   
 1470   	protected String[] getPropertySubclassNames() {
 1471   		return propertySubclassNames;
 1472   	}
 1473   
 1474   	public String[] getPropertyColumnNames(int i) {
 1475   		return propertyColumnNames[i];
 1476   	}
 1477   
 1478   	protected int getPropertyColumnSpan(int i) {
 1479   		return propertyColumnSpans[i];
 1480   	}
 1481   
 1482   	protected boolean hasFormulaProperties() {
 1483   		return hasFormulaProperties;
 1484   	}
 1485   
 1486   	public FetchMode getFetchMode(int i) {
 1487   		return subclassPropertyFetchModeClosure[i];
 1488   	}
 1489   
 1490   	public CascadeStyle getCascadeStyle(int i) {
 1491   		return subclassPropertyCascadeStyleClosure[i];
 1492   	}
 1493   
 1494   	public Type getSubclassPropertyType(int i) {
 1495   		return subclassPropertyTypeClosure[i];
 1496   	}
 1497   
 1498   	public String getSubclassPropertyName(int i) {
 1499   		return subclassPropertyNameClosure[i];
 1500   	}
 1501   
 1502   	public int countSubclassProperties() {
 1503   		return subclassPropertyTypeClosure.length;
 1504   	}
 1505   
 1506   	public String[] getSubclassPropertyColumnNames(int i) {
 1507   		return subclassPropertyColumnNameClosure[i];
 1508   	}
 1509   
 1510   	public boolean isDefinedOnSubclass(int i) {
 1511   		return propertyDefinedOnSubclass[i];
 1512   	}
 1513   
 1514   	protected String[][] getSubclassPropertyFormulaTemplateClosure() {
 1515   		return subclassPropertyFormulaTemplateClosure;
 1516   	}
 1517   
 1518   	protected Type[] getSubclassPropertyTypeClosure() {
 1519   		return subclassPropertyTypeClosure;
 1520   	}
 1521   
 1522   	protected String[][] getSubclassPropertyColumnNameClosure() {
 1523   		return subclassPropertyColumnNameClosure;
 1524   	}
 1525   
 1526   	protected String[] getSubclassPropertyNameClosure() {
 1527   		return subclassPropertyNameClosure;
 1528   	}
 1529   
 1530   	protected String[] getSubclassPropertySubclassNameClosure() {
 1531   		return subclassPropertySubclassNameClosure;
 1532   	}
 1533   
 1534   	protected String[] getSubclassColumnClosure() {
 1535   		return subclassColumnClosure;
 1536   	}
 1537   
 1538   	protected String[] getSubclassColumnAliasClosure() {
 1539   		return subclassColumnAliasClosure;
 1540   	}
 1541   
 1542   	protected String[] getSubclassFormulaClosure() {
 1543   		return subclassFormulaClosure;
 1544   	}
 1545   
 1546   	protected String[] getSubclassFormulaTemplateClosure() {
 1547   		return subclassFormulaTemplateClosure;
 1548   	}
 1549   
 1550   	protected String[] getSubclassFormulaAliasClosure() {
 1551   		return subclassFormulaAliasClosure;
 1552   	}
 1553   
 1554   	public String[] getSubclassPropertyColumnAliases(String propertyName, String suffix) {
 1555   		String rawAliases[] = ( String[] ) subclassPropertyAliases.get( propertyName );
 1556   
 1557   		if ( rawAliases == null ) {
 1558   			return null;
 1559   		}
 1560   
 1561   		String result[] = new String[rawAliases.length];
 1562   		for ( int i = 0; i < rawAliases.length; i++ ) {
 1563   			result[i] = new Alias( suffix ).toUnquotedAliasString( rawAliases[i] );
 1564   		}
 1565   		return result;
 1566   	}
 1567   
 1568   	public String[] getSubclassPropertyColumnNames(String propertyName) {
 1569   		//TODO: should we allow suffixes on these ?
 1570   		return ( String[] ) subclassPropertyColumnNames.get( propertyName );
 1571   	}
 1572   
 1573   
 1574   
 1575   	//This is really ugly, but necessary:
 1576   	/**
 1577   	 * Must be called by subclasses, at the end of their constructors
 1578   	 */
 1579   	protected void initSubclassPropertyAliasesMap(PersistentClass model) throws MappingException {
 1580   
 1581   		// ALIASES
 1582   		internalInitSubclassPropertyAliasesMap( null, model.getSubclassPropertyClosureIterator() );
 1583   
 1584   		// aliases for identifier ( alias.id ); skip if the entity defines a non-id property named 'id'
 1585   		if ( ! entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
 1586   			subclassPropertyAliases.put( ENTITY_ID, getIdentifierAliases() );
 1587   			subclassPropertyColumnNames.put( ENTITY_ID, getIdentifierColumnNames() );
 1588   		}
 1589   
 1590   		// aliases named identifier ( alias.idname )
 1591   		if ( hasIdentifierProperty() ) {
 1592   			subclassPropertyAliases.put( getIdentifierPropertyName(), getIdentifierAliases() );
 1593   			subclassPropertyColumnNames.put( getIdentifierPropertyName(), getIdentifierColumnNames() );
 1594   		}
 1595   
 1596   		// aliases for composite-id's
 1597   		if ( getIdentifierType().isComponentType() ) {
 1598   			// Fetch embedded identifiers propertynames from the "virtual" identifier component
 1599   			AbstractComponentType componentId = ( AbstractComponentType ) getIdentifierType();
 1600   			String[] idPropertyNames = componentId.getPropertyNames();
 1601   			String[] idAliases = getIdentifierAliases();
 1602   			String[] idColumnNames = getIdentifierColumnNames();
 1603   
 1604   			for ( int i = 0; i < idPropertyNames.length; i++ ) {
 1605   				if ( entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
 1606   					subclassPropertyAliases.put(
 1607   							ENTITY_ID + "." + idPropertyNames[i],
 1608   							new String[] { idAliases[i] }
 1609   					);
 1610   					subclassPropertyColumnNames.put(
 1611   							ENTITY_ID + "." + getIdentifierPropertyName() + "." + idPropertyNames[i],
 1612   							new String[] { idColumnNames[i] }
 1613   					);
 1614   				}
 1615   //				if (hasIdentifierProperty() && !ENTITY_ID.equals( getIdentifierPropertyName() ) ) {
 1616   				if ( hasIdentifierProperty() ) {
 1617   					subclassPropertyAliases.put(
 1618   							getIdentifierPropertyName() + "." + idPropertyNames[i],
 1619   							new String[] { idAliases[i] }
 1620   					);
 1621   					subclassPropertyColumnNames.put(
 1622   							getIdentifierPropertyName() + "." + idPropertyNames[i],
 1623   							new String[] { idColumnNames[i] }
 1624   					);
 1625   				}
 1626   				else {
 1627   					// embedded composite ids ( alias.idname1, alias.idname2 )
 1628   					subclassPropertyAliases.put( idPropertyNames[i], new String[] { idAliases[i] } );
 1629   					subclassPropertyColumnNames.put( idPropertyNames[i],  new String[] { idColumnNames[i] } );
 1630   				}
 1631   			}
 1632   		}
 1633   
 1634   		if ( entityMetamodel.isPolymorphic() ) {
 1635   			subclassPropertyAliases.put( ENTITY_CLASS, new String[] { getDiscriminatorAlias() } );
 1636   			subclassPropertyColumnNames.put( ENTITY_CLASS, new String[] { getDiscriminatorColumnName() } );
 1637   		}
 1638   
 1639   	}
 1640   
 1641   	private void internalInitSubclassPropertyAliasesMap(String path, Iterator propertyIterator) {
 1642   		while ( propertyIterator.hasNext() ) {
 1643   
 1644   			Property prop = ( Property ) propertyIterator.next();
 1645   			String propname = path == null ? prop.getName() : path + "." + prop.getName();
 1646   			if ( prop.isComposite() ) {
 1647   				Component component = ( Component ) prop.getValue();
 1648   				Iterator compProps = component.getPropertyIterator();
 1649   				internalInitSubclassPropertyAliasesMap( propname, compProps );
 1650   			}
 1651   			else {
 1652   				String[] aliases = new String[prop.getColumnSpan()];
 1653   				String[] cols = new String[prop.getColumnSpan()];
 1654   				Iterator colIter = prop.getColumnIterator();
 1655   				int l = 0;
 1656   				while ( colIter.hasNext() ) {
 1657   					Selectable thing = ( Selectable ) colIter.next();
 1658   					aliases[l] = thing.getAlias( getFactory().getDialect(), prop.getValue().getTable() );
 1659   					cols[l] = thing.getText( getFactory().getDialect() ); // TODO: skip formulas?
 1660   					l++;
 1661   				}
 1662   
 1663   				subclassPropertyAliases.put( propname, aliases );
 1664   				subclassPropertyColumnNames.put( propname, cols );
 1665   			}
 1666   		}
 1667   
 1668   	}
 1669   
 1670   	public Object loadByUniqueKey(String propertyName, Object uniqueKey, SessionImplementor session)
 1671   			throws HibernateException {
 1672   		return getAppropriateUniqueKeyLoader( propertyName, session.getEnabledFilters() )
 1673   				.loadByUniqueKey( session, uniqueKey );
 1674   	}
 1675   
 1676   	private EntityLoader getAppropriateUniqueKeyLoader(String propertyName, Map enabledFilters) {
 1677   
 1678   		final boolean useStaticLoader = ( enabledFilters == null || enabledFilters.isEmpty() )
 1679   				&& propertyName.indexOf('.')<0; //ugly little workaround for fact that createUniqueKeyLoaders() does not handle component properties
 1680   
 1681   		if ( useStaticLoader ) {
 1682   			return (EntityLoader) uniqueKeyLoaders.get( propertyName );
 1683   		}
 1684   		else {
 1685   			return createUniqueKeyLoader(
 1686   					propertyMapping.toType(propertyName),
 1687   					propertyMapping.toColumns(propertyName),
 1688   					enabledFilters
 1689   				);
 1690   		}
 1691   	}
 1692   
 1693   	public int getPropertyIndex(String propertyName) {
 1694   		return entityMetamodel.getPropertyIndex(propertyName);
 1695   	}
 1696   
 1697   	protected void createUniqueKeyLoaders() throws MappingException {
 1698   		Type[] propertyTypes = getPropertyTypes();
 1699   		String[] propertyNames = getPropertyNames();
 1700   		for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
 1701   			if ( propertyUniqueness[i] ) {
 1702   				//don't need filters for the static loaders
 1703   				uniqueKeyLoaders.put(
 1704   						propertyNames[i],
 1705   						createUniqueKeyLoader(
 1706   								propertyTypes[i],
 1707   								getPropertyColumnNames( i ),
 1708   								CollectionHelper.EMPTY_MAP
 1709   							)
 1710   					);
 1711   				//TODO: create uk loaders for component properties
 1712   			}
 1713   		}
 1714   	}
 1715   
 1716   	private EntityLoader createUniqueKeyLoader(Type uniqueKeyType, String[] columns, Map enabledFilters) {
 1717   		if ( uniqueKeyType.isEntityType() ) {
 1718   			String className = ( ( EntityType ) uniqueKeyType ).getAssociatedEntityName();
 1719   			uniqueKeyType = getFactory().getEntityPersister( className ).getIdentifierType();
 1720   		}
 1721   
 1722   		return new EntityLoader( this, columns, uniqueKeyType, 1, LockMode.NONE, getFactory(), enabledFilters );
 1723   	}
 1724   
 1725   	protected String getSQLWhereString(String alias) {
 1726   		return StringHelper.replace( sqlWhereStringTemplate, Template.TEMPLATE, alias );
 1727   	}
 1728   
 1729   	protected boolean hasWhere() {
 1730   		return sqlWhereString != null;
 1731   	}
 1732   
 1733   	private void initOrdinaryPropertyPaths(Mapping mapping) throws MappingException {
 1734   		for ( int i = 0; i < getSubclassPropertyNameClosure().length; i++ ) {
 1735   			propertyMapping.initPropertyPaths( getSubclassPropertyNameClosure()[i],
 1736   					getSubclassPropertyTypeClosure()[i],
 1737   					getSubclassPropertyColumnNameClosure()[i],
 1738   					getSubclassPropertyFormulaTemplateClosure()[i],
 1739   					mapping );
 1740   		}
 1741   	}
 1742   
 1743   	private void initIdentifierPropertyPaths(Mapping mapping) throws MappingException {
 1744   		String idProp = getIdentifierPropertyName();
 1745   		if ( idProp != null ) {
 1746   			propertyMapping.initPropertyPaths( idProp, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
 1747   		}
 1748   		if ( entityMetamodel.getIdentifierProperty().isEmbedded() ) {
 1749   			propertyMapping.initPropertyPaths( null, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
 1750   		}
 1751   		if ( ! entityMetamodel.hasNonIdentifierPropertyNamedId() ) {
 1752   			propertyMapping.initPropertyPaths( ENTITY_ID, getIdentifierType(), getIdentifierColumnNames(), null, mapping );
 1753   		}
 1754   	}
 1755   
 1756   	private void initDiscriminatorPropertyPath(Mapping mapping) throws MappingException {
 1757   		propertyMapping.initPropertyPaths( ENTITY_CLASS,
 1758   				getDiscriminatorType(),
 1759   				new String[]{getDiscriminatorColumnName()},
 1760   				new String[]{getDiscriminatorFormulaTemplate()},
 1761   				getFactory() );
 1762   	}
 1763   
 1764   	protected void initPropertyPaths(Mapping mapping) throws MappingException {
 1765   		initOrdinaryPropertyPaths(mapping);
 1766   		initOrdinaryPropertyPaths(mapping); //do two passes, for collection property-ref!
 1767   		initIdentifierPropertyPaths(mapping);
 1768   		if ( entityMetamodel.isPolymorphic() ) {
 1769   			initDiscriminatorPropertyPath( mapping );
 1770   		}
 1771   	}
 1772   
 1773   	protected UniqueEntityLoader createEntityLoader(LockMode lockMode, Map enabledFilters) throws MappingException {
 1774   		//TODO: disable batch loading if lockMode > READ?
 1775   		return BatchingEntityLoader.createBatchingEntityLoader( this, batchSize, lockMode, getFactory(), enabledFilters );
 1776   	}
 1777   
 1778   	protected UniqueEntityLoader createEntityLoader(LockMode lockMode) throws MappingException {
 1779   		return createEntityLoader( lockMode, CollectionHelper.EMPTY_MAP );
 1780   	}
 1781   
 1782   	protected boolean check(int rows, Serializable id, int tableNumber, Expectation expectation, PreparedStatement statement) throws HibernateException {
 1783   		try {
 1784   			expectation.verifyOutcome( rows, statement, -1 );
 1785   		}
 1786   		catch( StaleStateException e ) {
 1787   			if ( !isNullableTable( tableNumber ) ) {
 1788   				if ( getFactory().getStatistics().isStatisticsEnabled() ) {
 1789   					getFactory().getStatisticsImplementor()
 1790   							.optimisticFailure( getEntityName() );
 1791   				}
 1792   				throw new StaleObjectStateException( getEntityName(), id );
 1793   			}
 1794   			return false;
 1795   		}
 1796   		catch( TooManyRowsAffectedException e ) {
 1797   			throw new HibernateException(
 1798   					"Duplicate identifier in table for: " +
 1799   					MessageHelper.infoString( this, id, getFactory() )
 1800   			);
 1801   		}
 1802   		catch ( Throwable t ) {
 1803   			return false;
 1804   		}
 1805   		return true;
 1806   	}
 1807   
 1808   	protected String generateUpdateString(boolean[] includeProperty, int j, boolean useRowId) {
 1809   		return generateUpdateString( includeProperty, j, null, useRowId );
 1810   	}
 1811   
 1812   	/**
 1813   	 * Generate the SQL that updates a row by id (and version)
 1814   	 */
 1815   	protected String generateUpdateString(final boolean[] includeProperty,
 1816   										  final int j,
 1817   										  final Object[] oldFields,
 1818   										  final boolean useRowId) {
 1819   
 1820   		Update update = new Update( getFactory().getDialect() ).setTableName( getTableName( j ) );
 1821   
 1822   		// select the correct row by either pk or rowid
 1823   		if ( useRowId ) {
 1824   			update.setPrimaryKeyColumnNames( new String[]{rowIdName} ); //TODO: eventually, rowIdName[j]
 1825   		}
 1826   		else {
 1827   			update.setPrimaryKeyColumnNames( getKeyColumns( j ) );
 1828   		}
 1829   
 1830   		boolean hasColumns = false;
 1831   		for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
 1832   			if ( includeProperty[i] && isPropertyOfTable( i, j ) ) {
 1833   				// this is a property of the table, which we are updating
 1834   				update.addColumns( getPropertyColumnNames(i), propertyColumnUpdateable[i] );
 1835   				hasColumns = hasColumns || getPropertyColumnSpan( i ) > 0;
 1836   			}
 1837   		}
 1838   
 1839   		if ( j == 0 && isVersioned() && entityMetamodel.getOptimisticLockMode() == Versioning.OPTIMISTIC_LOCK_VERSION ) {
 1840   			// this is the root (versioned) table, and we are using version-based
 1841   			// optimistic locking;  if we are not updating the version, also don't
 1842   			// check it (unless this is a "generated" version column)!
 1843   			if ( checkVersion( includeProperty ) ) {
 1844   				update.setVersionColumnName( getVersionColumnName() );
 1845   				hasColumns = true;
 1846   			}
 1847   		}
 1848   		else if ( entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION && oldFields != null ) {
 1849   			// we are using "all" or "dirty" property-based optimistic locking
 1850   
 1851   			boolean[] includeInWhere = entityMetamodel.getOptimisticLockMode() == Versioning.OPTIMISTIC_LOCK_ALL ?
 1852   					getPropertyUpdateability() : //optimistic-lock="all", include all updatable properties
 1853   					includeProperty; //optimistic-lock="dirty", include all properties we are updating this time
 1854   
 1855   			boolean[] versionability = getPropertyVersionability();
 1856   			Type[] types = getPropertyTypes();
 1857   			for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
 1858   				boolean include = includeInWhere[i] &&
 1859   						isPropertyOfTable( i, j ) &&
 1860   						versionability[i];
 1861   				if ( include ) {
 1862   					// this property belongs to the table, and it is not specifically
 1863   					// excluded from optimistic locking by optimistic-lock="false"
 1864   					String[] propertyColumnNames = getPropertyColumnNames( i );
 1865   					boolean[] propertyNullness = types[i].toColumnNullness( oldFields[i], getFactory() );
 1866   					for ( int k=0; k<propertyNullness.length; k++ ) {
 1867   						if ( propertyNullness[k] ) {
 1868   							update.addWhereColumn( propertyColumnNames[k] );
 1869   						}
 1870   						else {
 1871   							update.addWhereColumn( propertyColumnNames[k], " is null" );
 1872   						}
 1873   					}
 1874   				}
 1875   			}
 1876   
 1877   		}
 1878   
 1879   		if ( getFactory().getSettings().isCommentsEnabled() ) {
 1880   			update.setComment( "update " + getEntityName() );
 1881   		}
 1882   
 1883   		return hasColumns ? update.toStatementString() : null;
 1884   	}
 1885   
 1886   	private boolean checkVersion(final boolean[] includeProperty) {
 1887           return includeProperty[ getVersionProperty() ] ||
 1888   				entityMetamodel.getPropertyUpdateGenerationInclusions()[ getVersionProperty() ] != ValueInclusion.NONE;
 1889   	}
 1890   
 1891   	protected String generateInsertString(boolean[] includeProperty, int j) {
 1892   		return generateInsertString( false, includeProperty, j );
 1893   	}
 1894   
 1895   	protected String generateInsertString(boolean identityInsert, boolean[] includeProperty) {
 1896   		return generateInsertString( identityInsert, includeProperty, 0 );
 1897   	}
 1898   
 1899   	/**
 1900   	 * Generate the SQL that inserts a row
 1901   	 */
 1902   	protected String generateInsertString(boolean identityInsert, boolean[] includeProperty, int j) {
 1903   
 1904   		// todo : remove the identityInsert param and variations;
 1905   		//   identity-insert strings are now generated from generateIdentityInsertString()
 1906   
 1907   		Insert insert = new Insert( getFactory().getDialect() )
 1908   				.setTableName( getTableName( j ) );
 1909   
 1910   		// add normal properties
 1911   		for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
 1912   			if ( includeProperty[i] && isPropertyOfTable( i, j ) ) {
 1913   				// this property belongs on the table and is to be inserted
 1914   				insert.addColumns( getPropertyColumnNames(i), propertyColumnInsertable[i] );
 1915   			}
 1916   		}
 1917   
 1918   		// add the discriminator
 1919   		if ( j == 0 ) {
 1920   			addDiscriminatorToInsert( insert );
 1921   		}
 1922   
 1923   		// add the primary key
 1924   		if ( j == 0 && identityInsert ) {
 1925   			insert.addIdentityColumn( getKeyColumns( 0 )[0] );
 1926   		}
 1927   		else {
 1928   			insert.addColumns( getKeyColumns( j ) );
 1929   		}
 1930   
 1931   		if ( getFactory().getSettings().isCommentsEnabled() ) {
 1932   			insert.setComment( "insert " + getEntityName() );
 1933   		}
 1934   
 1935   		String result = insert.toStatementString();
 1936   
 1937   		// append the SQL to return the generated identifier
 1938   		if ( j == 0 && identityInsert && useInsertSelectIdentity() ) { //TODO: suck into Insert
 1939   			result = getFactory().getDialect().appendIdentitySelectToInsert( result );
 1940   		}
 1941   
 1942   		return result;
 1943   	}
 1944   
 1945   	/**
 1946   	 * Used to generate an insery statement against the root table in the
 1947   	 * case of identifier generation strategies where the insert statement
 1948   	 * executions actually generates the identifier value.
 1949   	 *
 1950   	 * @param includeProperty indices of the properties to include in the
 1951   	 * insert statement.
 1952   	 * @return The insert SQL statement string
 1953   	 */
 1954   	protected String generateIdentityInsertString(boolean[] includeProperty) {
 1955   		Insert insert = identityDelegate.prepareIdentifierGeneratingInsert();
 1956   		insert.setTableName( getTableName( 0 ) );
 1957   
 1958   		// add normal properties
 1959   		for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
 1960   			if ( includeProperty[i] && isPropertyOfTable( i, 0 ) ) {
 1961   				// this property belongs on the table and is to be inserted
 1962   				insert.addColumns( getPropertyColumnNames(i), propertyColumnInsertable[i] );
 1963   			}
 1964   		}
 1965   
 1966   		// add the discriminator
 1967   		addDiscriminatorToInsert( insert );
 1968   
 1969   		// delegate already handles PK columns
 1970   
 1971   		if ( getFactory().getSettings().isCommentsEnabled() ) {
 1972   			insert.setComment( "insert " + getEntityName() );
 1973   		}
 1974   
 1975   		return insert.toStatementString();
 1976   	}
 1977   
 1978   	/**
 1979   	 * Generate the SQL that deletes a row by id (and version)
 1980   	 */
 1981   	protected String generateDeleteString(int j) {
 1982   		Delete delete = new Delete()
 1983   				.setTableName( getTableName( j ) )
 1984   				.setPrimaryKeyColumnNames( getKeyColumns( j ) );
 1985   		if ( j == 0 ) {
 1986   			delete.setVersionColumnName( getVersionColumnName() );
 1987   		}
 1988   		if ( getFactory().getSettings().isCommentsEnabled() ) {
 1989   			delete.setComment( "delete " + getEntityName() );
 1990   		}
 1991   		return delete.toStatementString();
 1992   	}
 1993   
 1994   	protected int dehydrate(
 1995   			Serializable id,
 1996   			Object[] fields,
 1997   			boolean[] includeProperty,
 1998   			boolean[][] includeColumns,
 1999   			int j,
 2000   			PreparedStatement st,
 2001   			SessionImplementor session) throws HibernateException, SQLException {
 2002   		return dehydrate( id, fields, null, includeProperty, includeColumns, j, st, session, 1 );
 2003   	}
 2004   
 2005   	/**
 2006   	 * Marshall the fields of a persistent instance to a prepared statement
 2007   	 */
 2008   	protected int dehydrate(
 2009   			final Serializable id,
 2010   	        final Object[] fields,
 2011   	        final Object rowId,
 2012   	        final boolean[] includeProperty,
 2013   	        final boolean[][] includeColumns,
 2014   	        final int j,
 2015   	        final PreparedStatement ps,
 2016   	        final SessionImplementor session,
 2017   	        int index) throws SQLException, HibernateException {
 2018   
 2019   		if ( log.isTraceEnabled() ) {
 2020   			log.trace( "Dehydrating entity: " + MessageHelper.infoString( this, id, getFactory() ) );
 2021   		}
 2022   
 2023   		for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
 2024   			if ( includeProperty[i] && isPropertyOfTable( i, j ) ) {
 2025   				getPropertyTypes()[i].nullSafeSet( ps, fields[i], index, includeColumns[i], session );
 2026   				//index += getPropertyColumnSpan( i );
 2027   				index += ArrayHelper.countTrue( includeColumns[i] ); //TODO:  this is kinda slow...
 2028   			}
 2029   		}
 2030   
 2031   		if ( rowId != null ) {
 2032   			ps.setObject( index, rowId );
 2033   			index += 1;
 2034   		}
 2035   		else if ( id != null ) {
 2036   			getIdentifierType().nullSafeSet( ps, id, index, session );
 2037   			index += getIdentifierColumnSpan();
 2038   		}
 2039   
 2040   		return index;
 2041   
 2042   	}
 2043   
 2044   	/**
 2045   	 * Unmarshall the fields of a persistent instance from a result set,
 2046   	 * without resolving associations or collections. Question: should
 2047   	 * this really be here, or should it be sent back to Loader?
 2048   	 */
 2049   	public Object[] hydrate(
 2050   			final ResultSet rs,
 2051   	        final Serializable id,
 2052   	        final Object object,
 2053   	        final Loadable rootLoadable,
 2054   	        final String[][] suffixedPropertyColumns,
 2055   	        final boolean allProperties,
 2056   	        final SessionImplementor session) throws SQLException, HibernateException {
 2057   
 2058   		if ( log.isTraceEnabled() ) {
 2059   			log.trace( "Hydrating entity: " + MessageHelper.infoString( this, id, getFactory() ) );
 2060   		}
 2061   
 2062   		final AbstractEntityPersister rootPersister = (AbstractEntityPersister) rootLoadable;
 2063   
 2064   		final boolean hasDeferred = rootPersister.hasSequentialSelect();
 2065   		PreparedStatement sequentialSelect = null;
 2066   		ResultSet sequentialResultSet = null;
 2067   		boolean sequentialSelectEmpty = false;
 2068   		try {
 2069   
 2070   			if ( hasDeferred ) {
 2071   				final String sql = rootPersister.getSequentialSelect( getEntityName() );
 2072   				if ( sql != null ) {
 2073   					//TODO: I am not so sure about the exception handling in this bit!
 2074   					sequentialSelect = session.getBatcher().prepareSelectStatement( sql );
 2075   					rootPersister.getIdentifierType().nullSafeSet( sequentialSelect, id, 1, session );
 2076   					sequentialResultSet = sequentialSelect.executeQuery();
 2077   					if ( !sequentialResultSet.next() ) {
 2078   						// TODO: Deal with the "optional" attribute in the <join> mapping;
 2079   						// this code assumes that optional defaults to "true" because it
 2080   						// doesn't actually seem to work in the fetch="join" code
 2081   						//
 2082   						// Note that actual proper handling of optional-ality here is actually
 2083   						// more involved than this patch assumes.  Remember that we might have
 2084   						// multiple <join/> mappings associated with a single entity.  Really
 2085   						// a couple of things need to happen to properly handle optional here:
 2086   						//  1) First and foremost, when handling multiple <join/>s, we really
 2087   						//      should be using the entity root table as the driving table;
 2088   						//      another option here would be to choose some non-optional joined
 2089   						//      table to use as the driving table.  In all likelihood, just using
 2090   						//      the root table is much simplier
 2091   						//  2) Need to add the FK columns corresponding to each joined table
 2092   						//      to the generated select list; these would then be used when
 2093   						//      iterating the result set to determine whether all non-optional
 2094   						//      data is present
 2095   						// My initial thoughts on the best way to deal with this would be
 2096   						// to introduce a new SequentialSelect abstraction that actually gets
 2097   						// generated in the persisters (ok, SingleTable...) and utilized here.
 2098   						// It would encapsulated all this required optional-ality checking...
 2099   						sequentialSelectEmpty = true;
 2100   					}
 2101   				}
 2102   			}
 2103   
 2104   			final String[] propNames = getPropertyNames();
 2105   			final Type[] types = getPropertyTypes();
 2106   			final Object[] values = new Object[types.length];
 2107   			final boolean[] laziness = getPropertyLaziness();
 2108   			final String[] propSubclassNames = getSubclassPropertySubclassNameClosure();
 2109   
 2110   			for ( int i = 0; i < types.length; i++ ) {
 2111   				if ( !propertySelectable[i] ) {
 2112   					values[i] = BackrefPropertyAccessor.UNKNOWN;
 2113   				}
 2114   				else if ( allProperties || !laziness[i] ) {
 2115   					//decide which ResultSet to get the property value from:
 2116   					final boolean propertyIsDeferred = hasDeferred &&
 2117   							rootPersister.isSubclassPropertyDeferred( propNames[i], propSubclassNames[i] );
 2118   					if ( propertyIsDeferred && sequentialSelectEmpty ) {
 2119   						values[i] = null;
 2120   					}
 2121   					else {
 2122   						final ResultSet propertyResultSet = propertyIsDeferred ? sequentialResultSet : rs;
 2123   						final String[] cols = propertyIsDeferred ? propertyColumnAliases[i] : suffixedPropertyColumns[i];
 2124   						values[i] = types[i].hydrate( propertyResultSet, cols, session, object );
 2125   					}
 2126   				}
 2127   				else {
 2128   					values[i] = LazyPropertyInitializer.UNFETCHED_PROPERTY;
 2129   				}
 2130   			}
 2131   
 2132   			if ( sequentialResultSet != null ) {
 2133   				sequentialResultSet.close();
 2134   			}
 2135   
 2136   			return values;
 2137   
 2138   		}
 2139   		finally {
 2140   			if ( sequentialSelect != null ) {
 2141   				session.getBatcher().closeStatement( sequentialSelect );
 2142   			}
 2143   		}
 2144   	}
 2145   
 2146   	protected boolean useInsertSelectIdentity() {
 2147   		return !useGetGeneratedKeys() && getFactory().getDialect().supportsInsertSelectIdentity();
 2148   	}
 2149   
 2150   	protected boolean useGetGeneratedKeys() {
 2151   		return getFactory().getSettings().isGetGeneratedKeysEnabled();
 2152   	}
 2153   
 2154   	protected String getSequentialSelect(String entityName) {
 2155   		throw new UnsupportedOperationException("no sequential selects");
 2156   	}
 2157   
 2158   	/**
 2159   	 * Perform an SQL INSERT, and then retrieve a generated identifier.
 2160   	 * <p/>
 2161   	 * This form is used for PostInsertIdentifierGenerator-style ids (IDENTITY,
 2162   	 * select, etc).
 2163   	 */
 2164   	protected Serializable insert(
 2165   			final Object[] fields,
 2166   	        final boolean[] notNull,
 2167   	        String sql,
 2168   	        final Object object,
 2169   	        final SessionImplementor session) throws HibernateException {
 2170   
 2171   		if ( log.isTraceEnabled() ) {
 2172   			log.trace( "Inserting entity: " + getEntityName() + " (native id)" );
 2173   			if ( isVersioned() ) {
 2174   				log.trace( "Version: " + Versioning.getVersion( fields, this ) );
 2175   			}
 2176   		}
 2177   
 2178   		Binder binder = new Binder() {
 2179   			public void bindValues(PreparedStatement ps) throws SQLException {
 2180   				dehydrate( null, fields, notNull, propertyColumnInsertable, 0, ps, session );
 2181   			}
 2182   			public Object getEntity() {
 2183   				return object;
 2184   			}
 2185   		};
 2186   		return identityDelegate.performInsert( sql, session, binder );
 2187   	}
 2188   
 2189   	public String getIdentitySelectString() {
 2190   		//TODO: cache this in an instvar
 2191   		return getFactory().getDialect().getIdentitySelectString(
 2192   				getTableName(0),
 2193   				getKeyColumns(0)[0],
 2194   				getIdentifierType().sqlTypes( getFactory() )[0]
 2195   		);
 2196   	}
 2197   
 2198   	public String getSelectByUniqueKeyString(String propertyName) {
 2199   		return new SimpleSelect( getFactory().getDialect() )
 2200   			.setTableName( getTableName(0) )
 2201   			.addColumns( getKeyColumns(0) )
 2202   			.addCondition( getPropertyColumnNames(propertyName), "=?" )
 2203   			.toStatementString();
 2204   	}
 2205   
 2206   	/**
 2207   	 * Perform an SQL INSERT.
 2208   	 * <p/>
 2209   	 * This for is used for all non-root tables as well as the root table
 2210   	 * in cases where the identifier value is known before the insert occurs.
 2211   	 */
 2212   	protected void insert(
 2213   			final Serializable id,
 2214   	        final Object[] fields,
 2215   	        final boolean[] notNull,
 2216   	        final int j,
 2217   	        final String sql,
 2218   	        final Object object,
 2219   	        final SessionImplementor session) throws HibernateException {
 2220   
 2221   		if ( isInverseTable( j ) ) {
 2222   			return;
 2223   		}
 2224   
 2225   		//note: it is conceptually possible that a UserType could map null to
 2226   		//	  a non-null value, so the following is arguable:
 2227   		if ( isNullableTable( j ) && isAllNull( fields, j ) ) {
 2228   			return;
 2229   		}
 2230   
 2231   		if ( log.isTraceEnabled() ) {
 2232   			log.trace( "Inserting entity: " + MessageHelper.infoString( this, id, getFactory() ) );
 2233   			if ( j == 0 && isVersioned() ) {
 2234   				log.trace( "Version: " + Versioning.getVersion( fields, this ) );
 2235   			}
 2236   		}
 2237   
 2238   		Expectation expectation = Expectations.appropriateExpectation( insertResultCheckStyles[j] );
 2239   		boolean callable = isInsertCallable( j );
 2240   		// we can't batch joined inserts, *especially* not if it is an identity insert;
 2241   		// nor can we batch statements where the expectation is based on an output param
 2242   		final boolean useBatch = j == 0 && expectation.canBeBatched();
 2243   		try {
 2244   
 2245   			// Render the SQL query
 2246   			final PreparedStatement insert;
 2247   			if ( useBatch ) {
 2248   				if ( callable ) {
 2249   					insert = session.getBatcher().prepareBatchCallableStatement( sql );
 2250   				}
 2251   				else {
 2252   					insert = session.getBatcher().prepareBatchStatement( sql );
 2253   				}
 2254   			}
 2255   			else {
 2256   				if ( callable ) {
 2257   					insert = session.getBatcher().prepareCallableStatement( sql );
 2258   				}
 2259   				else {
 2260   					insert = session.getBatcher().prepareStatement( sql );
 2261   				}
 2262   			}
 2263   
 2264   			try {
 2265   				int index = 1;
 2266   				index += expectation.prepare( insert );
 2267   
 2268   				// Write the values of fields onto the prepared statement - we MUST use the state at the time the
 2269   				// insert was issued (cos of foreign key constraints). Not necessarily the object's current state
 2270   
 2271   				dehydrate( id, fields, null, notNull, propertyColumnInsertable, j, insert, session, index );
 2272   
 2273   				if ( useBatch ) {
 2274   					// TODO : shouldnt inserts be Expectations.NONE?
 2275   					session.getBatcher().addToBatch( expectation );
 2276   				}
 2277   				else {
 2278   					expectation.verifyOutcome( insert.executeUpdate(), insert, -1 );
 2279   				}
 2280   
 2281   			}
 2282   			catch ( SQLException sqle ) {
 2283   				if ( useBatch ) {
 2284   					session.getBatcher().abortBatch( sqle );
 2285   				}
 2286   				throw sqle;
 2287   			}
 2288   			finally {
 2289   				if ( !useBatch ) {
 2290   					session.getBatcher().closeStatement( insert );
 2291   				}
 2292   			}
 2293   		}
 2294   		catch ( SQLException sqle ) {
 2295   			throw JDBCExceptionHelper.convert(
 2296   					getFactory().getSQLExceptionConverter(),
 2297   					sqle,
 2298   					"could not insert: " + MessageHelper.infoString( this ),
 2299   					sql
 2300   				);
 2301   		}
 2302   
 2303   	}
 2304   
 2305   	/**
 2306   	 * Perform an SQL UPDATE or SQL INSERT
 2307   	 */
 2308   	protected void updateOrInsert(
 2309   			final Serializable id,
 2310   	        final Object[] fields,
 2311   	        final Object[] oldFields,
 2312   	        final Object rowId,
 2313   	        final boolean[] includeProperty,
 2314   	        final int j,
 2315   	        final Object oldVersion,
 2316   	        final Object object,
 2317   	        final String sql,
 2318   	        final SessionImplementor session) throws HibernateException {
 2319   
 2320   		if ( !isInverseTable( j ) ) {
 2321   
 2322   			final boolean isRowToUpdate;
 2323   			if ( isNullableTable( j ) && oldFields != null && isAllNull( oldFields, j ) ) {
 2324   				//don't bother trying to update, we know there is no row there yet
 2325   				isRowToUpdate = false;
 2326   			}
 2327   			else if ( isNullableTable( j ) && isAllNull( fields, j ) ) {
 2328   				//if all fields are null, we might need to delete existing row
 2329   				isRowToUpdate = true;
 2330   				delete( id, oldVersion, j, object, getSQLDeleteStrings()[j], session, null );
 2331   			}
 2332   			else {
 2333   				//there is probably a row there, so try to update
 2334   				//if no rows were updated, we will find out
 2335   				isRowToUpdate = update( id, fields, oldFields, rowId, includeProperty, j, oldVersion, object, sql, session );
 2336   			}
 2337   
 2338   			if ( !isRowToUpdate && !isAllNull( fields, j ) ) {
 2339   				// assume that the row was not there since it previously had only null
 2340   				// values, so do an INSERT instead
 2341   				//TODO: does not respect dynamic-insert
 2342   				insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
 2343   			}
 2344   
 2345   		}
 2346   
 2347   	}
 2348   
 2349   	protected boolean update(
 2350   			final Serializable id,
 2351   	        final Object[] fields,
 2352   	        final Object[] oldFields,
 2353   	        final Object rowId,
 2354   	        final boolean[] includeProperty,
 2355   	        final int j,
 2356   	        final Object oldVersion,
 2357   	        final Object object,
 2358   	        final String sql,
 2359   	        final SessionImplementor session) throws HibernateException {
 2360   
 2361   		final boolean useVersion = j == 0 && isVersioned();
 2362   		final Expectation expectation = Expectations.appropriateExpectation( updateResultCheckStyles[j] );
 2363   		final boolean callable = isUpdateCallable( j );
 2364   		final boolean useBatch = j == 0 && expectation.canBeBatched() && isBatchable(); //note: updates to joined tables can't be batched...
 2365   
 2366   		if ( log.isTraceEnabled() ) {
 2367   			log.trace( "Updating entity: " + MessageHelper.infoString( this, id, getFactory() ) );
 2368   			if ( useVersion ) {
 2369   				log.trace( "Existing version: " + oldVersion + " -> New version: " + fields[getVersionProperty()] );
 2370   			}
 2371   		}
 2372   
 2373   		try {
 2374   
 2375   			int index = 1; // starting index
 2376   			final PreparedStatement update;
 2377   			if ( useBatch ) {
 2378   				if ( callable ) {
 2379   					update = session.getBatcher().prepareBatchCallableStatement( sql );
 2380   				}
 2381   				else {
 2382   					update = session.getBatcher().prepareBatchStatement( sql );
 2383   				}
 2384   			}
 2385   			else {
 2386   				if ( callable ) {
 2387   					update = session.getBatcher().prepareCallableStatement( sql );
 2388   				}
 2389   				else {
 2390   					update = session.getBatcher().prepareStatement( sql );
 2391   				}
 2392   			}
 2393   
 2394   			try {
 2395   
 2396   				index+= expectation.prepare( update );
 2397   
 2398   				//Now write the values of fields onto the prepared statement
 2399   				index = dehydrate( id, fields, rowId, includeProperty, propertyColumnUpdateable, j, update, session, index );
 2400   
 2401   				// Write any appropriate versioning conditional parameters
 2402   				if ( useVersion && Versioning.OPTIMISTIC_LOCK_VERSION == entityMetamodel.getOptimisticLockMode() ) {
 2403   					if ( checkVersion( includeProperty ) ) {
 2404   						getVersionType().nullSafeSet( update, oldVersion, index, session );
 2405   					}
 2406   				}
 2407   				else if ( entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION && oldFields != null ) {
 2408   					boolean[] versionability = getPropertyVersionability(); //TODO: is this really necessary????
 2409   					boolean[] includeOldField = entityMetamodel.getOptimisticLockMode() == Versioning.OPTIMISTIC_LOCK_ALL ?
 2410   							getPropertyUpdateability() : includeProperty;
 2411   					Type[] types = getPropertyTypes();
 2412   					for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
 2413   						boolean include = includeOldField[i] &&
 2414   								isPropertyOfTable( i, j ) &&
 2415   								versionability[i]; //TODO: is this really necessary????
 2416   						if ( include ) {
 2417   							boolean[] settable = types[i].toColumnNullness( oldFields[i], getFactory() );
 2418   							types[i].nullSafeSet(
 2419   									update,
 2420   									oldFields[i],
 2421   									index,
 2422   									settable,
 2423   									session
 2424   								);
 2425   							index += ArrayHelper.countTrue(settable);
 2426   						}
 2427   					}
 2428   				}
 2429   
 2430   				if ( useBatch ) {
 2431   					session.getBatcher().addToBatch( expectation );
 2432   					return true;
 2433   				}
 2434   				else {
 2435   					return check( update.executeUpdate(), id, j, expectation, update );
 2436   				}
 2437   
 2438   			}
 2439   			catch ( SQLException sqle ) {
 2440   				if ( useBatch ) {
 2441   					session.getBatcher().abortBatch( sqle );
 2442   				}
 2443   				throw sqle;
 2444   			}
 2445   			finally {
 2446   				if ( !useBatch ) {
 2447   					session.getBatcher().closeStatement( update );
 2448   				}
 2449   			}
 2450   
 2451   		}
 2452   		catch ( SQLException sqle ) {
 2453   			throw JDBCExceptionHelper.convert(
 2454   					getFactory().getSQLExceptionConverter(),
 2455   					sqle,
 2456   					"could not update: " + MessageHelper.infoString( this, id, getFactory() ),
 2457   					sql
 2458   				);
 2459   		}
 2460   	}
 2461   
 2462   	/**
 2463   	 * Perform an SQL DELETE
 2464   	 */
 2465   	protected void delete(
 2466   			final Serializable id,
 2467   			final Object version,
 2468   			final int j,
 2469   			final Object object,
 2470   			final String sql,
 2471   			final SessionImplementor session,
 2472   			final Object[] loadedState) throws HibernateException {
 2473   
 2474   		if ( isInverseTable( j ) ) {
 2475   			return;
 2476   		}
 2477   
 2478   		final boolean useVersion = j == 0 && isVersioned();
 2479   		final boolean callable = isDeleteCallable( j );
 2480   		final Expectation expectation = Expectations.appropriateExpectation( deleteResultCheckStyles[j] );
 2481   		final boolean useBatch = j == 0 && isBatchable() && expectation.canBeBatched();
 2482   
 2483   		if ( log.isTraceEnabled() ) {
 2484   			log.trace( "Deleting entity: " + MessageHelper.infoString( this, id, getFactory() ) );
 2485   			if ( useVersion ) {
 2486   				log.trace( "Version: " + version );
 2487   			}
 2488   		}
 2489   
 2490   		if ( isTableCascadeDeleteEnabled( j ) ) {
 2491   			if ( log.isTraceEnabled() ) {
 2492   				log.trace( "delete handled by foreign key constraint: " + getTableName( j ) );
 2493   			}
 2494   			return; //EARLY EXIT!
 2495   		}
 2496   
 2497   		try {
 2498   
 2499   			//Render the SQL query
 2500   			PreparedStatement delete;
 2501   			int index = 1;
 2502   			if ( useBatch ) {
 2503   				if ( callable ) {
 2504   					delete = session.getBatcher().prepareBatchCallableStatement( sql );
 2505   				}
 2506   				else {
 2507   					delete = session.getBatcher().prepareBatchStatement( sql );
 2508   				}
 2509   			}
 2510   			else {
 2511   				if ( callable ) {
 2512   					delete = session.getBatcher().prepareCallableStatement( sql );
 2513   				}
 2514   				else {
 2515   					delete = session.getBatcher().prepareStatement( sql );
 2516   				}
 2517   			}
 2518   
 2519   			try {
 2520   
 2521   				index += expectation.prepare( delete );
 2522   
 2523   				// Do the key. The key is immutable so we can use the _current_ object state - not necessarily
 2524   				// the state at the time the delete was issued
 2525   				getIdentifierType().nullSafeSet( delete, id, index, session );
 2526   				index += getIdentifierColumnSpan();
 2527   
 2528   				// We should use the _current_ object state (ie. after any updates that occurred during flush)
 2529   
 2530   				if ( useVersion ) {
 2531   					getVersionType().nullSafeSet( delete, version, index, session );
 2532   				}
 2533   				else if ( entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION && loadedState != null ) {
 2534   					boolean[] versionability = getPropertyVersionability();
 2535   					Type[] types = getPropertyTypes();
 2536   					for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
 2537   						if ( isPropertyOfTable( i, j ) && versionability[i] ) {
 2538   							// this property belongs to the table and it is not specifically
 2539   							// excluded from optimistic locking by optimistic-lock="false"
 2540   							boolean[] settable = types[i].toColumnNullness( loadedState[i], getFactory() );
 2541   							types[i].nullSafeSet( delete, loadedState[i], index, settable, session );
 2542   							index += ArrayHelper.countTrue( settable );
 2543   						}
 2544   					}
 2545   				}
 2546   
 2547   				if ( useBatch ) {
 2548   					session.getBatcher().addToBatch( expectation );
 2549   				}
 2550   				else {
 2551   					check( delete.executeUpdate(), id, j, expectation, delete );
 2552   				}
 2553   
 2554   			}
 2555   			catch ( SQLException sqle ) {
 2556   				if ( useBatch ) {
 2557   					session.getBatcher().abortBatch( sqle );
 2558   				}
 2559   				throw sqle;
 2560   			}
 2561   			finally {
 2562   				if ( !useBatch ) {
 2563   					session.getBatcher().closeStatement( delete );
 2564   				}
 2565   			}
 2566   
 2567   		}
 2568   		catch ( SQLException sqle ) {
 2569   			throw JDBCExceptionHelper.convert(
 2570   					getFactory().getSQLExceptionConverter(),
 2571   					sqle,
 2572   					"could not delete: " +
 2573   					MessageHelper.infoString( this, id, getFactory() ),
 2574   					sql
 2575   				);
 2576   
 2577   		}
 2578   
 2579   	}
 2580   
 2581   	private String[] getUpdateStrings(boolean byRowId, boolean lazy) {
 2582   		if ( byRowId ) {
 2583   			return lazy ? getSQLLazyUpdateByRowIdStrings() : getSQLUpdateByRowIdStrings();
 2584   		}
 2585   		else {
 2586   			return lazy ? getSQLLazyUpdateStrings() : getSQLUpdateStrings();
 2587   		}
 2588   	}
 2589   
 2590   	/**
 2591   	 * Update an object
 2592   	 */
 2593   	public void update(
 2594   			final Serializable id,
 2595   	        final Object[] fields,
 2596   	        final int[] dirtyFields,
 2597   	        final boolean hasDirtyCollection,
 2598   	        final Object[] oldFields,
 2599   	        final Object oldVersion,
 2600   	        final Object object,
 2601   	        final Object rowId,
 2602   	        final SessionImplementor session) throws HibernateException {
 2603   
 2604   		//note: dirtyFields==null means we had no snapshot, and we couldn't get one using select-before-update
 2605   		//	  oldFields==null just means we had no snapshot to begin with (we might have used select-before-update to get the dirtyFields)
 2606   
 2607   		final boolean[] tableUpdateNeeded = getTableUpdateNeeded( dirtyFields, hasDirtyCollection );
 2608   		final int span = getTableSpan();
 2609   
 2610   		final boolean[] propsToUpdate;
 2611   		final String[] updateStrings;
 2612   		if ( entityMetamodel.isDynamicUpdate() && dirtyFields != null ) {
 2613   			// For the case of dynamic-update="true", we need to generate the UPDATE SQL
 2614   			propsToUpdate = getPropertiesToUpdate( dirtyFields, hasDirtyCollection );
 2615   			// don't need to check laziness (dirty checking algorithm handles that)
 2616   			updateStrings = new String[span];
 2617   			for ( int j = 0; j < span; j++ ) {
 2618   				updateStrings[j] = tableUpdateNeeded[j] ?
 2619   						generateUpdateString( propsToUpdate, j, oldFields, j == 0 && rowId != null ) :
 2620   						null;
 2621   			}
 2622   		}
 2623   		else {
 2624   			// For the case of dynamic-update="false", or no snapshot, we use the static SQL
 2625   			updateStrings = getUpdateStrings(
 2626   					rowId != null,
 2627   					hasUninitializedLazyProperties( object, session.getEntityMode() )
 2628   				);
 2629   			propsToUpdate = getPropertyUpdateability( object, session.getEntityMode() );
 2630   		}
 2631   
 2632   		for ( int j = 0; j < span; j++ ) {
 2633   			// Now update only the tables with dirty properties (and the table with the version number)
 2634   			if ( tableUpdateNeeded[j] ) {
 2635   				updateOrInsert(
 2636   						id,
 2637   						fields,
 2638   						oldFields,
 2639   						j == 0 ? rowId : null,
 2640   						propsToUpdate,
 2641   						j,
 2642   						oldVersion,
 2643   						object,
 2644   						updateStrings[j],
 2645   						session
 2646   					);
 2647   			}
 2648   		}
 2649   	}
 2650   
 2651   	public Serializable insert(Object[] fields, Object object, SessionImplementor session)
 2652   			throws HibernateException {
 2653   
 2654   		final int span = getTableSpan();
 2655   		final Serializable id;
 2656   		if ( entityMetamodel.isDynamicInsert() ) {
 2657   			// For the case of dynamic-insert="true", we need to generate the INSERT SQL
 2658   			boolean[] notNull = getPropertiesToInsert( fields );
 2659   			id = insert( fields, notNull, generateInsertString( true, notNull ), object, session );
 2660   			for ( int j = 1; j < span; j++ ) {
 2661   				insert( id, fields, notNull, j, generateInsertString( notNull, j ), object, session );
 2662   			}
 2663   		}
 2664   		else {
 2665   			// For the case of dynamic-insert="false", use the static SQL
 2666   			id = insert( fields, getPropertyInsertability(), getSQLIdentityInsertString(), object, session );
 2667   			for ( int j = 1; j < span; j++ ) {
 2668   				insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
 2669   			}
 2670   		}
 2671   		return id;
 2672   	}
 2673   
 2674   	public void insert(Serializable id, Object[] fields, Object object, SessionImplementor session)
 2675   			throws HibernateException {
 2676   
 2677   		final int span = getTableSpan();
 2678   		if ( entityMetamodel.isDynamicInsert() ) {
 2679   			// For the case of dynamic-insert="true", we need to generate the INSERT SQL
 2680   			boolean[] notNull = getPropertiesToInsert( fields );
 2681   			for ( int j = 0; j < span; j++ ) {
 2682   				insert( id, fields, notNull, j, generateInsertString( notNull, j ), object, session );
 2683   			}
 2684   		}
 2685   		else {
 2686   			// For the case of dynamic-insert="false", use the static SQL
 2687   			for ( int j = 0; j < span; j++ ) {
 2688   				insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
 2689   			}
 2690   		}
 2691   	}
 2692   
 2693   	/**
 2694   	 * Delete an object
 2695   	 */
 2696   	public void delete(Serializable id, Object version, Object object, SessionImplementor session)
 2697   			throws HibernateException {
 2698   		final int span = getTableSpan();
 2699   		boolean isImpliedOptimisticLocking = !entityMetamodel.isVersioned() && entityMetamodel.getOptimisticLockMode() > Versioning.OPTIMISTIC_LOCK_VERSION;
 2700   		Object[] loadedState = null;
 2701   		if ( isImpliedOptimisticLocking ) {
 2702   			// need to treat this as if it where optimistic-lock="all" (dirty does *not* make sense);
 2703   			// first we need to locate the "loaded" state
 2704   			//
 2705   			// Note, it potentially could be a proxy, so perform the location the safe way...
 2706   			EntityKey key = new EntityKey( id, this, session.getEntityMode() );
 2707   			Object entity = session.getPersistenceContext().getEntity( key );
 2708   			if ( entity != null ) {
 2709   				EntityEntry entry = session.getPersistenceContext().getEntry( entity );
 2710   				loadedState = entry.getLoadedState();
 2711   			}
 2712   		}
 2713   
 2714   		final String[] deleteStrings;
 2715   		if ( isImpliedOptimisticLocking && loadedState != null ) {
 2716   			// we need to utilize dynamic delete statements
 2717   			deleteStrings = generateSQLDeletStrings( loadedState );
 2718   		}
 2719   		else {
 2720   			// otherwise, utilize the static delete statements
 2721   			deleteStrings = getSQLDeleteStrings();
 2722   		}
 2723   
 2724   		for ( int j = span - 1; j >= 0; j-- ) {
 2725   			delete( id, version, j, object, deleteStrings[j], session, loadedState );
 2726   		}
 2727   
 2728   	}
 2729   
 2730   	private String[] generateSQLDeletStrings(Object[] loadedState) {
 2731   		int span = getTableSpan();
 2732   		String[] deleteStrings = new String[span];
 2733   		for ( int j = span - 1; j >= 0; j-- ) {
 2734   			Delete delete = new Delete()
 2735   					.setTableName( getTableName( j ) )
 2736   					.setPrimaryKeyColumnNames( getKeyColumns( j ) );
 2737   			if ( getFactory().getSettings().isCommentsEnabled() ) {
 2738   				delete.setComment( "delete " + getEntityName() + " [" + j + "]" );
 2739   			}
 2740   
 2741   			boolean[] versionability = getPropertyVersionability();
 2742   			Type[] types = getPropertyTypes();
 2743   			for ( int i = 0; i < entityMetamodel.getPropertySpan(); i++ ) {
 2744   				if ( isPropertyOfTable( i, j ) && versionability[i] ) {
 2745   					// this property belongs to the table and it is not specifically
 2746   					// excluded from optimistic locking by optimistic-lock="false"
 2747   					String[] propertyColumnNames = getPropertyColumnNames( i );
 2748   					boolean[] propertyNullness = types[i].toColumnNullness( loadedState[i], getFactory() );
 2749   					for ( int k = 0; k < propertyNullness.length; k++ ) {
 2750   						if ( propertyNullness[k] ) {
 2751   							delete.addWhereFragment( propertyColumnNames[k] + " = ?" );
 2752   						}
 2753   						else {
 2754   							delete.addWhereFragment( propertyColumnNames[k] + " is null" );
 2755   						}
 2756   					}
 2757   				}
 2758   			}
 2759   			deleteStrings[j] = delete.toStatementString();
 2760   		}
 2761   		return deleteStrings;
 2762   	}
 2763   
 2764   	protected void logStaticSQL() {
 2765   		if ( log.isDebugEnabled() ) {
 2766   			log.debug( "Static SQL for entity: " + getEntityName() );
 2767   			if ( sqlLazySelectString != null ) {
 2768   				log.debug( " Lazy select: " + sqlLazySelectString );
 2769   			}
 2770   			if ( sqlVersionSelectString != null ) {
 2771   				log.debug( " Version select: " + sqlVersionSelectString );
 2772   			}
 2773   			if ( sqlSnapshotSelectString != null ) {
 2774   				log.debug( " Snapshot select: " + sqlSnapshotSelectString );
 2775   			}
 2776   			for ( int j = 0; j < getTableSpan(); j++ ) {
 2777   				log.debug( " Insert " + j + ": " + getSQLInsertStrings()[j] );
 2778   				log.debug( " Update " + j + ": " + getSQLUpdateStrings()[j] );
 2779   				log.debug( " Delete " + j + ": " + getSQLDeleteStrings()[j] );
 2780   
 2781   			}
 2782   			if ( sqlIdentityInsertString != null ) {
 2783   				log.debug( " Identity insert: " + sqlIdentityInsertString );
 2784   			}
 2785   			if ( sqlUpdateByRowIdString != null ) {
 2786   				log.debug( " Update by row id (all fields): " + sqlUpdateByRowIdString );
 2787   			}
 2788   			if ( sqlLazyUpdateByRowIdString != null ) {
 2789   				log.debug( " Update by row id (non-lazy fields): " + sqlLazyUpdateByRowIdString );
 2790   			}
 2791   			if ( sqlInsertGeneratedValuesSelectString != null ) {
 2792   				log.debug( "Insert-generated property select: " + sqlInsertGeneratedValuesSelectString );
 2793   			}
 2794   			if ( sqlUpdateGeneratedValuesSelectString != null ) {
 2795   				log.debug( "Update-generated property select: " + sqlUpdateGeneratedValuesSelectString );
 2796   			}
 2797   		}
 2798   	}
 2799   
 2800   	public String filterFragment(String alias, Map enabledFilters) throws MappingException {
 2801   		final StringBuffer sessionFilterFragment = new StringBuffer();
 2802   		filterHelper.render( sessionFilterFragment, generateFilterConditionAlias( alias ), enabledFilters );
 2803   
 2804   		return sessionFilterFragment.append( filterFragment( alias ) ).toString();
 2805   	}
 2806   
 2807   	public String generateFilterConditionAlias(String rootAlias) {
 2808   		return rootAlias;
 2809   	}
 2810   
 2811   	public String oneToManyFilterFragment(String alias) throws MappingException {
 2812   		return "";
 2813   	}
 2814   
 2815   	public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
 2816   		return getSubclassTableSpan() == 1 ?
 2817   				"" : //just a performance opt!
 2818   				createJoin( alias, innerJoin, includeSubclasses ).toFromFragmentString();
 2819   	}
 2820   
 2821   	public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
 2822   		return getSubclassTableSpan() == 1 ?
 2823   				"" : //just a performance opt!
 2824   				createJoin( alias, innerJoin, includeSubclasses ).toWhereFragmentString();
 2825   	}
 2826   
 2827   	protected boolean isSubclassTableLazy(int j) {
 2828   		return false;
 2829   	}
 2830   
 2831   	protected JoinFragment createJoin(String name, boolean innerJoin, boolean includeSubclasses) {
 2832   		final String[] idCols = StringHelper.qualify( name, getIdentifierColumnNames() ); //all joins join to the pk of the driving table
 2833   		final JoinFragment join = getFactory().getDialect().createOuterJoinFragment();
 2834   		final int tableSpan = getSubclassTableSpan();
 2835   		for ( int j = 1; j < tableSpan; j++ ) { //notice that we skip the first table; it is the driving table!
 2836   			final boolean joinIsIncluded = isClassOrSuperclassTable( j ) ||
 2837   					( includeSubclasses && !isSubclassTableSequentialSelect( j ) && !isSubclassTableLazy( j ) );
 2838   			if ( joinIsIncluded ) {
 2839   				join.addJoin( getSubclassTableName( j ),
 2840   						generateTableAlias( name, j ),
 2841   						idCols,
 2842   						getSubclassTableKeyColumns( j ),
 2843   						innerJoin && isClassOrSuperclassTable( j ) && !isInverseTable( j ) && !isNullableTable( j ) ?
 2844   						JoinFragment.INNER_JOIN : //we can inner join to superclass tables (the row MUST be there)
 2845   						JoinFragment.LEFT_OUTER_JOIN //we can never inner join to subclass tables
 2846   					);
 2847   			}
 2848   		}
 2849   		return join;
 2850   	}
 2851   
 2852   	protected JoinFragment createJoin(int[] tableNumbers, String drivingAlias) {
 2853   		final String[] keyCols = StringHelper.qualify( drivingAlias, getSubclassTableKeyColumns( tableNumbers[0] ) );
 2854   		final JoinFragment jf = getFactory().getDialect().createOuterJoinFragment();
 2855   		for ( int i = 1; i < tableNumbers.length; i++ ) { //skip the driving table
 2856   			final int j = tableNumbers[i];
 2857   			jf.addJoin( getSubclassTableName( j ),
 2858   					generateTableAlias( getRootAlias(), j ),
 2859   					keyCols,
 2860   					getSubclassTableKeyColumns( j ),
 2861   					isInverseSubclassTable( j ) || isNullableSubclassTable( j ) ?
 2862   					JoinFragment.LEFT_OUTER_JOIN :
 2863   					JoinFragment.INNER_JOIN );
 2864   		}
 2865   		return jf;
 2866   	}
 2867   
 2868   	protected SelectFragment createSelect(final int[] subclassColumnNumbers,
 2869   										  final int[] subclassFormulaNumbers) {
 2870   
 2871   		SelectFragment selectFragment = new SelectFragment();
 2872   
 2873   		int[] columnTableNumbers = getSubclassColumnTableNumberClosure();
 2874   		String[] columnAliases = getSubclassColumnAliasClosure();
 2875   		String[] columns = getSubclassColumnClosure();
 2876   		for ( int i = 0; i < subclassColumnNumbers.length; i++ ) {
 2877   			if ( subclassColumnSelectableClosure[i] ) {
 2878   				int columnNumber = subclassColumnNumbers[i];
 2879   				final String subalias = generateTableAlias( getRootAlias(), columnTableNumbers[columnNumber] );
 2880   				selectFragment.addColumn( subalias, columns[columnNumber], columnAliases[columnNumber] );
 2881   			}
 2882   		}
 2883   
 2884   		int[] formulaTableNumbers = getSubclassFormulaTableNumberClosure();
 2885   		String[] formulaTemplates = getSubclassFormulaTemplateClosure();
 2886   		String[] formulaAliases = getSubclassFormulaAliasClosure();
 2887   		for ( int i = 0; i < subclassFormulaNumbers.length; i++ ) {
 2888   			int formulaNumber = subclassFormulaNumbers[i];
 2889   			final String subalias = generateTableAlias( getRootAlias(), formulaTableNumbers[formulaNumber] );
 2890   			selectFragment.addFormula( subalias, formulaTemplates[formulaNumber], formulaAliases[formulaNumber] );
 2891   		}
 2892   
 2893   		return selectFragment;
 2894   	}
 2895   
 2896   	protected String createFrom(int tableNumber, String alias) {
 2897   		return getSubclassTableName( tableNumber ) + ' ' + alias;
 2898   	}
 2899   
 2900   	protected String createWhereByKey(int tableNumber, String alias) {
 2901   		//TODO: move to .sql package, and refactor with similar things!
 2902   		return StringHelper.join( "=? and ",
 2903   				StringHelper.qualify( alias, getSubclassTableKeyColumns( tableNumber ) ) ) + "=?";
 2904   	}
 2905   
 2906   	protected String renderSelect(
 2907   			final int[] tableNumbers,
 2908   	        final int[] columnNumbers,
 2909   	        final int[] formulaNumbers) {
 2910   
 2911   		Arrays.sort( tableNumbers ); //get 'em in the right order (not that it really matters)
 2912   
 2913   		//render the where and from parts
 2914   		int drivingTable = tableNumbers[0];
 2915   		final String drivingAlias = generateTableAlias( getRootAlias(), drivingTable ); //we *could* regerate this inside each called method!
 2916   		final String where = createWhereByKey( drivingTable, drivingAlias );
 2917   		final String from = createFrom( drivingTable, drivingAlias );
 2918   
 2919   		//now render the joins
 2920   		JoinFragment jf = createJoin( tableNumbers, drivingAlias );
 2921   
 2922   		//now render the select clause
 2923   		SelectFragment selectFragment = createSelect( columnNumbers, formulaNumbers );
 2924   
 2925   		//now tie it all together
 2926   		Select select = new Select( getFactory().getDialect() );
 2927   		select.setSelectClause( selectFragment.toFragmentString().substring( 2 ) );
 2928   		select.setFromClause( from );
 2929   		select.setWhereClause( where );
 2930   		select.setOuterJoins( jf.toFromFragmentString(), jf.toWhereFragmentString() );
 2931   		if ( getFactory().getSettings().isCommentsEnabled() ) {
 2932   			select.setComment( "sequential select " + getEntityName() );
 2933   		}
 2934   		return select.toStatementString();
 2935   	}
 2936   
 2937   	private String getRootAlias() {
 2938   		return StringHelper.generateAlias( getEntityName() );
 2939   	}
 2940   
 2941   	protected void postConstruct(Mapping mapping) throws MappingException {
 2942   		initPropertyPaths(mapping);
 2943   
 2944   		//insert/update/delete SQL
 2945   		final int joinSpan = getTableSpan();
 2946   		sqlDeleteStrings = new String[joinSpan];
 2947   		sqlInsertStrings = new String[joinSpan];
 2948   		sqlUpdateStrings = new String[joinSpan];
 2949   		sqlLazyUpdateStrings = new String[joinSpan];
 2950   
 2951   		sqlUpdateByRowIdString = rowIdName == null ?
 2952   				null :
 2953   				generateUpdateString( getPropertyUpdateability(), 0, true );
 2954   		sqlLazyUpdateByRowIdString = rowIdName == null ?
 2955   				null :
 2956   				generateUpdateString( getNonLazyPropertyUpdateability(), 0, true );
 2957   
 2958   		for ( int j = 0; j < joinSpan; j++ ) {
 2959   			sqlInsertStrings[j] = customSQLInsert[j] == null ?
 2960   					generateInsertString( getPropertyInsertability(), j ) :
 2961   					customSQLInsert[j];
 2962   			sqlUpdateStrings[j] = customSQLUpdate[j] == null ?
 2963   					generateUpdateString( getPropertyUpdateability(), j, false ) :
 2964   					customSQLUpdate[j];
 2965   			sqlLazyUpdateStrings[j] = customSQLUpdate[j] == null ?
 2966   					generateUpdateString( getNonLazyPropertyUpdateability(), j, false ) :
 2967   					customSQLUpdate[j];
 2968   			sqlDeleteStrings[j] = customSQLDelete[j] == null ?
 2969   					generateDeleteString( j ) :
 2970   					customSQLDelete[j];
 2971   		}
 2972   
 2973   		tableHasColumns = new boolean[joinSpan];
 2974   		for ( int j = 0; j < joinSpan; j++ ) {
 2975   			tableHasColumns[j] = sqlUpdateStrings[j] != null;
 2976   		}
 2977   
 2978   		//select SQL
 2979   		sqlSnapshotSelectString = generateSnapshotSelectString();
 2980   		sqlLazySelectString = generateLazySelectString();
 2981   		sqlVersionSelectString = generateSelectVersionString();
 2982   		if ( hasInsertGeneratedProperties() ) {
 2983   			sqlInsertGeneratedValuesSelectString = generateInsertGeneratedValuesSelectString();
 2984   		}
 2985   		if ( hasUpdateGeneratedProperties() ) {
 2986   			sqlUpdateGeneratedValuesSelectString = generateUpdateGeneratedValuesSelectString();
 2987   		}
 2988   		if ( isIdentifierAssignedByInsert() ) {
 2989   			identityDelegate = ( ( PostInsertIdentifierGenerator ) getIdentifierGenerator() )
 2990   					.getInsertGeneratedIdentifierDelegate( this, getFactory().getDialect(), useGetGeneratedKeys() );
 2991   			sqlIdentityInsertString = customSQLInsert[0] == null
 2992   					? generateIdentityInsertString( getPropertyInsertability() )
 2993   					: customSQLInsert[0];
 2994   		}
 2995   		else {
 2996   			sqlIdentityInsertString = null;
 2997   		}
 2998   
 2999   		logStaticSQL();
 3000   
 3001   	}
 3002   
 3003   	public void postInstantiate() throws MappingException {
 3004   
 3005   		createLoaders();
 3006   		createUniqueKeyLoaders();
 3007   		createQueryLoader();
 3008   
 3009   	}
 3010   
 3011   	private void createLoaders() {
 3012   		loaders.put( LockMode.NONE, createEntityLoader( LockMode.NONE ) );
 3013   
 3014   		UniqueEntityLoader readLoader = createEntityLoader( LockMode.READ );
 3015   		loaders.put( LockMode.READ, readLoader );
 3016   
 3017   		//TODO: inexact, what we really need to know is: are any outer joins used?
 3018   		boolean disableForUpdate = getSubclassTableSpan() > 1 &&
 3019   				hasSubclasses() &&
 3020   				!getFactory().getDialect().supportsOuterJoinForUpdate();
 3021   
 3022   		loaders.put(
 3023   				LockMode.UPGRADE,
 3024   				disableForUpdate ?
 3025   						readLoader :
 3026   						createEntityLoader( LockMode.UPGRADE )
 3027   			);
 3028   		loaders.put(
 3029   				LockMode.UPGRADE_NOWAIT,
 3030   				disableForUpdate ?
 3031   						readLoader :
 3032   						createEntityLoader( LockMode.UPGRADE_NOWAIT )
 3033   			);
 3034   		loaders.put(
 3035   				LockMode.FORCE,
 3036   				disableForUpdate ?
 3037   						readLoader :
 3038   						createEntityLoader( LockMode.FORCE )
 3039   			);
 3040   
 3041   		loaders.put(
 3042   				"merge",
 3043   				new CascadeEntityLoader( this, CascadingAction.MERGE, getFactory() )
 3044   			);
 3045   		loaders.put(
 3046   				"refresh",
 3047   				new CascadeEntityLoader( this, CascadingAction.REFRESH, getFactory() )
 3048   			);
 3049   	}
 3050   
 3051   	protected void createQueryLoader() {
 3052   		if ( loaderName != null ) {
 3053   			queryLoader = new NamedQueryLoader( loaderName, this );
 3054   		}
 3055   	}
 3056   
 3057   	/**
 3058   	 * Load an instance using either the <tt>forUpdateLoader</tt> or the outer joining <tt>loader</tt>,
 3059   	 * depending upon the value of the <tt>lock</tt> parameter
 3060   	 */
 3061   	public Object load(Serializable id, Object optionalObject, LockMode lockMode, SessionImplementor session)
 3062   			throws HibernateException {
 3063   
 3064   		if ( log.isTraceEnabled() ) {
 3065   			log.trace(
 3066   					"Fetching entity: " +
 3067   					MessageHelper.infoString( this, id, getFactory() )
 3068   				);
 3069   		}
 3070   
 3071   		final UniqueEntityLoader loader = getAppropriateLoader( lockMode, session );
 3072   		return loader.load( id, optionalObject, session );
 3073   	}
 3074   
 3075   	private UniqueEntityLoader getAppropriateLoader(LockMode lockMode, SessionImplementor session) {
 3076   		final Map enabledFilters = session.getEnabledFilters();
 3077   		if ( queryLoader != null ) {
 3078   			return queryLoader;
 3079   		}
 3080   		else if ( enabledFilters == null || enabledFilters.isEmpty() ) {
 3081   			if ( session.getFetchProfile()!=null && LockMode.UPGRADE.greaterThan(lockMode) ) {
 3082   				return (UniqueEntityLoader) loaders.get( session.getFetchProfile() );
 3083   			}
 3084   			else {
 3085   				return (UniqueEntityLoader) loaders.get( lockMode );
 3086   			}
 3087   		}
 3088   		else {
 3089   			return createEntityLoader( lockMode, enabledFilters );
 3090   		}
 3091   	}
 3092   
 3093   	private boolean isAllNull(Object[] array, int tableNumber) {
 3094   		for ( int i = 0; i < array.length; i++ ) {
 3095   			if ( isPropertyOfTable( i, tableNumber ) && array[i] != null ) {
 3096   				return false;
 3097   			}
 3098   		}
 3099   		return true;
 3100   	}
 3101   
 3102   	public boolean isSubclassPropertyNullable(int i) {
 3103   		return subclassPropertyNullabilityClosure[i];
 3104   	}
 3105   
 3106   	/**
 3107   	 * Transform the array of property indexes to an array of booleans,
 3108   	 * true when the property is dirty
 3109   	 */
 3110   	protected final boolean[] getPropertiesToUpdate(final int[] dirtyProperties, final boolean hasDirtyCollection) {
 3111   		final boolean[] propsToUpdate = new boolean[ entityMetamodel.getPropertySpan() ];
 3112   		final boolean[] updateability = getPropertyUpdateability(); //no need to check laziness, dirty checking handles that
 3113   		for ( int j = 0; j < dirtyProperties.length; j++ ) {
 3114   			int property = dirtyProperties[j];
 3115   			if ( updateability[property] ) {
 3116   				propsToUpdate[property] = true;
 3117   			}
 3118   		}
 3119   		if ( isVersioned() ) {
 3120   			propsToUpdate[ getVersionProperty() ] =
 3121   				Versioning.isVersionIncrementRequired( dirtyProperties, hasDirtyCollection, getPropertyVersionability() );
 3122   		}
 3123   		return propsToUpdate;
 3124   	}
 3125   
 3126   	/**
 3127   	 * Transform the array of property indexes to an array of booleans,
 3128   	 * true when the property is insertable and non-null
 3129   	 */
 3130   	protected boolean[] getPropertiesToInsert(Object[] fields) {
 3131   		boolean[] notNull = new boolean[fields.length];
 3132   		boolean[] insertable = getPropertyInsertability();
 3133   		for ( int i = 0; i < fields.length; i++ ) {
 3134   			notNull[i] = insertable[i] && fields[i] != null;
 3135   		}
 3136   		return notNull;
 3137   	}
 3138   
 3139   	/**
 3140   	 * Locate the property-indices of all properties considered to be dirty.
 3141   	 *
 3142   	 * @param currentState The current state of the entity (the state to be checked).
 3143   	 * @param previousState The previous state of the entity (the state to be checked against).
 3144   	 * @param entity The entity for which we are checking state dirtiness.
 3145   	 * @param session The session in which the check is ccurring.
 3146   	 * @return <tt>null</tt> or the indices of the dirty properties
 3147   	 * @throws HibernateException
 3148   	 */
 3149   	public int[] findDirty(Object[] currentState, Object[] previousState, Object entity, SessionImplementor session)
 3150   	throws HibernateException {
 3151   		int[] props = TypeFactory.findDirty(
 3152   				entityMetamodel.getProperties(),
 3153   				currentState,
 3154   				previousState,
 3155   				propertyColumnUpdateable,
 3156   				hasUninitializedLazyProperties( entity, session.getEntityMode() ),
 3157   				session
 3158   			);
 3159   		if ( props == null ) {
 3160   			return null;
 3161   		}
 3162   		else {
 3163   			logDirtyProperties( props );
 3164   			return props;
 3165   		}
 3166   	}
 3167   
 3168   	/**
 3169   	 * Locate the property-indices of all properties considered to be dirty.
 3170   	 *
 3171   	 * @param old The old state of the entity.
 3172   	 * @param current The current state of the entity.
 3173   	 * @param entity The entity for which we are checking state modification.
 3174   	 * @param session The session in which the check is ccurring.
 3175   	 * @return <tt>null</tt> or the indices of the modified properties
 3176   	 * @throws HibernateException
 3177   	 */
 3178   	public int[] findModified(Object[] old, Object[] current, Object entity, SessionImplementor session)
 3179   	throws HibernateException {
 3180   		int[] props = TypeFactory.findModified(
 3181   				entityMetamodel.getProperties(),
 3182   				current,
 3183   				old,
 3184   				propertyColumnUpdateable,
 3185   				hasUninitializedLazyProperties( entity, session.getEntityMode() ),
 3186   				session
 3187   			);
 3188   		if ( props == null ) {
 3189   			return null;
 3190   		}
 3191   		else {
 3192   			logDirtyProperties( props );
 3193   			return props;
 3194   		}
 3195   	}
 3196   
 3197   	/**
 3198   	 * Which properties appear in the SQL update?
 3199   	 * (Initialized, updateable ones!)
 3200   	 */
 3201   	protected boolean[] getPropertyUpdateability(Object entity, EntityMode entityMode) {
 3202   		return hasUninitializedLazyProperties( entity, entityMode ) ?
 3203   				getNonLazyPropertyUpdateability() :
 3204   				getPropertyUpdateability();
 3205   	}
 3206   
 3207   	private void logDirtyProperties(int[] props) {
 3208   		if ( log.isTraceEnabled() ) {
 3209   			for ( int i = 0; i < props.length; i++ ) {
 3210   				String propertyName = entityMetamodel.getProperties()[ props[i] ].getName();
 3211   				log.trace( StringHelper.qualify( getEntityName(), propertyName ) + " is dirty" );
 3212   			}
 3213   		}
 3214   	}
 3215   
 3216   	protected EntityTuplizer getTuplizer(SessionImplementor session) {
 3217   		return getTuplizer( session.getEntityMode() );
 3218   	}
 3219   
 3220   	protected EntityTuplizer getTuplizer(EntityMode entityMode) {
 3221   		return entityMetamodel.getTuplizer( entityMode );
 3222   	}
 3223   
 3224   	public SessionFactoryImplementor getFactory() {
 3225   		return factory;
 3226   	}
 3227   
 3228   	public EntityMetamodel getEntityMetamodel() {
 3229   		return entityMetamodel;
 3230   	}
 3231   
 3232   	public boolean hasCache() {
 3233   		return cacheAccessStrategy != null;
 3234   	}
 3235   
 3236   	public EntityRegionAccessStrategy getCacheAccessStrategy() {
 3237   		return cacheAccessStrategy;
 3238   	}
 3239   
 3240   	public CacheEntryStructure getCacheEntryStructure() {
 3241   		return cacheEntryStructure;
 3242   	}
 3243   
 3244   	public Comparator getVersionComparator() {
 3245   		return isVersioned() ? getVersionType().getComparator() : null;
 3246   	}
 3247   
 3248   	// temporary ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 3249   	public final String getEntityName() {
 3250   		return entityMetamodel.getName();
 3251   	}
 3252   
 3253   	public EntityType getEntityType() {
 3254   		return entityMetamodel.getEntityType();
 3255   	}
 3256   
 3257   	private String getSubclassEntityName(Class clazz) {
 3258   		return ( String ) entityNameBySubclass.get( clazz );
 3259   	}
 3260   
 3261   	public boolean isPolymorphic() {
 3262   		return entityMetamodel.isPolymorphic();
 3263   	}
 3264   
 3265   	public boolean isInherited() {
 3266   		return entityMetamodel.isInherited();
 3267   	}
 3268   
 3269   	public boolean hasCascades() {
 3270   		return entityMetamodel.hasCascades();
 3271   	}
 3272   
 3273   	public boolean hasIdentifierProperty() {
 3274   		return !entityMetamodel.getIdentifierProperty().isVirtual();
 3275   	}
 3276   
 3277   	public VersionType getVersionType() {
 3278   		return ( VersionType ) locateVersionType();
 3279   	}
 3280   
 3281   	private Type locateVersionType() {
 3282   		return entityMetamodel.getVersionProperty() == null ?
 3283   				null :
 3284   				entityMetamodel.getVersionProperty().getType();
 3285   	}
 3286   
 3287   	public int getVersionProperty() {
 3288   		return entityMetamodel.getVersionPropertyIndex();
 3289   	}
 3290   
 3291   	public boolean isVersioned() {
 3292   		return entityMetamodel.isVersioned();
 3293   	}
 3294   
 3295   	public boolean isIdentifierAssignedByInsert() {
 3296   		return entityMetamodel.getIdentifierProperty().isIdentifierAssignedByInsert();
 3297   	}
 3298   
 3299   	public boolean hasLazyProperties() {
 3300   		return entityMetamodel.hasLazyProperties();
 3301   	}
 3302   
 3303   //	public boolean hasUninitializedLazyProperties(Object entity) {
 3304   //		if ( hasLazyProperties() ) {
 3305   //			InterceptFieldCallback callback = ( ( InterceptFieldEnabled ) entity ).getInterceptFieldCallback();
 3306   //			return callback != null && !( ( FieldInterceptor ) callback ).isInitialized();
 3307   //		}
 3308   //		else {
 3309   //			return false;
 3310   //		}
 3311   //	}
 3312   
 3313   	public void afterReassociate(Object entity, SessionImplementor session) {
 3314   		//if ( hasLazyProperties() ) {
 3315   		if ( FieldInterceptionHelper.isInstrumented( entity ) ) {
 3316   			FieldInterceptor interceptor = FieldInterceptionHelper.extractFieldInterceptor( entity );
 3317   			if ( interceptor != null ) {
 3318   				interceptor.setSession( session );
 3319   			}
 3320   			else {
 3321   				FieldInterceptor fieldInterceptor = FieldInterceptionHelper.injectFieldInterceptor(
 3322   						entity,
 3323   						getEntityName(),
 3324   						null,
 3325   						session
 3326   				);
 3327   				fieldInterceptor.dirty();
 3328   			}
 3329   		}
 3330   	}
 3331   
 3332   	public Boolean isTransient(Object entity, SessionImplementor session) throws HibernateException {
 3333   		final Serializable id;
 3334   		if ( canExtractIdOutOfEntity() ) {
 3335   			id = getIdentifier( entity, session.getEntityMode() );
 3336   		}
 3337   		else {
 3338   			id = null;
 3339   		}
 3340   		// we *always* assume an instance with a null
 3341   		// identifier or no identifier property is unsaved!
 3342   		if ( id == null ) {
 3343   			return Boolean.TRUE;
 3344   		}
 3345   
 3346   		// check the version unsaved-value, if appropriate
 3347   		final Object version = getVersion( entity, session.getEntityMode() );
 3348   		if ( isVersioned() ) {
 3349   			// let this take precedence if defined, since it works for
 3350   			// assigned identifiers
 3351   			Boolean result = entityMetamodel.getVersionProperty()
 3352   					.getUnsavedValue().isUnsaved( version );
 3353   			if ( result != null ) {
 3354   				return result;
 3355   			}
 3356   		}
 3357   
 3358   		// check the id unsaved-value
 3359   		Boolean result = entityMetamodel.getIdentifierProperty()
 3360   				.getUnsavedValue().isUnsaved( id );
 3361   		if ( result != null ) {
 3362   			return result;
 3363   		}
 3364   
 3365   		// check to see if it is in the second-level cache
 3366   		if ( hasCache() ) {
 3367   			CacheKey ck = new CacheKey(
 3368   					id,
 3369   					getIdentifierType(),
 3370   					getRootEntityName(),
 3371   					session.getEntityMode(),
 3372   					session.getFactory()
 3373   				);
 3374   			if ( getCacheAccessStrategy().get( ck, session.getTimestamp() ) != null ) {
 3375   				return Boolean.FALSE;
 3376   			}
 3377   		}
 3378   
 3379   		return null;
 3380   	}
 3381   
 3382   	public boolean hasCollections() {
 3383   		return entityMetamodel.hasCollections();
 3384   	}
 3385   
 3386   	public boolean hasMutableProperties() {
 3387   		return entityMetamodel.hasMutableProperties();
 3388   	}
 3389   
 3390   	public boolean isMutable() {
 3391   		return entityMetamodel.isMutable();
 3392   	}
 3393   
 3394   	public boolean isAbstract() {
 3395   		return entityMetamodel.isAbstract();
 3396   	}
 3397   
 3398   	public boolean hasSubclasses() {
 3399   		return entityMetamodel.hasSubclasses();
 3400   	}
 3401   
 3402   	public boolean hasProxy() {
 3403   		return entityMetamodel.isLazy();
 3404   	}
 3405   
 3406   	public IdentifierGenerator getIdentifierGenerator() throws HibernateException {
 3407   		return entityMetamodel.getIdentifierProperty().getIdentifierGenerator();
 3408   	}
 3409   
 3410   	public String getRootEntityName() {
 3411   		return entityMetamodel.getRootName();
 3412   	}
 3413   
 3414   	public ClassMetadata getClassMetadata() {
 3415   		return this;
 3416   	}
 3417   
 3418   	public String getMappedSuperclass() {
 3419   		return entityMetamodel.getSuperclass();
 3420   	}
 3421   
 3422   	public boolean isExplicitPolymorphism() {
 3423   		return entityMetamodel.isExplicitPolymorphism();
 3424   	}
 3425   
 3426   	protected boolean useDynamicUpdate() {
 3427   		return entityMetamodel.isDynamicUpdate();
 3428   	}
 3429   
 3430   	protected boolean useDynamicInsert() {
 3431   		return entityMetamodel.isDynamicInsert();
 3432   	}
 3433   
 3434   	protected boolean hasEmbeddedCompositeIdentifier() {
 3435   		return entityMetamodel.getIdentifierProperty().isEmbedded();
 3436   	}
 3437   
 3438   	public boolean canExtractIdOutOfEntity() {
 3439   		return hasIdentifierProperty() || hasEmbeddedCompositeIdentifier() || hasIdentifierMapper();
 3440   	}
 3441   
 3442   	private boolean hasIdentifierMapper() {
 3443   		return entityMetamodel.getIdentifierProperty().hasIdentifierMapper();
 3444   	}
 3445   
 3446   	public String[] getKeyColumnNames() {
 3447   		return getIdentifierColumnNames();
 3448   	}
 3449   
 3450   	public String getName() {
 3451   		return getEntityName();
 3452   	}
 3453   
 3454   	public boolean isCollection() {
 3455   		return false;
 3456   	}
 3457   
 3458   	public boolean consumesEntityAlias() {
 3459   		return true;
 3460   	}
 3461   
 3462   	public boolean consumesCollectionAlias() {
 3463   		return false;
 3464   	}
 3465   
 3466   	public Type getPropertyType(String propertyName) throws MappingException {
 3467   		return propertyMapping.toType(propertyName);
 3468   	}
 3469   
 3470   	public Type getType() {
 3471   		return entityMetamodel.getEntityType();
 3472   	}
 3473   
 3474   	public boolean isSelectBeforeUpdateRequired() {
 3475   		return entityMetamodel.isSelectBeforeUpdate();
 3476   	}
 3477   
 3478   	protected final int optimisticLockMode() {
 3479   		return entityMetamodel.getOptimisticLockMode();
 3480   	}
 3481   
 3482   	public Object createProxy(Serializable id, SessionImplementor session) throws HibernateException {
 3483   		return entityMetamodel.getTuplizer( session.getEntityMode() )
 3484   				.createProxy( id, session );
 3485   	}
 3486   
 3487   	public String toString() {
 3488   		return StringHelper.unqualify( getClass().getName() ) +
 3489   				'(' + entityMetamodel.getName() + ')';
 3490   	}
 3491   
 3492   	public final String selectFragment(
 3493   			Joinable rhs,
 3494   			String rhsAlias,
 3495   			String lhsAlias,
 3496   			String entitySuffix,
 3497   			String collectionSuffix,
 3498   			boolean includeCollectionColumns) {
 3499   		return selectFragment( lhsAlias, entitySuffix );
 3500   	}
 3501   
 3502   	public boolean isInstrumented(EntityMode entityMode) {
 3503   		EntityTuplizer tuplizer = entityMetamodel.getTuplizerOrNull(entityMode);
 3504   		return tuplizer!=null && tuplizer.isInstrumented();
 3505   	}
 3506   
 3507   	public boolean hasInsertGeneratedProperties() {
 3508   		return entityMetamodel.hasInsertGeneratedValues();
 3509   	}
 3510   
 3511   	public boolean hasUpdateGeneratedProperties() {
 3512   		return entityMetamodel.hasUpdateGeneratedValues();
 3513   	}
 3514   
 3515   	public boolean isVersionPropertyGenerated() {
 3516   		return isVersioned() && ( getPropertyUpdateGenerationInclusions() [ getVersionProperty() ] != ValueInclusion.NONE );
 3517   	}
 3518   
 3519   	public boolean isVersionPropertyInsertable() {
 3520   		return isVersioned() && getPropertyInsertability() [ getVersionProperty() ];
 3521   	}
 3522   
 3523   	public void afterInitialize(Object entity, boolean lazyPropertiesAreUnfetched, SessionImplementor session) {
 3524   		getTuplizer( session ).afterInitialize( entity, lazyPropertiesAreUnfetched, session );
 3525   	}
 3526   
 3527   	public String[] getPropertyNames() {
 3528   		return entityMetamodel.getPropertyNames();
 3529   	}
 3530   
 3531   	public Type[] getPropertyTypes() {
 3532   		return entityMetamodel.getPropertyTypes();
 3533   	}
 3534   
 3535   	public boolean[] getPropertyLaziness() {
 3536   		return entityMetamodel.getPropertyLaziness();
 3537   	}
 3538   
 3539   	public boolean[] getPropertyUpdateability() {
 3540   		return entityMetamodel.getPropertyUpdateability();
 3541   	}
 3542   
 3543   	public boolean[] getPropertyCheckability() {
 3544   		return entityMetamodel.getPropertyCheckability();
 3545   	}
 3546   
 3547   	public boolean[] getNonLazyPropertyUpdateability() {
 3548   		return entityMetamodel.getNonlazyPropertyUpdateability();
 3549   	}
 3550   
 3551   	public boolean[] getPropertyInsertability() {
 3552   		return entityMetamodel.getPropertyInsertability();
 3553   	}
 3554   
 3555   	public ValueInclusion[] getPropertyInsertGenerationInclusions() {
 3556   		return entityMetamodel.getPropertyInsertGenerationInclusions();
 3557   	}
 3558   
 3559   	public ValueInclusion[] getPropertyUpdateGenerationInclusions() {
 3560   		return entityMetamodel.getPropertyUpdateGenerationInclusions();
 3561   	}
 3562   
 3563   	public boolean[] getPropertyNullability() {
 3564   		return entityMetamodel.getPropertyNullability();
 3565   	}
 3566   
 3567   	public boolean[] getPropertyVersionability() {
 3568   		return entityMetamodel.getPropertyVersionability();
 3569   	}
 3570   
 3571   	public CascadeStyle[] getPropertyCascadeStyles() {
 3572   		return entityMetamodel.getCascadeStyles();
 3573   	}
 3574   
 3575   	public final Class getMappedClass(EntityMode entityMode) {
 3576   		Tuplizer tup = entityMetamodel.getTuplizerOrNull(entityMode);
 3577   		return tup==null ? null : tup.getMappedClass();
 3578   	}
 3579   
 3580   	public boolean implementsLifecycle(EntityMode entityMode) {
 3581   		return getTuplizer( entityMode ).isLifecycleImplementor();
 3582   	}
 3583   
 3584   	public boolean implementsValidatable(EntityMode entityMode) {
 3585   		return getTuplizer( entityMode ).isValidatableImplementor();
 3586   	}
 3587   
 3588   	public Class getConcreteProxyClass(EntityMode entityMode) {
 3589   		return getTuplizer( entityMode ).getConcreteProxyClass();
 3590   	}
 3591   
 3592   	public void setPropertyValues(Object object, Object[] values, EntityMode entityMode)
 3593   			throws HibernateException {
 3594   		getTuplizer( entityMode ).setPropertyValues( object, values );
 3595   	}
 3596   
 3597   	public void setPropertyValue(Object object, int i, Object value, EntityMode entityMode)
 3598   			throws HibernateException {
 3599   		getTuplizer( entityMode ).setPropertyValue( object, i, value );
 3600   	}
 3601   
 3602   	public Object[] getPropertyValues(Object object, EntityMode entityMode)
 3603   			throws HibernateException {
 3604   		return getTuplizer( entityMode ).getPropertyValues( object );
 3605   	}
 3606   
 3607   	public Object getPropertyValue(Object object, int i, EntityMode entityMode)
 3608   			throws HibernateException {
 3609   		return getTuplizer( entityMode ).getPropertyValue( object , i );
 3610   	}
 3611   
 3612   	public Object getPropertyValue(Object object, String propertyName, EntityMode entityMode)
 3613   			throws HibernateException {
 3614   		return getTuplizer( entityMode ).getPropertyValue( object, propertyName );
 3615   	}
 3616   
 3617   	public Serializable getIdentifier(Object object, EntityMode entityMode)
 3618   			throws HibernateException {
 3619   		return getTuplizer( entityMode ).getIdentifier( object );
 3620   	}
 3621   
 3622   	public void setIdentifier(Object object, Serializable id, EntityMode entityMode)
 3623   			throws HibernateException {
 3624   		getTuplizer( entityMode ).setIdentifier( object, id );
 3625   	}
 3626   
 3627   	public Object getVersion(Object object, EntityMode entityMode)
 3628   			throws HibernateException {
 3629   		return getTuplizer( entityMode ).getVersion( object );
 3630   	}
 3631   
 3632   	public Object instantiate(Serializable id, EntityMode entityMode)
 3633   			throws HibernateException {
 3634   		return getTuplizer( entityMode ).instantiate( id );
 3635   	}
 3636   
 3637   	public boolean isInstance(Object object, EntityMode entityMode) {
 3638   		return getTuplizer( entityMode ).isInstance( object );
 3639   	}
 3640   
 3641   	public boolean hasUninitializedLazyProperties(Object object, EntityMode entityMode) {
 3642   		return getTuplizer( entityMode ).hasUninitializedLazyProperties( object );
 3643   	}
 3644   
 3645   	public void resetIdentifier(Object entity, Serializable currentId, Object currentVersion, EntityMode entityMode) {
 3646   		getTuplizer( entityMode ).resetIdentifier( entity, currentId, currentVersion );
 3647   	}
 3648   
 3649   	public EntityPersister getSubclassEntityPersister(Object instance, SessionFactoryImplementor factory, EntityMode entityMode) {
 3650   		if ( !hasSubclasses() ) {
 3651   			return this;
 3652   		}
 3653   		else {
 3654   			// TODO : really need a way to do something like :
 3655   			//      getTuplizer(entityMode).determineConcreteSubclassEntityName(instance)
 3656   			Class clazz = instance.getClass();
 3657   			if ( clazz == getMappedClass( entityMode ) ) {
 3658   				return this;
 3659   			}
 3660   			else {
 3661   				String subclassEntityName = getSubclassEntityName( clazz );
 3662   				if ( subclassEntityName == null ) {
 3663   					throw new HibernateException(
 3664   							"instance not of expected entity type: " + clazz.getName() +
 3665   							" is not a: " + getEntityName()
 3666   						);
 3667   				}
 3668   				else {
 3669   					return factory.getEntityPersister( subclassEntityName );
 3670   				}
 3671   			}
 3672   		}
 3673   	}
 3674   
 3675   	public EntityMode guessEntityMode(Object object) {
 3676   		return entityMetamodel.guessEntityMode(object);
 3677   	}
 3678   
 3679   	public boolean isMultiTable() {
 3680   		return false;
 3681   	}
 3682   
 3683   	public String getTemporaryIdTableName() {
 3684   		return temporaryIdTableName;
 3685   	}
 3686   
 3687   	public String getTemporaryIdTableDDL() {
 3688   		return temporaryIdTableDDL;
 3689   	}
 3690   
 3691   	protected int getPropertySpan() {
 3692   		return entityMetamodel.getPropertySpan();
 3693   	}
 3694   
 3695   	public Object[] getPropertyValuesToInsert(Object object, Map mergeMap, SessionImplementor session) throws HibernateException {
 3696   		return getTuplizer( session.getEntityMode() ).getPropertyValuesToInsert( object, mergeMap, session );
 3697   	}
 3698   
 3699   	public void processInsertGeneratedProperties(Serializable id, Object entity, Object[] state, SessionImplementor session) {
 3700   		if ( !hasInsertGeneratedProperties() ) {
 3701   			throw new AssertionFailure("no insert-generated properties");
 3702   		}
 3703   		processGeneratedProperties( id, entity, state, session, sqlInsertGeneratedValuesSelectString, getPropertyInsertGenerationInclusions() );
 3704   	}
 3705   
 3706   	public void processUpdateGeneratedProperties(Serializable id, Object entity, Object[] state, SessionImplementor session) {
 3707   		if ( !hasUpdateGeneratedProperties() ) {
 3708   			throw new AssertionFailure("no update-generated properties");
 3709   		}
 3710   		processGeneratedProperties( id, entity, state, session, sqlUpdateGeneratedValuesSelectString, getPropertyUpdateGenerationInclusions() );
 3711   	}
 3712   
 3713   	private void processGeneratedProperties(
 3714   			Serializable id,
 3715   	        Object entity,
 3716   	        Object[] state,
 3717   	        SessionImplementor session,
 3718   	        String selectionSQL,
 3719   	        ValueInclusion[] includeds) {
 3720   
 3721   		session.getBatcher().executeBatch(); //force immediate execution of the insert
 3722   
 3723   		try {
 3724   			PreparedStatement ps = session.getBatcher().prepareSelectStatement( selectionSQL );
 3725   			try {
 3726   				getIdentifierType().nullSafeSet( ps, id, 1, session );
 3727   				ResultSet rs = ps.executeQuery();
 3728   				try {
 3729   					if ( !rs.next() ) {
 3730   						throw new HibernateException(
 3731   								"Unable to locate row for retrieval of generated properties: " +
 3732   								MessageHelper.infoString( this, id, getFactory() )
 3733   							);
 3734   					}
 3735   					for ( int i = 0; i < getPropertySpan(); i++ ) {
 3736   						if ( includeds[i] != ValueInclusion.NONE ) {
 3737   							Object hydratedState = getPropertyTypes()[i].hydrate( rs, getPropertyAliases( "", i ), session, entity );
 3738   							state[i] = getPropertyTypes()[i].resolve( hydratedState, session, entity );
 3739   							setPropertyValue( entity, i, state[i], session.getEntityMode() );
 3740   						}
 3741   					}
 3742   				}
 3743   				finally {
 3744   					if ( rs != null ) {
 3745   						rs.close();
 3746   					}
 3747   				}
 3748   			}
 3749   			finally {
 3750   				session.getBatcher().closeStatement( ps );
 3751   			}
 3752   		}
 3753   		catch( SQLException sqle ) {
 3754   			throw JDBCExceptionHelper.convert(
 3755   					getFactory().getSQLExceptionConverter(),
 3756   					sqle,
 3757   					"unable to select generated column values",
 3758   					selectionSQL
 3759   			);
 3760   		}
 3761   
 3762   	}
 3763   
 3764   	public String getIdentifierPropertyName() {
 3765   		return entityMetamodel.getIdentifierProperty().getName();
 3766   	}
 3767   
 3768   	public Type getIdentifierType() {
 3769   		return entityMetamodel.getIdentifierProperty().getType();
 3770   	}
 3771   
 3772   	public boolean hasSubselectLoadableCollections() {
 3773   		return hasSubselectLoadableCollections;
 3774   	}
 3775   
 3776   	public int[] getNaturalIdentifierProperties() {
 3777   		return entityMetamodel.getNaturalIdentifierProperties();
 3778   	}
 3779   
 3780   	public Object[] getNaturalIdentifierSnapshot(Serializable id, SessionImplementor session) throws HibernateException {
 3781   		if ( !hasNaturalIdentifier() ) {
 3782   			throw new MappingException( "persistent class did not define a natural-id : " + MessageHelper.infoString( this ) );
 3783   		}
 3784   		if ( log.isTraceEnabled() ) {
 3785   			log.trace( "Getting current natural-id snapshot state for: " + MessageHelper.infoString( this, id, getFactory() ) );
 3786   		}
 3787   
 3788   		int[] naturalIdPropertyIndexes = getNaturalIdentifierProperties();
 3789   		int naturalIdPropertyCount = naturalIdPropertyIndexes.length;
 3790   		boolean[] naturalIdMarkers = new boolean[ getPropertySpan() ];
 3791   		Type[] extractionTypes = new Type[ naturalIdPropertyCount ];
 3792   		for ( int i = 0; i < naturalIdPropertyCount; i++ ) {
 3793   			extractionTypes[i] = getPropertyTypes()[ naturalIdPropertyIndexes[i] ];
 3794   			naturalIdMarkers[ naturalIdPropertyIndexes[i] ] = true;
 3795   		}
 3796   
 3797   		///////////////////////////////////////////////////////////////////////
 3798   		// TODO : look at perhaps caching this...
 3799   		Select select = new Select( getFactory().getDialect() );
 3800   		if ( getFactory().getSettings().isCommentsEnabled() ) {
 3801   			select.setComment( "get current natural-id state " + getEntityName() );
 3802   		}
 3803   		select.setSelectClause( concretePropertySelectFragmentSansLeadingComma( getRootAlias(), naturalIdMarkers ) );
 3804   		select.setFromClause( fromTableFragment( getRootAlias() ) + fromJoinFragment( getRootAlias(), true, false ) );
 3805   
 3806   		String[] aliasedIdColumns = StringHelper.qualify( getRootAlias(), getIdentifierColumnNames() );
 3807   		String whereClause = new StringBuffer()
 3808   			.append( StringHelper.join( "=? and ",
 3809   					aliasedIdColumns ) )
 3810   			.append( "=?" )
 3811   			.append( whereJoinFragment( getRootAlias(), true, false ) )
 3812   			.toString();
 3813   
 3814   		String sql = select.setOuterJoins( "", "" )
 3815   				.setWhereClause( whereClause )
 3816   				.toStatementString();
 3817   		///////////////////////////////////////////////////////////////////////
 3818   
 3819   		Object[] snapshot = new Object[ naturalIdPropertyCount ];
 3820   		try {
 3821   			PreparedStatement ps = session.getBatcher().prepareSelectStatement( sql );
 3822   			try {
 3823   				getIdentifierType().nullSafeSet( ps, id, 1, session );
 3824   				ResultSet rs = ps.executeQuery();
 3825   				try {
 3826   					//if there is no resulting row, return null
 3827   					if ( !rs.next() ) {
 3828   						return null;
 3829   					}
 3830   
 3831   					for ( int i = 0; i < naturalIdPropertyCount; i++ ) {
 3832   						snapshot[i] = extractionTypes[i].hydrate( rs, getPropertyAliases( "", naturalIdPropertyIndexes[i] ), session, null );
 3833   					}
 3834   					return snapshot;
 3835   				}
 3836   				finally {
 3837   					rs.close();
 3838   				}
 3839   			}
 3840   			finally {
 3841   				session.getBatcher().closeStatement( ps );
 3842   			}
 3843   		}
 3844   		catch ( SQLException sqle ) {
 3845   			throw JDBCExceptionHelper.convert(
 3846   					getFactory().getSQLExceptionConverter(),
 3847   					sqle,
 3848   					"could not retrieve snapshot: " +
 3849   					MessageHelper.infoString( this, id, getFactory() ),
 3850   			        sql
 3851   				);
 3852   		}
 3853   	}
 3854   
 3855   	protected String concretePropertySelectFragmentSansLeadingComma(String alias, boolean[] include) {
 3856   		String concretePropertySelectFragment = concretePropertySelectFragment( alias, include );
 3857   		int firstComma = concretePropertySelectFragment.indexOf( ", " );
 3858   		if ( firstComma == 0 ) {
 3859   			concretePropertySelectFragment = concretePropertySelectFragment.substring( 2 );
 3860   		}
 3861   		return concretePropertySelectFragment;
 3862   	}
 3863   	public boolean hasNaturalIdentifier() {
 3864   		return entityMetamodel.hasNaturalIdentifier();
 3865   	}
 3866   
 3867   	public void setPropertyValue(Object object, String propertyName, Object value, EntityMode entityMode)
 3868   			throws HibernateException {
 3869   		getTuplizer( entityMode ).setPropertyValue( object, propertyName, value );
 3870   	}
 3871   	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 3872   
 3873   }

Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » persister » entity » [javadoc | source]