Java tutorial
/* * Hibernate, Relational Persistence for Idiomatic Java * * License: GNU Lesser General Public License (LGPL), version 2.1 or later. * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>. */ package org.hibernate.persister.collection; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Set; import org.hibernate.AssertionFailure; import org.hibernate.FetchMode; import org.hibernate.Filter; import org.hibernate.HibernateException; import org.hibernate.MappingException; import org.hibernate.QueryException; import org.hibernate.TransientObjectException; import org.hibernate.boot.model.relational.Database; import org.hibernate.cache.CacheException; import org.hibernate.cache.spi.access.CollectionDataAccess; import org.hibernate.cache.spi.entry.CacheEntryStructure; import org.hibernate.cache.spi.entry.StructuredCollectionCacheEntry; import org.hibernate.cache.spi.entry.StructuredMapCacheEntry; import org.hibernate.cache.spi.entry.UnstructuredCacheEntry; import org.hibernate.collection.spi.PersistentCollection; import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey; import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.spi.EntityKey; import org.hibernate.engine.spi.ExecuteUpdateResultCheckStyle; import org.hibernate.engine.spi.LoadQueryInfluencers; import org.hibernate.engine.spi.PersistenceContext; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SubselectFetch; import org.hibernate.exception.spi.SQLExceptionConverter; import org.hibernate.id.IdentifierGenerator; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.FilterAliasGenerator; import org.hibernate.internal.FilterHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.jdbc.Expectation; import org.hibernate.jdbc.Expectations; import org.hibernate.loader.collection.CollectionInitializer; import org.hibernate.mapping.Collection; import org.hibernate.mapping.Column; import org.hibernate.mapping.Formula; import org.hibernate.mapping.IdentifierCollection; import org.hibernate.mapping.IndexedCollection; import org.hibernate.mapping.List; import org.hibernate.mapping.Selectable; import org.hibernate.mapping.Table; import org.hibernate.metadata.CollectionMetadata; import org.hibernate.metamodel.model.domain.NavigableRole; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.Loadable; import org.hibernate.persister.entity.PropertyMapping; import org.hibernate.persister.entity.Queryable; import org.hibernate.persister.spi.PersisterCreationContext; import org.hibernate.persister.walking.internal.CompositionSingularSubAttributesHelper; import org.hibernate.persister.walking.internal.StandardAnyTypeDefinition; import org.hibernate.persister.walking.spi.AnyMappingDefinition; import org.hibernate.persister.walking.spi.AttributeDefinition; import org.hibernate.persister.walking.spi.AttributeSource; import org.hibernate.persister.walking.spi.CollectionDefinition; import org.hibernate.persister.walking.spi.CollectionElementDefinition; import org.hibernate.persister.walking.spi.CollectionIndexDefinition; import org.hibernate.persister.walking.spi.CompositeCollectionElementDefinition; import org.hibernate.persister.walking.spi.CompositionDefinition; import org.hibernate.persister.walking.spi.EntityDefinition; import org.hibernate.pretty.MessageHelper; import org.hibernate.sql.Alias; import org.hibernate.sql.SelectFragment; import org.hibernate.sql.SimpleSelect; import org.hibernate.sql.Template; import org.hibernate.sql.ordering.antlr.ColumnMapper; import org.hibernate.sql.ordering.antlr.ColumnReference; import org.hibernate.sql.ordering.antlr.FormulaReference; import org.hibernate.sql.ordering.antlr.OrderByAliasResolver; import org.hibernate.sql.ordering.antlr.OrderByTranslation; import org.hibernate.sql.ordering.antlr.SqlValueReference; import org.hibernate.type.AnyType; import org.hibernate.type.AssociationType; import org.hibernate.type.CollectionType; import org.hibernate.type.CompositeType; import org.hibernate.type.EntityType; import org.hibernate.type.Type; import org.jboss.logging.Logger; /** * Base implementation of the <tt>QueryableCollection</tt> interface. * * @author Gavin King * @see BasicCollectionPersister * @see OneToManyPersister */ public abstract class AbstractCollectionPersister implements CollectionMetadata, SQLLoadableCollection { private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, AbstractCollectionPersister.class.getName()); // TODO: encapsulate the protected instance variables! private final NavigableRole navigableRole; // SQL statements private final String sqlDeleteString; private final String sqlInsertRowString; private final String sqlUpdateRowString; private final String sqlDeleteRowString; private final String sqlSelectSizeString; private final String sqlSelectRowByIndexString; private final String sqlDetectRowByIndexString; private final String sqlDetectRowByElementString; protected final boolean hasWhere; protected final String sqlWhereString; private final String sqlWhereStringTemplate; private final boolean hasOrder; private final OrderByTranslation orderByTranslation; private final boolean hasManyToManyOrder; private final OrderByTranslation manyToManyOrderByTranslation; private final int baseIndex; private String mappedByProperty; protected final boolean indexContainsFormula; protected final boolean elementIsPureFormula; // types private final Type keyType; private final Type indexType; protected final Type elementType; private final Type identifierType; // columns protected final String[] keyColumnNames; protected final String[] indexColumnNames; protected final String[] indexFormulaTemplates; protected final String[] indexFormulas; protected final boolean[] indexColumnIsGettable; protected final boolean[] indexColumnIsSettable; protected final String[] elementColumnNames; protected final String[] elementColumnWriters; protected final String[] elementColumnReaders; protected final String[] elementColumnReaderTemplates; protected final String[] elementFormulaTemplates; protected final String[] elementFormulas; protected final boolean[] elementColumnIsGettable; protected final boolean[] elementColumnIsSettable; protected final boolean[] elementColumnIsInPrimaryKey; protected final String[] indexColumnAliases; protected final String[] elementColumnAliases; protected final String[] keyColumnAliases; protected final String identifierColumnName; private final String identifierColumnAlias; // private final String unquotedIdentifierColumnName; protected final String qualifiedTableName; private final String queryLoaderName; private final boolean isPrimitiveArray; private final boolean isArray; protected final boolean hasIndex; protected final boolean hasIdentifier; private final boolean isLazy; private final boolean isExtraLazy; protected final boolean isInverse; private final boolean isMutable; private final boolean isVersioned; protected final int batchSize; private final FetchMode fetchMode; private final boolean hasOrphanDelete; private final boolean subselectLoadable; // extra information about the element type private final Class elementClass; private final String entityName; private final Dialect dialect; protected final SqlExceptionHelper sqlExceptionHelper; private final SessionFactoryImplementor factory; private final EntityPersister ownerPersister; private final IdentifierGenerator identifierGenerator; private final PropertyMapping elementPropertyMapping; private final EntityPersister elementPersister; private final CollectionDataAccess cacheAccessStrategy; private final CollectionType collectionType; private CollectionInitializer initializer; private final CacheEntryStructure cacheEntryStructure; // dynamic filters for the collection private final FilterHelper filterHelper; // dynamic filters specifically for many-to-many inside the collection private final FilterHelper manyToManyFilterHelper; private final String manyToManyWhereString; private final String manyToManyWhereTemplate; // custom sql private final boolean insertCallable; private final boolean updateCallable; private final boolean deleteCallable; private final boolean deleteAllCallable; private ExecuteUpdateResultCheckStyle insertCheckStyle; private ExecuteUpdateResultCheckStyle updateCheckStyle; private ExecuteUpdateResultCheckStyle deleteCheckStyle; private ExecuteUpdateResultCheckStyle deleteAllCheckStyle; private final Serializable[] spaces; private Map collectionPropertyColumnAliases = new HashMap(); public AbstractCollectionPersister(Collection collectionBinding, CollectionDataAccess cacheAccessStrategy, PersisterCreationContext creationContext) throws MappingException, CacheException { final Database database = creationContext.getMetadata().getDatabase(); final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment(); this.factory = creationContext.getSessionFactory(); this.cacheAccessStrategy = cacheAccessStrategy; if (factory.getSessionFactoryOptions().isStructuredCacheEntriesEnabled()) { cacheEntryStructure = collectionBinding.isMap() ? StructuredMapCacheEntry.INSTANCE : StructuredCollectionCacheEntry.INSTANCE; } else { cacheEntryStructure = UnstructuredCacheEntry.INSTANCE; } dialect = factory.getDialect(); sqlExceptionHelper = factory.getSQLExceptionHelper(); collectionType = collectionBinding.getCollectionType(); navigableRole = new NavigableRole(collectionBinding.getRole()); entityName = collectionBinding.getOwnerEntityName(); ownerPersister = factory.getEntityPersister(entityName); queryLoaderName = collectionBinding.getLoaderName(); isMutable = collectionBinding.isMutable(); mappedByProperty = collectionBinding.getMappedByProperty(); Table table = collectionBinding.getCollectionTable(); fetchMode = collectionBinding.getElement().getFetchMode(); elementType = collectionBinding.getElement().getType(); // isSet = collectionBinding.isSet(); // isSorted = collectionBinding.isSorted(); isPrimitiveArray = collectionBinding.isPrimitiveArray(); isArray = collectionBinding.isArray(); subselectLoadable = collectionBinding.isSubselectLoadable(); qualifiedTableName = determineTableName(table, jdbcEnvironment); int spacesSize = 1 + collectionBinding.getSynchronizedTables().size(); spaces = new String[spacesSize]; spaces[0] = qualifiedTableName; Iterator iter = collectionBinding.getSynchronizedTables().iterator(); for (int i = 1; i < spacesSize; i++) { spaces[i] = (String) iter.next(); } sqlWhereString = StringHelper.isNotEmpty(collectionBinding.getWhere()) ? "( " + collectionBinding.getWhere() + ") " : null; hasWhere = sqlWhereString != null; sqlWhereStringTemplate = hasWhere ? Template.renderWhereStringTemplate(sqlWhereString, dialect, factory.getSqlFunctionRegistry()) : null; hasOrphanDelete = collectionBinding.hasOrphanDelete(); int batch = collectionBinding.getBatchSize(); if (batch == -1) { batch = factory.getSessionFactoryOptions().getDefaultBatchFetchSize(); } batchSize = batch; isVersioned = collectionBinding.isOptimisticLocked(); // KEY keyType = collectionBinding.getKey().getType(); iter = collectionBinding.getKey().getColumnIterator(); int keySpan = collectionBinding.getKey().getColumnSpan(); keyColumnNames = new String[keySpan]; keyColumnAliases = new String[keySpan]; int k = 0; while (iter.hasNext()) { // NativeSQL: collect key column and auto-aliases Column col = ((Column) iter.next()); keyColumnNames[k] = col.getQuotedName(dialect); keyColumnAliases[k] = col.getAlias(dialect, table); k++; } // unquotedKeyColumnNames = StringHelper.unQuote(keyColumnAliases); // ELEMENT if (elementType.isEntityType()) { String entityName = ((EntityType) elementType).getAssociatedEntityName(); elementPersister = factory.getEntityPersister(entityName); // NativeSQL: collect element column and auto-aliases } else { elementPersister = null; } int elementSpan = collectionBinding.getElement().getColumnSpan(); elementColumnAliases = new String[elementSpan]; elementColumnNames = new String[elementSpan]; elementColumnWriters = new String[elementSpan]; elementColumnReaders = new String[elementSpan]; elementColumnReaderTemplates = new String[elementSpan]; elementFormulaTemplates = new String[elementSpan]; elementFormulas = new String[elementSpan]; elementColumnIsSettable = new boolean[elementSpan]; elementColumnIsGettable = new boolean[elementSpan]; elementColumnIsInPrimaryKey = new boolean[elementSpan]; boolean isPureFormula = true; boolean hasNotNullableColumns = false; boolean oneToMany = collectionBinding.isOneToMany(); boolean[] columnInsertability = null; if (!oneToMany) { columnInsertability = collectionBinding.getElement().getColumnInsertability(); } int j = 0; iter = collectionBinding.getElement().getColumnIterator(); while (iter.hasNext()) { Selectable selectable = (Selectable) iter.next(); elementColumnAliases[j] = selectable.getAlias(dialect, table); if (selectable.isFormula()) { Formula form = (Formula) selectable; elementFormulaTemplates[j] = form.getTemplate(dialect, factory.getSqlFunctionRegistry()); elementFormulas[j] = form.getFormula(); } else { Column col = (Column) selectable; elementColumnNames[j] = col.getQuotedName(dialect); elementColumnWriters[j] = col.getWriteExpr(); elementColumnReaders[j] = col.getReadExpr(dialect); elementColumnReaderTemplates[j] = col.getTemplate(dialect, factory.getSqlFunctionRegistry()); elementColumnIsGettable[j] = true; if (elementType.isComponentType()) { // Implements desired behavior specifically for @ElementCollection mappings. elementColumnIsSettable[j] = columnInsertability[j]; } else { // Preserves legacy non-@ElementCollection behavior elementColumnIsSettable[j] = true; } elementColumnIsInPrimaryKey[j] = !col.isNullable(); if (!col.isNullable()) { hasNotNullableColumns = true; } isPureFormula = false; } j++; } elementIsPureFormula = isPureFormula; // workaround, for backward compatibility of sets with no // not-null columns, assume all columns are used in the // row locator SQL if (!hasNotNullableColumns) { Arrays.fill(elementColumnIsInPrimaryKey, true); } // INDEX AND ROW SELECT hasIndex = collectionBinding.isIndexed(); if (hasIndex) { // NativeSQL: collect index column and auto-aliases IndexedCollection indexedCollection = (IndexedCollection) collectionBinding; indexType = indexedCollection.getIndex().getType(); int indexSpan = indexedCollection.getIndex().getColumnSpan(); boolean[] indexColumnInsertability = indexedCollection.getIndex().getColumnInsertability(); boolean[] indexColumnUpdatability = indexedCollection.getIndex().getColumnUpdateability(); iter = indexedCollection.getIndex().getColumnIterator(); indexColumnNames = new String[indexSpan]; indexFormulaTemplates = new String[indexSpan]; indexFormulas = new String[indexSpan]; indexColumnIsGettable = new boolean[indexSpan]; indexColumnIsSettable = new boolean[indexSpan]; indexColumnAliases = new String[indexSpan]; int i = 0; boolean hasFormula = false; while (iter.hasNext()) { Selectable s = (Selectable) iter.next(); indexColumnAliases[i] = s.getAlias(dialect); if (s.isFormula()) { Formula indexForm = (Formula) s; indexFormulaTemplates[i] = indexForm.getTemplate(dialect, factory.getSqlFunctionRegistry()); indexFormulas[i] = indexForm.getFormula(); hasFormula = true; } else { Column indexCol = (Column) s; indexColumnNames[i] = indexCol.getQuotedName(dialect); indexColumnIsGettable[i] = true; indexColumnIsSettable[i] = indexColumnInsertability[i] || indexColumnUpdatability[i]; } i++; } indexContainsFormula = hasFormula; baseIndex = indexedCollection.isList() ? ((List) indexedCollection).getBaseIndex() : 0; } else { indexContainsFormula = false; indexColumnIsGettable = null; indexColumnIsSettable = null; indexFormulaTemplates = null; indexFormulas = null; indexType = null; indexColumnNames = null; indexColumnAliases = null; baseIndex = 0; } hasIdentifier = collectionBinding.isIdentified(); if (hasIdentifier) { if (collectionBinding.isOneToMany()) { throw new MappingException("one-to-many collections with identifiers are not supported"); } IdentifierCollection idColl = (IdentifierCollection) collectionBinding; identifierType = idColl.getIdentifier().getType(); iter = idColl.getIdentifier().getColumnIterator(); Column col = (Column) iter.next(); identifierColumnName = col.getQuotedName(dialect); identifierColumnAlias = col.getAlias(dialect); // unquotedIdentifierColumnName = identifierColumnAlias; identifierGenerator = idColl.getIdentifier().createIdentifierGenerator( creationContext.getMetadata().getIdentifierGeneratorFactory(), factory.getDialect(), factory.getSettings().getDefaultCatalogName(), factory.getSettings().getDefaultSchemaName(), null); } else { identifierType = null; identifierColumnName = null; identifierColumnAlias = null; // unquotedIdentifierColumnName = null; identifierGenerator = null; } // GENERATE THE SQL: // sqlSelectString = sqlSelectString(); // sqlSelectRowString = sqlSelectRowString(); if (collectionBinding.getCustomSQLInsert() == null) { sqlInsertRowString = generateInsertRowString(); insertCallable = false; insertCheckStyle = ExecuteUpdateResultCheckStyle.COUNT; } else { sqlInsertRowString = collectionBinding.getCustomSQLInsert(); insertCallable = collectionBinding.isCustomInsertCallable(); insertCheckStyle = collectionBinding.getCustomSQLInsertCheckStyle() == null ? ExecuteUpdateResultCheckStyle.determineDefault(collectionBinding.getCustomSQLInsert(), insertCallable) : collectionBinding.getCustomSQLInsertCheckStyle(); } if (collectionBinding.getCustomSQLUpdate() == null) { sqlUpdateRowString = generateUpdateRowString(); updateCallable = false; updateCheckStyle = ExecuteUpdateResultCheckStyle.COUNT; } else { sqlUpdateRowString = collectionBinding.getCustomSQLUpdate(); updateCallable = collectionBinding.isCustomUpdateCallable(); updateCheckStyle = collectionBinding.getCustomSQLUpdateCheckStyle() == null ? ExecuteUpdateResultCheckStyle.determineDefault(collectionBinding.getCustomSQLUpdate(), insertCallable) : collectionBinding.getCustomSQLUpdateCheckStyle(); } if (collectionBinding.getCustomSQLDelete() == null) { sqlDeleteRowString = generateDeleteRowString(); deleteCallable = false; deleteCheckStyle = ExecuteUpdateResultCheckStyle.NONE; } else { sqlDeleteRowString = collectionBinding.getCustomSQLDelete(); deleteCallable = collectionBinding.isCustomDeleteCallable(); deleteCheckStyle = ExecuteUpdateResultCheckStyle.NONE; } if (collectionBinding.getCustomSQLDeleteAll() == null) { sqlDeleteString = generateDeleteString(); deleteAllCallable = false; deleteAllCheckStyle = ExecuteUpdateResultCheckStyle.NONE; } else { sqlDeleteString = collectionBinding.getCustomSQLDeleteAll(); deleteAllCallable = collectionBinding.isCustomDeleteAllCallable(); deleteAllCheckStyle = ExecuteUpdateResultCheckStyle.NONE; } sqlSelectSizeString = generateSelectSizeString(collectionBinding.isIndexed() && !collectionBinding.isMap()); sqlDetectRowByIndexString = generateDetectRowByIndexString(); sqlDetectRowByElementString = generateDetectRowByElementString(); sqlSelectRowByIndexString = generateSelectRowByIndexString(); logStaticSQL(); isLazy = collectionBinding.isLazy(); isExtraLazy = collectionBinding.isExtraLazy(); isInverse = collectionBinding.isInverse(); if (collectionBinding.isArray()) { elementClass = ((org.hibernate.mapping.Array) collectionBinding).getElementClass(); } else { // for non-arrays, we don't need to know the element class elementClass = null; // elementType.returnedClass(); } if (elementType.isComponentType()) { elementPropertyMapping = new CompositeElementPropertyMapping(elementColumnNames, elementColumnReaders, elementColumnReaderTemplates, elementFormulaTemplates, (CompositeType) elementType, factory); } else if (!elementType.isEntityType()) { elementPropertyMapping = new ElementPropertyMapping(elementColumnNames, elementType); } else { if (elementPersister instanceof PropertyMapping) { // not all classpersisters implement PropertyMapping! elementPropertyMapping = (PropertyMapping) elementPersister; } else { elementPropertyMapping = new ElementPropertyMapping(elementColumnNames, elementType); } } hasOrder = collectionBinding.getOrderBy() != null; if (hasOrder) { LOG.debugf("Translating order-by fragment [%s] for collection role : %s", collectionBinding.getOrderBy(), getRole()); orderByTranslation = Template.translateOrderBy(collectionBinding.getOrderBy(), new ColumnMapperImpl(), factory, dialect, factory.getSqlFunctionRegistry()); } else { orderByTranslation = null; } // Handle any filters applied to this collectionBinding filterHelper = new FilterHelper(collectionBinding.getFilters(), factory); // Handle any filters applied to this collectionBinding for many-to-many manyToManyFilterHelper = new FilterHelper(collectionBinding.getManyToManyFilters(), factory); manyToManyWhereString = StringHelper.isNotEmpty(collectionBinding.getManyToManyWhere()) ? "( " + collectionBinding.getManyToManyWhere() + ")" : null; manyToManyWhereTemplate = manyToManyWhereString == null ? null : Template.renderWhereStringTemplate(manyToManyWhereString, factory.getDialect(), factory.getSqlFunctionRegistry()); hasManyToManyOrder = collectionBinding.getManyToManyOrdering() != null; if (hasManyToManyOrder) { LOG.debugf("Translating many-to-many order-by fragment [%s] for collection role : %s", collectionBinding.getOrderBy(), getRole()); manyToManyOrderByTranslation = Template.translateOrderBy(collectionBinding.getManyToManyOrdering(), new ColumnMapperImpl(), factory, dialect, factory.getSqlFunctionRegistry()); } else { manyToManyOrderByTranslation = null; } initCollectionPropertyMap(); } protected String determineTableName(Table table, JdbcEnvironment jdbcEnvironment) { if (table.getSubselect() != null) { return "( " + table.getSubselect() + " )"; } return jdbcEnvironment.getQualifiedObjectNameFormatter().format(table.getQualifiedTableName(), jdbcEnvironment.getDialect()); } private class ColumnMapperImpl implements ColumnMapper { @Override public SqlValueReference[] map(String reference) { final String[] columnNames; final String[] formulaTemplates; // handle the special "$element$" property name... if ("$element$".equals(reference)) { columnNames = elementColumnNames; formulaTemplates = elementFormulaTemplates; } else { columnNames = elementPropertyMapping.toColumns(reference); formulaTemplates = formulaTemplates(reference, columnNames.length); } final SqlValueReference[] result = new SqlValueReference[columnNames.length]; int i = 0; for (final String columnName : columnNames) { if (columnName == null) { // if the column name is null, it indicates that this index in the property value mapping is // actually represented by a formula. // final int propertyIndex = elementPersister.getEntityMetamodel().getPropertyIndex( reference ); final String formulaTemplate = formulaTemplates[i]; result[i] = new FormulaReference() { @Override public String getFormulaFragment() { return formulaTemplate; } }; } else { result[i] = new ColumnReference() { @Override public String getColumnName() { return columnName; } }; } i++; } return result; } } private String[] formulaTemplates(String reference, int expectedSize) { try { final int propertyIndex = elementPersister.getEntityMetamodel().getPropertyIndex(reference); return ((Queryable) elementPersister).getSubclassPropertyFormulaTemplateClosure()[propertyIndex]; } catch (Exception e) { return new String[expectedSize]; } } @Override public void postInstantiate() throws MappingException { initializer = queryLoaderName == null ? createCollectionInitializer(LoadQueryInfluencers.NONE) : new NamedQueryCollectionInitializer(queryLoaderName, this); } protected void logStaticSQL() { if (LOG.isDebugEnabled()) { LOG.debugf("Static SQL for collection: %s", getRole()); if (getSQLInsertRowString() != null) { LOG.debugf(" Row insert: %s", getSQLInsertRowString()); } if (getSQLUpdateRowString() != null) { LOG.debugf(" Row update: %s", getSQLUpdateRowString()); } if (getSQLDeleteRowString() != null) { LOG.debugf(" Row delete: %s", getSQLDeleteRowString()); } if (getSQLDeleteString() != null) { LOG.debugf(" One-shot delete: %s", getSQLDeleteString()); } } } @Override public void initialize(Serializable key, SharedSessionContractImplementor session) throws HibernateException { getAppropriateInitializer(key, session).initialize(key, session); } protected CollectionInitializer getAppropriateInitializer(Serializable key, SharedSessionContractImplementor session) { if (queryLoaderName != null) { // if there is a user-specified loader, return that // TODO: filters!? return initializer; } CollectionInitializer subselectInitializer = getSubselectInitializer(key, session); if (subselectInitializer != null) { return subselectInitializer; } else if (!session.getLoadQueryInfluencers().hasEnabledFilters()) { return initializer; } else { return createCollectionInitializer(session.getLoadQueryInfluencers()); } } private CollectionInitializer getSubselectInitializer(Serializable key, SharedSessionContractImplementor session) { if (!isSubselectLoadable()) { return null; } final PersistenceContext persistenceContext = session.getPersistenceContextInternal(); SubselectFetch subselect = persistenceContext.getBatchFetchQueue() .getSubselect(session.generateEntityKey(key, getOwnerEntityPersister())); if (subselect == null) { return null; } else { // Take care of any entities that might have // been evicted! Iterator iter = subselect.getResult().iterator(); while (iter.hasNext()) { if (!persistenceContext.containsEntity((EntityKey) iter.next())) { iter.remove(); } } // Run a subquery loader return createSubselectInitializer(subselect, session); } } protected abstract CollectionInitializer createSubselectInitializer(SubselectFetch subselect, SharedSessionContractImplementor session); protected abstract CollectionInitializer createCollectionInitializer(LoadQueryInfluencers loadQueryInfluencers) throws MappingException; @Override public NavigableRole getNavigableRole() { return navigableRole; } @Override public CollectionDataAccess getCacheAccessStrategy() { return cacheAccessStrategy; } @Override public boolean hasCache() { return cacheAccessStrategy != null; } @Override public CollectionType getCollectionType() { return collectionType; } protected String getSQLWhereString(String alias) { return StringHelper.replace(sqlWhereStringTemplate, Template.TEMPLATE, alias); } @Override public String getSQLOrderByString(String alias) { return hasOrdering() ? orderByTranslation.injectAliases(new StandardOrderByAliasResolver(alias)) : ""; } @Override public String getManyToManyOrderByString(String alias) { return hasManyToManyOrdering() ? manyToManyOrderByTranslation.injectAliases(new StandardOrderByAliasResolver(alias)) : ""; } @Override public FetchMode getFetchMode() { return fetchMode; } @Override public boolean hasOrdering() { return hasOrder; } @Override public boolean hasManyToManyOrdering() { return isManyToMany() && hasManyToManyOrder; } @Override public boolean hasWhere() { return hasWhere; } protected String getSQLDeleteString() { return sqlDeleteString; } protected String getSQLInsertRowString() { return sqlInsertRowString; } protected String getSQLUpdateRowString() { return sqlUpdateRowString; } protected String getSQLDeleteRowString() { return sqlDeleteRowString; } @Override public Type getKeyType() { return keyType; } @Override public Type getIndexType() { return indexType; } @Override public Type getElementType() { return elementType; } /** * Return the element class of an array, or null otherwise. needed by arrays */ @Override public Class getElementClass() { return elementClass; } @Override public Object readElement(ResultSet rs, Object owner, String[] aliases, SharedSessionContractImplementor session) throws HibernateException, SQLException { return getElementType().nullSafeGet(rs, aliases, session, owner); } @Override public Object readIndex(ResultSet rs, String[] aliases, SharedSessionContractImplementor session) throws HibernateException, SQLException { Object index = getIndexType().nullSafeGet(rs, aliases, session, null); if (index == null) { throw new HibernateException("null index column for collection: " + navigableRole.getFullPath()); } index = decrementIndexByBase(index); return index; } protected Object decrementIndexByBase(Object index) { if (baseIndex != 0) { index = (Integer) index - baseIndex; } return index; } @Override public Object readIdentifier(ResultSet rs, String alias, SharedSessionContractImplementor session) throws HibernateException, SQLException { Object id = getIdentifierType().nullSafeGet(rs, alias, session, null); if (id == null) { throw new HibernateException("null identifier column for collection: " + navigableRole.getFullPath()); } return id; } @Override public Object readKey(ResultSet rs, String[] aliases, SharedSessionContractImplementor session) throws HibernateException, SQLException { // First hydrate the collection key to check if it is null. // Don't bother resolving the collection key if the hydrated value is null. // Implementation note: if collection key is a composite value, then resolving a null value will // result in instantiating an empty composite if AvailableSettings#CREATE_EMPTY_COMPOSITES_ENABLED // is true. By not resolving a null value for a composite key, we avoid the overhead of instantiating // an empty composite, checking if it is equivalent to null (it should be), then ultimately throwing // out the empty value. final Object hydratedKey = getKeyType().hydrate(rs, aliases, session, null); return hydratedKey == null ? null : getKeyType().resolve(hydratedKey, session, null); } /** * Write the key to a JDBC <tt>PreparedStatement</tt> */ protected int writeKey(PreparedStatement st, Serializable key, int i, SharedSessionContractImplementor session) throws HibernateException, SQLException { if (key == null) { throw new NullPointerException("null key for collection: " + navigableRole.getFullPath()); // an assertion } getKeyType().nullSafeSet(st, key, i, session); return i + keyColumnAliases.length; } /** * Write the element to a JDBC <tt>PreparedStatement</tt> */ protected int writeElement(PreparedStatement st, Object elt, int i, SharedSessionContractImplementor session) throws HibernateException, SQLException { getElementType().nullSafeSet(st, elt, i, elementColumnIsSettable, session); return i + ArrayHelper.countTrue(elementColumnIsSettable); } /** * Write the index to a JDBC <tt>PreparedStatement</tt> */ protected int writeIndex(PreparedStatement st, Object index, int i, SharedSessionContractImplementor session) throws HibernateException, SQLException { getIndexType().nullSafeSet(st, incrementIndexByBase(index), i, indexColumnIsSettable, session); return i + ArrayHelper.countTrue(indexColumnIsSettable); } protected Object incrementIndexByBase(Object index) { if (baseIndex != 0) { index = (Integer) index + baseIndex; } return index; } /** * Write the element to a JDBC <tt>PreparedStatement</tt> */ protected int writeElementToWhere(PreparedStatement st, Object elt, int i, SharedSessionContractImplementor session) throws HibernateException, SQLException { if (elementIsPureFormula) { throw new AssertionFailure("cannot use a formula-based element in the where condition"); } getElementType().nullSafeSet(st, elt, i, elementColumnIsInPrimaryKey, session); return i + elementColumnAliases.length; } /** * Write the index to a JDBC <tt>PreparedStatement</tt> */ protected int writeIndexToWhere(PreparedStatement st, Object index, int i, SharedSessionContractImplementor session) throws HibernateException, SQLException { if (indexContainsFormula) { throw new AssertionFailure("cannot use a formula-based index in the where condition"); } getIndexType().nullSafeSet(st, incrementIndexByBase(index), i, session); return i + indexColumnAliases.length; } /** * Write the identifier to a JDBC <tt>PreparedStatement</tt> */ public int writeIdentifier(PreparedStatement st, Object id, int i, SharedSessionContractImplementor session) throws HibernateException, SQLException { getIdentifierType().nullSafeSet(st, id, i, session); return i + 1; } @Override public boolean isPrimitiveArray() { return isPrimitiveArray; } @Override public boolean isArray() { return isArray; } @Override public String[] getKeyColumnAliases(String suffix) { return new Alias(suffix).toAliasStrings(keyColumnAliases); } @Override public String[] getElementColumnAliases(String suffix) { return new Alias(suffix).toAliasStrings(elementColumnAliases); } @Override public String[] getIndexColumnAliases(String suffix) { if (hasIndex) { return new Alias(suffix).toAliasStrings(indexColumnAliases); } else { return null; } } @Override public String getIdentifierColumnAlias(String suffix) { if (hasIdentifier) { return new Alias(suffix).toAliasString(identifierColumnAlias); } else { return null; } } @Override public String getIdentifierColumnName() { if (hasIdentifier) { return identifierColumnName; } else { return null; } } /** * Generate a list of collection index, key and element columns */ @Override public String selectFragment(String alias, String columnSuffix) { SelectFragment frag = generateSelectFragment(alias, columnSuffix); appendElementColumns(frag, alias); appendIndexColumns(frag, alias); appendIdentifierColumns(frag, alias); return frag.toFragmentString().substring(2); // strip leading ',' } protected String generateSelectSizeString(boolean isIntegerIndexed) { String selectValue = isIntegerIndexed ? "max(" + getIndexColumnNames()[0] + ") + 1" : // lists, arrays "count(" + getElementColumnNames()[0] + ")"; // sets, maps, bags return new SimpleSelect(dialect).setTableName(getTableName()).addCondition(getKeyColumnNames(), "=?") .addWhereToken(sqlWhereString).addColumn(selectValue).toStatementString(); } protected String generateDetectRowByIndexString() { if (!hasIndex()) { return null; } return new SimpleSelect(dialect).setTableName(getTableName()).addCondition(getKeyColumnNames(), "=?") .addCondition(getIndexColumnNames(), "=?").addCondition(indexFormulas, "=?") .addWhereToken(sqlWhereString).addColumn("1").toStatementString(); } protected String generateSelectRowByIndexString() { if (!hasIndex()) { return null; } return new SimpleSelect(dialect).setTableName(getTableName()).addCondition(getKeyColumnNames(), "=?") .addCondition(getIndexColumnNames(), "=?").addCondition(indexFormulas, "=?") .addWhereToken(sqlWhereString).addColumns(getElementColumnNames(), elementColumnAliases) .addColumns(indexFormulas, indexColumnAliases).toStatementString(); } protected String generateDetectRowByElementString() { return new SimpleSelect(dialect).setTableName(getTableName()).addCondition(getKeyColumnNames(), "=?") .addCondition(getElementColumnNames(), "=?").addCondition(elementFormulas, "=?") .addWhereToken(sqlWhereString).addColumn("1").toStatementString(); } protected SelectFragment generateSelectFragment(String alias, String columnSuffix) { return new SelectFragment().setSuffix(columnSuffix).addColumns(alias, keyColumnNames, keyColumnAliases); } protected void appendElementColumns(SelectFragment frag, String elemAlias) { for (int i = 0; i < elementColumnIsGettable.length; i++) { if (elementColumnIsGettable[i]) { frag.addColumnTemplate(elemAlias, elementColumnReaderTemplates[i], elementColumnAliases[i]); } else { frag.addFormula(elemAlias, elementFormulaTemplates[i], elementColumnAliases[i]); } } } protected void appendIndexColumns(SelectFragment frag, String alias) { if (hasIndex) { for (int i = 0; i < indexColumnIsGettable.length; i++) { if (indexColumnIsGettable[i]) { frag.addColumn(alias, indexColumnNames[i], indexColumnAliases[i]); } else { frag.addFormula(alias, indexFormulaTemplates[i], indexColumnAliases[i]); } } } } protected void appendIdentifierColumns(SelectFragment frag, String alias) { if (hasIdentifier) { frag.addColumn(alias, identifierColumnName, identifierColumnAlias); } } @Override public String[] getIndexColumnNames() { return indexColumnNames; } @Override public String[] getIndexFormulas() { return indexFormulas; } @Override public String[] getIndexColumnNames(String alias) { return qualify(alias, indexColumnNames, indexFormulaTemplates); } @Override public String[] getElementColumnNames(String alias) { return qualify(alias, elementColumnNames, elementFormulaTemplates); } private static String[] qualify(String alias, String[] columnNames, String[] formulaTemplates) { int span = columnNames.length; String[] result = new String[span]; for (int i = 0; i < span; i++) { if (columnNames[i] == null) { result[i] = StringHelper.replace(formulaTemplates[i], Template.TEMPLATE, alias); } else { result[i] = StringHelper.qualify(alias, columnNames[i]); } } return result; } @Override public String[] getElementColumnNames() { return elementColumnNames; // TODO: something with formulas... } @Override public String[] getKeyColumnNames() { return keyColumnNames; } @Override public boolean hasIndex() { return hasIndex; } @Override public boolean isLazy() { return isLazy; } @Override public boolean isInverse() { return isInverse; } @Override public String getTableName() { return qualifiedTableName; } private BasicBatchKey removeBatchKey; @Override public void remove(Serializable id, SharedSessionContractImplementor session) throws HibernateException { if (!isInverse && isRowDeleteEnabled()) { if (LOG.isDebugEnabled()) { LOG.debugf("Deleting collection: %s", MessageHelper.collectionInfoString(this, id, getFactory())); } // Remove all the old entries try { int offset = 1; final PreparedStatement st; Expectation expectation = Expectations.appropriateExpectation(getDeleteAllCheckStyle()); boolean callable = isDeleteAllCallable(); boolean useBatch = expectation.canBeBatched(); String sql = getSQLDeleteString(); if (useBatch) { if (removeBatchKey == null) { removeBatchKey = new BasicBatchKey(getRole() + "#REMOVE", expectation); } st = session.getJdbcCoordinator().getBatch(removeBatchKey).getBatchStatement(sql, callable); } else { st = session.getJdbcCoordinator().getStatementPreparer().prepareStatement(sql, callable); } try { offset += expectation.prepare(st); writeKey(st, id, offset, session); if (useBatch) { session.getJdbcCoordinator().getBatch(removeBatchKey).addToBatch(); } else { expectation.verifyOutcome( session.getJdbcCoordinator().getResultSetReturn().executeUpdate(st), st, -1); } } catch (SQLException sqle) { if (useBatch) { session.getJdbcCoordinator().abortBatch(); } throw sqle; } finally { if (!useBatch) { session.getJdbcCoordinator().getResourceRegistry().release(st); session.getJdbcCoordinator().afterStatementExecution(); } } LOG.debug("Done deleting collection"); } catch (SQLException sqle) { throw sqlExceptionHelper.convert(sqle, "could not delete collection: " + MessageHelper.collectionInfoString(this, id, getFactory()), getSQLDeleteString()); } } } protected BasicBatchKey recreateBatchKey; @Override public void recreate(PersistentCollection collection, Serializable id, SharedSessionContractImplementor session) throws HibernateException { if (isInverse) { return; } if (!isRowInsertEnabled()) { return; } if (LOG.isDebugEnabled()) { LOG.debugf("Inserting collection: %s", MessageHelper.collectionInfoString(this, collection, id, session)); } try { // create all the new entries Iterator entries = collection.entries(this); final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); if (entries.hasNext()) { Expectation expectation = Expectations.appropriateExpectation(getInsertCheckStyle()); collection.preInsert(this); int i = 0; int count = 0; while (entries.hasNext()) { final Object entry = entries.next(); if (collection.entryExists(entry, i)) { int offset = 1; final PreparedStatement st; boolean callable = isInsertCallable(); boolean useBatch = expectation.canBeBatched(); String sql = getSQLInsertRowString(); if (useBatch) { if (recreateBatchKey == null) { recreateBatchKey = new BasicBatchKey(getRole() + "#RECREATE", expectation); } st = jdbcCoordinator.getBatch(recreateBatchKey).getBatchStatement(sql, callable); } else { st = jdbcCoordinator.getStatementPreparer().prepareStatement(sql, callable); } try { offset += expectation.prepare(st); // TODO: copy/paste from insertRows() int loc = writeKey(st, id, offset, session); if (hasIdentifier) { loc = writeIdentifier(st, collection.getIdentifier(entry, i), loc, session); } if (hasIndex /* && !indexIsFormula */) { loc = writeIndex(st, collection.getIndex(entry, i, this), loc, session); } loc = writeElement(st, collection.getElement(entry), loc, session); if (useBatch) { jdbcCoordinator.getBatch(recreateBatchKey).addToBatch(); } else { expectation.verifyOutcome(jdbcCoordinator.getResultSetReturn().executeUpdate(st), st, -1); } collection.afterRowInsert(this, entry, i); count++; } catch (SQLException sqle) { if (useBatch) { jdbcCoordinator.abortBatch(); } throw sqle; } finally { if (!useBatch) { jdbcCoordinator.getResourceRegistry().release(st); jdbcCoordinator.afterStatementExecution(); } } } i++; } LOG.debugf("Done inserting collection: %s rows inserted", count); } else { LOG.debug("Collection was empty"); } } catch (SQLException sqle) { throw sqlExceptionHelper.convert(sqle, "could not insert collection: " + MessageHelper.collectionInfoString(this, collection, id, session), getSQLInsertRowString()); } } protected boolean isRowDeleteEnabled() { return true; } private BasicBatchKey deleteBatchKey; @Override public void deleteRows(PersistentCollection collection, Serializable id, SharedSessionContractImplementor session) throws HibernateException { if (isInverse) { return; } if (!isRowDeleteEnabled()) { return; } if (LOG.isDebugEnabled()) { LOG.debugf("Deleting rows of collection: %s", MessageHelper.collectionInfoString(this, collection, id, session)); } boolean deleteByIndex = !isOneToMany() && hasIndex && !indexContainsFormula; final Expectation expectation = Expectations.appropriateExpectation(getDeleteCheckStyle()); try { // delete all the deleted entries Iterator deletes = collection.getDeletes(this, !deleteByIndex); if (deletes.hasNext()) { int offset = 1; int count = 0; while (deletes.hasNext()) { final PreparedStatement st; boolean callable = isDeleteCallable(); boolean useBatch = expectation.canBeBatched(); String sql = getSQLDeleteRowString(); if (useBatch) { if (deleteBatchKey == null) { deleteBatchKey = new BasicBatchKey(getRole() + "#DELETE", expectation); } st = session.getJdbcCoordinator().getBatch(deleteBatchKey).getBatchStatement(sql, callable); } else { st = session.getJdbcCoordinator().getStatementPreparer().prepareStatement(sql, callable); } try { expectation.prepare(st); Object entry = deletes.next(); int loc = offset; if (hasIdentifier) { writeIdentifier(st, entry, loc, session); } else { loc = writeKey(st, id, loc, session); if (deleteByIndex) { writeIndexToWhere(st, entry, loc, session); } else { writeElementToWhere(st, entry, loc, session); } } if (useBatch) { session.getJdbcCoordinator().getBatch(deleteBatchKey).addToBatch(); } else { expectation.verifyOutcome( session.getJdbcCoordinator().getResultSetReturn().executeUpdate(st), st, -1); } count++; } catch (SQLException sqle) { if (useBatch) { session.getJdbcCoordinator().abortBatch(); } throw sqle; } finally { if (!useBatch) { session.getJdbcCoordinator().getResourceRegistry().release(st); session.getJdbcCoordinator().afterStatementExecution(); } } LOG.debugf("Done deleting collection rows: %s deleted", count); } } else { LOG.debug("No rows to delete"); } } catch (SQLException sqle) { throw sqlExceptionHelper.convert(sqle, "could not delete collection rows: " + MessageHelper.collectionInfoString(this, collection, id, session), getSQLDeleteRowString()); } } protected boolean isRowInsertEnabled() { return true; } private BasicBatchKey insertBatchKey; @Override public void insertRows(PersistentCollection collection, Serializable id, SharedSessionContractImplementor session) throws HibernateException { if (isInverse) { return; } if (!isRowInsertEnabled()) { return; } if (LOG.isDebugEnabled()) { LOG.debugf("Inserting rows of collection: %s", MessageHelper.collectionInfoString(this, collection, id, session)); } try { // insert all the new entries collection.preInsert(this); Iterator entries = collection.entries(this); Expectation expectation = Expectations.appropriateExpectation(getInsertCheckStyle()); boolean callable = isInsertCallable(); boolean useBatch = expectation.canBeBatched(); String sql = getSQLInsertRowString(); int i = 0; int count = 0; while (entries.hasNext()) { int offset = 1; Object entry = entries.next(); PreparedStatement st = null; if (collection.needsInserting(entry, i, elementType)) { if (useBatch) { if (insertBatchKey == null) { insertBatchKey = new BasicBatchKey(getRole() + "#INSERT", expectation); } if (st == null) { st = session.getJdbcCoordinator().getBatch(insertBatchKey).getBatchStatement(sql, callable); } } else { st = session.getJdbcCoordinator().getStatementPreparer().prepareStatement(sql, callable); } try { offset += expectation.prepare(st); // TODO: copy/paste from recreate() offset = writeKey(st, id, offset, session); if (hasIdentifier) { offset = writeIdentifier(st, collection.getIdentifier(entry, i), offset, session); } if (hasIndex /* && !indexIsFormula */) { offset = writeIndex(st, collection.getIndex(entry, i, this), offset, session); } writeElement(st, collection.getElement(entry), offset, session); if (useBatch) { session.getJdbcCoordinator().getBatch(insertBatchKey).addToBatch(); } else { expectation.verifyOutcome( session.getJdbcCoordinator().getResultSetReturn().executeUpdate(st), st, -1); } collection.afterRowInsert(this, entry, i); count++; } catch (SQLException sqle) { if (useBatch) { session.getJdbcCoordinator().abortBatch(); } throw sqle; } finally { if (!useBatch) { session.getJdbcCoordinator().getResourceRegistry().release(st); session.getJdbcCoordinator().afterStatementExecution(); } } } i++; } LOG.debugf("Done inserting rows: %s inserted", count); } catch (SQLException sqle) { throw sqlExceptionHelper.convert(sqle, "could not insert collection rows: " + MessageHelper.collectionInfoString(this, collection, id, session), getSQLInsertRowString()); } } @Override public String getRole() { return navigableRole.getFullPath(); } public String getOwnerEntityName() { return entityName; } @Override public EntityPersister getOwnerEntityPersister() { return ownerPersister; } @Override public IdentifierGenerator getIdentifierGenerator() { return identifierGenerator; } @Override public Type getIdentifierType() { return identifierType; } @Override public boolean hasOrphanDelete() { return hasOrphanDelete; } @Override public Type toType(String propertyName) throws QueryException { if ("index".equals(propertyName)) { return indexType; } return elementPropertyMapping.toType(propertyName); } @Override public abstract boolean isManyToMany(); @Override public String getManyToManyFilterFragment(String alias, Map enabledFilters) { StringBuilder buffer = new StringBuilder(); manyToManyFilterHelper.render(buffer, elementPersister.getFilterAliasGenerator(alias), enabledFilters); if (manyToManyWhereString != null) { buffer.append(" and ").append(StringHelper.replace(manyToManyWhereTemplate, Template.TEMPLATE, alias)); } return buffer.toString(); } @Override public String[] toColumns(String alias, String propertyName) throws QueryException { if ("index".equals(propertyName)) { return qualify(alias, indexColumnNames, indexFormulaTemplates); } return elementPropertyMapping.toColumns(alias, propertyName); } private String[] indexFragments; @Override public String[] toColumns(String propertyName) throws QueryException { if ("index".equals(propertyName)) { if (indexFragments == null) { String[] tmp = new String[indexColumnNames.length]; for (int i = 0; i < indexColumnNames.length; i++) { tmp[i] = indexColumnNames[i] == null ? indexFormulas[i] : indexColumnNames[i]; indexFragments = tmp; } } return indexFragments; } return elementPropertyMapping.toColumns(propertyName); } @Override public Type getType() { return elementPropertyMapping.getType(); // ==elementType ?? } @Override public String getName() { return getRole(); } @Override public EntityPersister getElementPersister() { if (elementPersister == null) { throw new AssertionFailure("not an association"); } return elementPersister; } @Override public boolean isCollection() { return true; } @Override public Serializable[] getCollectionSpaces() { return spaces; } protected abstract String generateDeleteString(); protected abstract String generateDeleteRowString(); protected abstract String generateUpdateRowString(); protected abstract String generateInsertRowString(); @Override public void updateRows(PersistentCollection collection, Serializable id, SharedSessionContractImplementor session) throws HibernateException { if (!isInverse && collection.isRowUpdatePossible()) { LOG.debugf("Updating rows of collection: %s#%s", navigableRole.getFullPath(), id); // update all the modified entries int count = doUpdateRows(id, collection, session); LOG.debugf("Done updating rows: %s updated", count); } } protected abstract int doUpdateRows(Serializable key, PersistentCollection collection, SharedSessionContractImplementor session) throws HibernateException; @Override public void processQueuedOps(PersistentCollection collection, Serializable key, SharedSessionContractImplementor session) throws HibernateException { if (collection.hasQueuedOperations()) { doProcessQueuedOps(collection, key, session); } } /** * Process queued operations within the PersistentCollection. * * @param collection The collection * @param key The collection key * @param nextIndex The next index to write * @param session The session * @throws HibernateException * * @deprecated Use {@link #doProcessQueuedOps(org.hibernate.collection.spi.PersistentCollection, java.io.Serializable, org.hibernate.engine.spi.SharedSessionContractImplementor)} */ @Deprecated protected void doProcessQueuedOps(PersistentCollection collection, Serializable key, int nextIndex, SharedSessionContractImplementor session) throws HibernateException { doProcessQueuedOps(collection, key, session); } protected abstract void doProcessQueuedOps(PersistentCollection collection, Serializable key, SharedSessionContractImplementor session) throws HibernateException; @Override public CollectionMetadata getCollectionMetadata() { return this; } @Override public SessionFactoryImplementor getFactory() { return factory; } protected String filterFragment(String alias) throws MappingException { return hasWhere() ? " and " + getSQLWhereString(alias) : ""; } protected String filterFragment(String alias, Set<String> treatAsDeclarations) throws MappingException { return hasWhere() ? " and " + getSQLWhereString(alias) : ""; } @Override public String filterFragment(String alias, Map enabledFilters) throws MappingException { StringBuilder sessionFilterFragment = new StringBuilder(); filterHelper.render(sessionFilterFragment, getFilterAliasGenerator(alias), enabledFilters); return sessionFilterFragment.append(filterFragment(alias)).toString(); } @Override public String filterFragment(String alias, Map enabledFilters, Set<String> treatAsDeclarations) { StringBuilder sessionFilterFragment = new StringBuilder(); filterHelper.render(sessionFilterFragment, getFilterAliasGenerator(alias), enabledFilters); return sessionFilterFragment.append(filterFragment(alias, treatAsDeclarations)).toString(); } @Override public String oneToManyFilterFragment(String alias) throws MappingException { return ""; } @Override public String oneToManyFilterFragment(String alias, Set<String> treatAsDeclarations) { return oneToManyFilterFragment(alias); } protected boolean isInsertCallable() { return insertCallable; } protected ExecuteUpdateResultCheckStyle getInsertCheckStyle() { return insertCheckStyle; } protected boolean isUpdateCallable() { return updateCallable; } protected ExecuteUpdateResultCheckStyle getUpdateCheckStyle() { return updateCheckStyle; } protected boolean isDeleteCallable() { return deleteCallable; } protected ExecuteUpdateResultCheckStyle getDeleteCheckStyle() { return deleteCheckStyle; } protected boolean isDeleteAllCallable() { return deleteAllCallable; } protected ExecuteUpdateResultCheckStyle getDeleteAllCheckStyle() { return deleteAllCheckStyle; } @Override public String toString() { return StringHelper.unqualify(getClass().getName()) + '(' + navigableRole.getFullPath() + ')'; } @Override public boolean isVersioned() { return isVersioned && getOwnerEntityPersister().isVersioned(); } // TODO: deprecate??? protected SQLExceptionConverter getSQLExceptionConverter() { return getSQLExceptionHelper().getSqlExceptionConverter(); } // TODO: needed??? protected SqlExceptionHelper getSQLExceptionHelper() { return sqlExceptionHelper; } @Override public CacheEntryStructure getCacheEntryStructure() { return cacheEntryStructure; } @Override public boolean isAffectedByEnabledFilters(SharedSessionContractImplementor session) { final Map<String, Filter> enabledFilters = session.getLoadQueryInfluencers().getEnabledFilters(); return filterHelper.isAffectedBy(enabledFilters) || (isManyToMany() && manyToManyFilterHelper.isAffectedBy(enabledFilters)); } public boolean isSubselectLoadable() { return subselectLoadable; } @Override public boolean isMutable() { return isMutable; } @Override public String[] getCollectionPropertyColumnAliases(String propertyName, String suffix) { String[] rawAliases = (String[]) collectionPropertyColumnAliases.get(propertyName); if (rawAliases == null) { return null; } String[] result = new String[rawAliases.length]; final Alias alias = new Alias(suffix); for (int i = 0; i < rawAliases.length; i++) { result[i] = alias.toUnquotedAliasString(rawAliases[i]); } return result; } // TODO: formulas ? public void initCollectionPropertyMap() { initCollectionPropertyMap("key", keyType, keyColumnAliases, keyColumnNames); initCollectionPropertyMap("element", elementType, elementColumnAliases, elementColumnNames); if (hasIndex) { initCollectionPropertyMap("index", indexType, indexColumnAliases, indexColumnNames); } if (hasIdentifier) { initCollectionPropertyMap("id", identifierType, new String[] { identifierColumnAlias }, new String[] { identifierColumnName }); } } private void initCollectionPropertyMap(String aliasName, Type type, String[] columnAliases, String[] columnNames) { collectionPropertyColumnAliases.put(aliasName, columnAliases); if (type.isComponentType()) { CompositeType ct = (CompositeType) type; String[] propertyNames = ct.getPropertyNames(); for (int i = 0; i < propertyNames.length; i++) { String name = propertyNames[i]; collectionPropertyColumnAliases.put(aliasName + "." + name, columnAliases[i]); } } } @Override public int getSize(Serializable key, SharedSessionContractImplementor session) { try { final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); PreparedStatement st = jdbcCoordinator.getStatementPreparer().prepareStatement(sqlSelectSizeString); try { getKeyType().nullSafeSet(st, key, 1, session); ResultSet rs = jdbcCoordinator.getResultSetReturn().extract(st); try { return rs.next() ? rs.getInt(1) - baseIndex : 0; } finally { jdbcCoordinator.getResourceRegistry().release(rs, st); } } finally { jdbcCoordinator.getResourceRegistry().release(st); jdbcCoordinator.afterStatementExecution(); } } catch (SQLException sqle) { throw getSQLExceptionHelper().convert(sqle, "could not retrieve collection size: " + MessageHelper.collectionInfoString(this, key, getFactory()), sqlSelectSizeString); } } @Override public boolean indexExists(Serializable key, Object index, SharedSessionContractImplementor session) { return exists(key, incrementIndexByBase(index), getIndexType(), sqlDetectRowByIndexString, session); } @Override public boolean elementExists(Serializable key, Object element, SharedSessionContractImplementor session) { return exists(key, element, getElementType(), sqlDetectRowByElementString, session); } private boolean exists(Serializable key, Object indexOrElement, Type indexOrElementType, String sql, SharedSessionContractImplementor session) { try { final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); PreparedStatement st = jdbcCoordinator.getStatementPreparer().prepareStatement(sql); try { getKeyType().nullSafeSet(st, key, 1, session); indexOrElementType.nullSafeSet(st, indexOrElement, keyColumnNames.length + 1, session); ResultSet rs = jdbcCoordinator.getResultSetReturn().extract(st); try { return rs.next(); } finally { jdbcCoordinator.getResourceRegistry().release(rs, st); } } catch (TransientObjectException e) { return false; } finally { jdbcCoordinator.getResourceRegistry().release(st); jdbcCoordinator.afterStatementExecution(); } } catch (SQLException sqle) { throw getSQLExceptionHelper().convert(sqle, "could not check row existence: " + MessageHelper.collectionInfoString(this, key, getFactory()), sqlSelectSizeString); } } @Override public Object getElementByIndex(Serializable key, Object index, SharedSessionContractImplementor session, Object owner) { try { final JdbcCoordinator jdbcCoordinator = session.getJdbcCoordinator(); PreparedStatement st = jdbcCoordinator.getStatementPreparer() .prepareStatement(sqlSelectRowByIndexString); try { getKeyType().nullSafeSet(st, key, 1, session); getIndexType().nullSafeSet(st, incrementIndexByBase(index), keyColumnNames.length + 1, session); ResultSet rs = jdbcCoordinator.getResultSetReturn().extract(st); try { if (rs.next()) { return getElementType().nullSafeGet(rs, elementColumnAliases, session, owner); } else { return null; } } finally { jdbcCoordinator.getResourceRegistry().release(rs, st); } } finally { jdbcCoordinator.getResourceRegistry().release(st); jdbcCoordinator.afterStatementExecution(); } } catch (SQLException sqle) { throw getSQLExceptionHelper().convert(sqle, "could not read row: " + MessageHelper.collectionInfoString(this, key, getFactory()), sqlSelectSizeString); } } @Override public boolean isExtraLazy() { return isExtraLazy; } protected Dialect getDialect() { return dialect; } /** * Intended for internal use only. In fact really only currently used from * test suite for assertion purposes. * * @return The default collection initializer for this persister/collection. */ public CollectionInitializer getInitializer() { return initializer; } @Override public int getBatchSize() { return batchSize; } @Override public String getMappedByProperty() { return mappedByProperty; } private class StandardOrderByAliasResolver implements OrderByAliasResolver { private final String rootAlias; private StandardOrderByAliasResolver(String rootAlias) { this.rootAlias = rootAlias; } @Override public String resolveTableAlias(String columnReference) { if (elementPersister == null) { // we have collection of non-entity elements... return rootAlias; } else { return ((Loadable) elementPersister).getTableAliasForColumn(columnReference, rootAlias); } } } public abstract FilterAliasGenerator getFilterAliasGenerator(final String rootAlias); // ColectionDefinition impl ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @Override public CollectionPersister getCollectionPersister() { return this; } @Override public CollectionIndexDefinition getIndexDefinition() { if (!hasIndex()) { return null; } return new CollectionIndexDefinition() { @Override public CollectionDefinition getCollectionDefinition() { return AbstractCollectionPersister.this; } @Override public Type getType() { return getIndexType(); } @Override public EntityDefinition toEntityDefinition() { if (!getType().isEntityType()) { throw new IllegalStateException("Cannot treat collection index type as entity"); } return (EntityPersister) ((AssociationType) getIndexType()).getAssociatedJoinable(getFactory()); } @Override public CompositionDefinition toCompositeDefinition() { if (!getType().isComponentType()) { throw new IllegalStateException("Cannot treat collection index type as composite"); } return new CompositeCollectionElementDefinition() { @Override public String getName() { return "index"; } @Override public CompositeType getType() { return (CompositeType) getIndexType(); } @Override public boolean isNullable() { return false; } @Override public AttributeSource getSource() { // TODO: what if this is a collection w/in an encapsulated composition attribute? // should return the encapsulated composition attribute instead??? return getOwnerEntityPersister(); } @Override public Iterable<AttributeDefinition> getAttributes() { return CompositionSingularSubAttributesHelper .getCompositeCollectionIndexSubAttributes(this); } @Override public CollectionDefinition getCollectionDefinition() { return AbstractCollectionPersister.this; } }; } @Override public AnyMappingDefinition toAnyMappingDefinition() { final Type type = getType(); if (!type.isAnyType()) { throw new IllegalStateException("Cannot treat collection index type as ManyToAny"); } return new StandardAnyTypeDefinition((AnyType) type, isLazy() || isExtraLazy()); } }; } @Override public CollectionElementDefinition getElementDefinition() { return new CollectionElementDefinition() { @Override public CollectionDefinition getCollectionDefinition() { return AbstractCollectionPersister.this; } @Override public Type getType() { return getElementType(); } @Override public AnyMappingDefinition toAnyMappingDefinition() { final Type type = getType(); if (!type.isAnyType()) { throw new IllegalStateException("Cannot treat collection element type as ManyToAny"); } return new StandardAnyTypeDefinition((AnyType) type, isLazy() || isExtraLazy()); } @Override public EntityDefinition toEntityDefinition() { if (!getType().isEntityType()) { throw new IllegalStateException("Cannot treat collection element type as entity"); } return getElementPersister(); } @Override public CompositeCollectionElementDefinition toCompositeElementDefinition() { if (!getType().isComponentType()) { throw new IllegalStateException("Cannot treat entity collection element type as composite"); } return new CompositeCollectionElementDefinition() { @Override public String getName() { return ""; } @Override public CompositeType getType() { return (CompositeType) getElementType(); } @Override public boolean isNullable() { return false; } @Override public AttributeSource getSource() { // TODO: what if this is a collection w/in an encapsulated composition attribute? // should return the encapsulated composition attribute instead??? return getOwnerEntityPersister(); } @Override public Iterable<AttributeDefinition> getAttributes() { return CompositionSingularSubAttributesHelper .getCompositeCollectionElementSubAttributes(this); } @Override public CollectionDefinition getCollectionDefinition() { return AbstractCollectionPersister.this; } }; } }; } }