org.hibernate.persister.collection.OneToManyPersister.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.persister.collection.OneToManyPersister.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.persister.collection;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Set;

import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.access.CollectionDataAccess;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.internal.FilterAliasGenerator;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.jdbc.Expectation;
import org.hibernate.jdbc.Expectations;
import org.hibernate.loader.collection.BatchingCollectionInitializerBuilder;
import org.hibernate.loader.collection.CollectionInitializer;
import org.hibernate.loader.collection.SubselectOneToManyLoader;
import org.hibernate.loader.entity.CollectionElementLoader;
import org.hibernate.mapping.Collection;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.sql.Update;

/**
 * Collection persister for one-to-many associations.
 *
 * @author Gavin King
 * @author Brett Meyer
 */
public class OneToManyPersister extends AbstractCollectionPersister {

    private final boolean cascadeDeleteEnabled;
    private final boolean keyIsNullable;
    private final boolean keyIsUpdateable;

    @Override
    protected boolean isRowDeleteEnabled() {
        return keyIsUpdateable && keyIsNullable;
    }

    @Override
    protected boolean isRowInsertEnabled() {
        return keyIsUpdateable;
    }

    public boolean isCascadeDeleteEnabled() {
        return cascadeDeleteEnabled;
    }

    public OneToManyPersister(Collection collectionBinding, CollectionDataAccess cacheAccessStrategy,
            PersisterCreationContext creationContext) throws MappingException, CacheException {
        super(collectionBinding, cacheAccessStrategy, creationContext);
        cascadeDeleteEnabled = collectionBinding.getKey().isCascadeDeleteEnabled()
                && creationContext.getSessionFactory().getDialect().supportsCascadeDelete();
        keyIsNullable = collectionBinding.getKey().isNullable();
        keyIsUpdateable = collectionBinding.getKey().isUpdateable();
    }

    /**
     * Generate the SQL UPDATE that updates all the foreign keys to null
     */
    @Override
    protected String generateDeleteString() {
        final Update update = new Update(getDialect()).setTableName(qualifiedTableName)
                .addColumns(keyColumnNames, "null").addPrimaryKeyColumns(keyColumnNames);

        if (hasIndex && !indexContainsFormula) {
            for (int i = 0; i < indexColumnNames.length; i++) {
                if (indexColumnIsSettable[i]) {
                    update.addColumn(indexColumnNames[i], "null");
                }
            }
        }

        if (hasWhere) {
            update.setWhere(sqlWhereString);
        }

        if (getFactory().getSessionFactoryOptions().isCommentsEnabled()) {
            update.setComment("delete one-to-many " + getRole());
        }

        return update.toStatementString();
    }

    /**
     * Generate the SQL UPDATE that updates a foreign key to a value
     */
    @Override
    protected String generateInsertRowString() {
        final Update update = new Update(getDialect()).setTableName(qualifiedTableName).addColumns(keyColumnNames);

        if (hasIndex && !indexContainsFormula) {
            for (int i = 0; i < indexColumnNames.length; i++) {
                if (indexColumnIsSettable[i]) {
                    update.addColumn(indexColumnNames[i]);
                }
            }
        }

        //identifier collections not supported for 1-to-many

        if (getFactory().getSessionFactoryOptions().isCommentsEnabled()) {
            update.setComment("create one-to-many row " + getRole());
        }

        return update.addPrimaryKeyColumns(elementColumnNames, elementColumnWriters).toStatementString();
    }

    /**
     * Generate the SQL UPDATE that inserts a collection index
     */
    @Override
    protected String generateUpdateRowString() {
        final Update update = new Update(getDialect()).setTableName(qualifiedTableName);
        update.addPrimaryKeyColumns(elementColumnNames, elementColumnIsSettable, elementColumnWriters);
        if (hasIdentifier) {
            update.addPrimaryKeyColumns(new String[] { identifierColumnName });
        }
        if (hasIndex && !indexContainsFormula) {
            for (int i = 0; i < indexColumnNames.length; i++) {
                if (indexColumnIsSettable[i]) {
                    update.addColumn(indexColumnNames[i]);
                }
            }
        }

        return update.toStatementString();
    }

    /**
     * Generate the SQL UPDATE that updates a particular row's foreign
     * key to null
     */
    @Override
    protected String generateDeleteRowString() {
        final Update update = new Update(getDialect()).setTableName(qualifiedTableName).addColumns(keyColumnNames,
                "null");

        if (hasIndex && !indexContainsFormula) {
            for (int i = 0; i < indexColumnNames.length; i++) {
                if (indexColumnIsSettable[i]) {
                    update.addColumn(indexColumnNames[i], "null");
                }
            }
        }

        if (getFactory().getSessionFactoryOptions().isCommentsEnabled()) {
            update.setComment("delete one-to-many row " + getRole());
        }

        //use a combination of foreign key columns and pk columns, since
        //the ordering of removal and addition is not guaranteed when
        //a child moves from one parent to another
        String[] rowSelectColumnNames = ArrayHelper.join(keyColumnNames, elementColumnNames);
        return update.addPrimaryKeyColumns(rowSelectColumnNames).toStatementString();
    }

    @Override
    public void recreate(PersistentCollection collection, Serializable id, SharedSessionContractImplementor session)
            throws HibernateException {
        super.recreate(collection, id, session);
        writeIndex(collection, collection.entries(this), id, true, session);
    }

    @Override
    public void insertRows(PersistentCollection collection, Serializable id,
            SharedSessionContractImplementor session) throws HibernateException {
        super.insertRows(collection, id, session);
        writeIndex(collection, collection.entries(this), id, true, session);
    }

    @Override
    protected void doProcessQueuedOps(PersistentCollection collection, Serializable id,
            SharedSessionContractImplementor session) throws HibernateException {
        writeIndex(collection, collection.queuedAdditionIterator(), id, false, session);
    }

    private void writeIndex(PersistentCollection collection, Iterator entries, Serializable id, boolean resetIndex,
            SharedSessionContractImplementor session) {
        // If one-to-many and inverse, still need to create the index.  See HHH-5732.
        if (isInverse && hasIndex && !indexContainsFormula && ArrayHelper.countTrue(indexColumnIsSettable) > 0) {
            try {
                if (entries.hasNext()) {
                    int nextIndex = resetIndex ? 0 : getSize(id, session);
                    Expectation expectation = Expectations.appropriateExpectation(getUpdateCheckStyle());
                    while (entries.hasNext()) {

                        final Object entry = entries.next();
                        if (entry != null && collection.entryExists(entry, nextIndex)) {
                            int offset = 1;
                            PreparedStatement st = null;
                            boolean callable = isUpdateCallable();
                            boolean useBatch = expectation.canBeBatched();
                            String sql = getSQLUpdateRowString();

                            if (useBatch) {
                                if (recreateBatchKey == null) {
                                    recreateBatchKey = new BasicBatchKey(getRole() + "#RECREATE", expectation);
                                }
                                st = session.getJdbcCoordinator().getBatch(recreateBatchKey).getBatchStatement(sql,
                                        callable);
                            } else {
                                st = session.getJdbcCoordinator().getStatementPreparer().prepareStatement(sql,
                                        callable);
                            }

                            try {
                                offset += expectation.prepare(st);
                                if (hasIdentifier) {
                                    offset = writeIdentifier(st, collection.getIdentifier(entry, nextIndex), offset,
                                            session);
                                }
                                offset = writeIndex(st, collection.getIndex(entry, nextIndex, this), offset,
                                        session);
                                offset = writeElement(st, collection.getElement(entry), offset, session);

                                if (useBatch) {
                                    session.getJdbcCoordinator().getBatch(recreateBatchKey).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();
                                }
                            }

                        }
                        nextIndex++;
                    }
                }
            } catch (SQLException sqle) {
                throw sqlExceptionHelper.convert(sqle,
                        "could not update collection: "
                                + MessageHelper.collectionInfoString(this, collection, id, session),
                        getSQLUpdateRowString());
            }
        }
    }

    public boolean consumesEntityAlias() {
        return true;
    }

    public boolean consumesCollectionAlias() {
        return true;
    }

    public boolean isOneToMany() {
        return true;
    }

    @Override
    public boolean isManyToMany() {
        return false;
    }

    private BasicBatchKey deleteRowBatchKey;
    private BasicBatchKey insertRowBatchKey;

    @Override
    protected int doUpdateRows(Serializable id, PersistentCollection collection,
            SharedSessionContractImplementor session) {

        // we finish all the "removes" first to take care of possible unique
        // constraints and so that we can take better advantage of batching

        try {
            int count = 0;
            if (isRowDeleteEnabled()) {
                final Expectation deleteExpectation = Expectations.appropriateExpectation(getDeleteCheckStyle());
                final boolean useBatch = deleteExpectation.canBeBatched();
                if (useBatch && deleteRowBatchKey == null) {
                    deleteRowBatchKey = new BasicBatchKey(getRole() + "#DELETEROW", deleteExpectation);
                }
                final String sql = getSQLDeleteRowString();

                PreparedStatement st = null;
                // update removed rows fks to null
                try {
                    int i = 0;
                    Iterator entries = collection.entries(this);
                    int offset = 1;
                    while (entries.hasNext()) {
                        Object entry = entries.next();
                        if (collection.needsUpdating(entry, i, elementType)) { // will still be issued when it used to be null
                            if (useBatch) {
                                st = session.getJdbcCoordinator().getBatch(deleteRowBatchKey).getBatchStatement(sql,
                                        isDeleteCallable());
                            } else {
                                st = session.getJdbcCoordinator().getStatementPreparer().prepareStatement(sql,
                                        isDeleteCallable());
                            }
                            int loc = writeKey(st, id, offset, session);
                            writeElementToWhere(st, collection.getSnapshotElement(entry, i), loc, session);
                            if (useBatch) {
                                session.getJdbcCoordinator().getBatch(deleteRowBatchKey).addToBatch();
                            } else {
                                deleteExpectation.verifyOutcome(
                                        session.getJdbcCoordinator().getResultSetReturn().executeUpdate(st), st,
                                        -1);
                            }
                            count++;
                        }
                        i++;
                    }
                } catch (SQLException e) {
                    if (useBatch) {
                        session.getJdbcCoordinator().abortBatch();
                    }
                    throw e;
                } finally {
                    if (!useBatch) {
                        session.getJdbcCoordinator().getResourceRegistry().release(st);
                        session.getJdbcCoordinator().afterStatementExecution();
                    }
                }
            }

            if (isRowInsertEnabled()) {
                final Expectation insertExpectation = Expectations.appropriateExpectation(getInsertCheckStyle());
                boolean useBatch = insertExpectation.canBeBatched();
                boolean callable = isInsertCallable();
                if (useBatch && insertRowBatchKey == null) {
                    insertRowBatchKey = new BasicBatchKey(getRole() + "#INSERTROW", insertExpectation);
                }
                final String sql = getSQLInsertRowString();

                PreparedStatement st = null;
                // now update all changed or added rows fks
                try {
                    int i = 0;
                    Iterator entries = collection.entries(this);
                    while (entries.hasNext()) {
                        Object entry = entries.next();
                        int offset = 1;
                        if (collection.needsUpdating(entry, i, elementType)) {
                            if (useBatch) {
                                st = session.getJdbcCoordinator().getBatch(insertRowBatchKey).getBatchStatement(sql,
                                        callable);
                            } else {
                                st = session.getJdbcCoordinator().getStatementPreparer().prepareStatement(sql,
                                        callable);
                            }

                            offset += insertExpectation.prepare(st);

                            int loc = writeKey(st, id, offset, session);
                            if (hasIndex && !indexContainsFormula) {
                                loc = writeIndexToWhere(st, collection.getIndex(entry, i, this), loc, session);
                            }

                            writeElementToWhere(st, collection.getElement(entry), loc, session);

                            if (useBatch) {
                                session.getJdbcCoordinator().getBatch(insertRowBatchKey).addToBatch();
                            } else {
                                insertExpectation.verifyOutcome(
                                        session.getJdbcCoordinator().getResultSetReturn().executeUpdate(st), st,
                                        -1);
                            }
                            count++;
                        }
                        i++;
                    }
                } catch (SQLException sqle) {
                    if (useBatch) {
                        session.getJdbcCoordinator().abortBatch();
                    }
                    throw sqle;
                } finally {
                    if (!useBatch) {
                        session.getJdbcCoordinator().getResourceRegistry().release(st);
                        session.getJdbcCoordinator().afterStatementExecution();
                    }
                }
            }

            return count;
        } catch (SQLException sqle) {
            throw getFactory().getSQLExceptionHelper().convert(sqle,
                    "could not update collection rows: "
                            + MessageHelper.collectionInfoString(this, collection, id, session),
                    getSQLInsertRowString());
        }
    }

    public String selectFragment(Joinable rhs, String rhsAlias, String lhsAlias, String entitySuffix,
            String collectionSuffix, boolean includeCollectionColumns) {
        StringBuilder buf = new StringBuilder();
        if (includeCollectionColumns) {
            //         buf.append( selectFragment( lhsAlias, "" ) )//ignore suffix for collection columns!
            buf.append(selectFragment(lhsAlias, collectionSuffix)).append(", ");
        }
        OuterJoinLoadable ojl = (OuterJoinLoadable) getElementPersister();
        return buf.append(ojl.selectFragment(lhsAlias, entitySuffix))//use suffix for the entity columns
                .toString();
    }

    /**
     * Create the <tt>OneToManyLoader</tt>
     *
     * @see org.hibernate.loader.collection.OneToManyLoader
     */
    @Override
    protected CollectionInitializer createCollectionInitializer(LoadQueryInfluencers loadQueryInfluencers)
            throws MappingException {
        return BatchingCollectionInitializerBuilder.getBuilder(getFactory())
                .createBatchingOneToManyInitializer(this, batchSize, getFactory(), loadQueryInfluencers);
    }

    @Override
    public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
        return ((Joinable) getElementPersister()).fromJoinFragment(alias, innerJoin, includeSubclasses);
    }

    @Override
    public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses,
            Set<String> treatAsDeclarations) {
        return ((Joinable) getElementPersister()).fromJoinFragment(alias, innerJoin, includeSubclasses,
                treatAsDeclarations);
    }

    @Override
    public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
        return ((Joinable) getElementPersister()).whereJoinFragment(alias, innerJoin, includeSubclasses);
    }

    @Override
    public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses,
            Set<String> treatAsDeclarations) {
        return ((Joinable) getElementPersister()).whereJoinFragment(alias, innerJoin, includeSubclasses,
                treatAsDeclarations);
    }

    @Override
    public String getTableName() {
        return ((Joinable) getElementPersister()).getTableName();
    }

    @Override
    public String filterFragment(String alias) throws MappingException {
        String result = super.filterFragment(alias);
        if (getElementPersister() instanceof Joinable) {
            result += ((Joinable) getElementPersister()).oneToManyFilterFragment(alias);
        }
        return result;

    }

    @Override
    protected String filterFragment(String alias, Set<String> treatAsDeclarations) throws MappingException {
        String result = super.filterFragment(alias);
        if (getElementPersister() instanceof Joinable) {
            result += ((Joinable) getElementPersister()).oneToManyFilterFragment(alias, treatAsDeclarations);
        }
        return result;
    }

    @Override
    protected CollectionInitializer createSubselectInitializer(SubselectFetch subselect,
            SharedSessionContractImplementor session) {
        return new SubselectOneToManyLoader(this,
                subselect.toSubselectString(getCollectionType().getLHSPropertyName()), subselect.getResult(),
                subselect.getQueryParameters(), subselect.getNamedParameterLocMap(), session.getFactory(),
                session.getLoadQueryInfluencers());
    }

    @Override
    public Object getElementByIndex(Serializable key, Object index, SharedSessionContractImplementor session,
            Object owner) {
        return new CollectionElementLoader(this, getFactory(), session.getLoadQueryInfluencers())
                .loadElement(session, key, incrementIndexByBase(index));
    }

    @Override
    public FilterAliasGenerator getFilterAliasGenerator(String rootAlias) {
        return getElementPersister().getFilterAliasGenerator(rootAlias);
    }

}