org.springframework.orm.hibernate3.HibernateAccessor.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.orm.hibernate3.HibernateAccessor.java

Source

/*
 * Copyright 2002-2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.orm.hibernate3;

import java.sql.SQLException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.FlushMode;
import org.hibernate.HibernateException;
import org.hibernate.Interceptor;
import org.hibernate.JDBCException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.exception.GenericJDBCException;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Constants;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.support.SQLExceptionTranslator;

/**
 * Base class for {@link HibernateTemplate} and {@link HibernateInterceptor},
 * defining common properties such as SessionFactory and flushing behavior.
 *
 * <p>Not intended to be used directly.
 * See {@link HibernateTemplate} and {@link HibernateInterceptor}.
 *
 * @author Juergen Hoeller
 * @since 1.2
 * @see HibernateTemplate
 * @see HibernateInterceptor
 * @see #setFlushMode
 */
public abstract class HibernateAccessor implements InitializingBean, BeanFactoryAware {

    /**
     * Never flush is a good strategy for read-only units of work.
     * Hibernate will not track and look for changes in this case,
     * avoiding any overhead of modification detection.
     * <p>In case of an existing Session, FLUSH_NEVER will turn the flush mode
     * to NEVER for the scope of the current operation, resetting the previous
     * flush mode afterwards.
     * @see #setFlushMode
     */
    public static final int FLUSH_NEVER = 0;

    /**
     * Automatic flushing is the default mode for a Hibernate Session.
     * A session will get flushed on transaction commit, and on certain find
     * operations that might involve already modified instances, but not
     * after each unit of work like with eager flushing.
     * <p>In case of an existing Session, FLUSH_AUTO will participate in the
     * existing flush mode, not modifying it for the current operation.
     * This in particular means that this setting will not modify an existing
     * flush mode NEVER, in contrast to FLUSH_EAGER.
     * @see #setFlushMode
     */
    public static final int FLUSH_AUTO = 1;

    /**
     * Eager flushing leads to immediate synchronization with the database,
     * even if in a transaction. This causes inconsistencies to show up and throw
     * a respective exception immediately, and JDBC access code that participates
     * in the same transaction will see the changes as the database is already
     * aware of them then. But the drawbacks are:
     * <ul>
     * <li>additional communication roundtrips with the database, instead of a
     * single batch at transaction commit;
     * <li>the fact that an actual database rollback is needed if the Hibernate
     * transaction rolls back (due to already submitted SQL statements).
     * </ul>
     * <p>In case of an existing Session, FLUSH_EAGER will turn the flush mode
     * to AUTO for the scope of the current operation and issue a flush at the
     * end, resetting the previous flush mode afterwards.
     * @see #setFlushMode
     */
    public static final int FLUSH_EAGER = 2;

    /**
     * Flushing at commit only is intended for units of work where no
     * intermediate flushing is desired, not even for find operations
     * that might involve already modified instances.
     * <p>In case of an existing Session, FLUSH_COMMIT will turn the flush mode
     * to COMMIT for the scope of the current operation, resetting the previous
     * flush mode afterwards. The only exception is an existing flush mode
     * NEVER, which will not be modified through this setting.
     * @see #setFlushMode
     */
    public static final int FLUSH_COMMIT = 3;

    /**
     * Flushing before every query statement is rarely necessary.
     * It is only available for special needs.
     * <p>In case of an existing Session, FLUSH_ALWAYS will turn the flush mode
     * to ALWAYS for the scope of the current operation, resetting the previous
     * flush mode afterwards.
     * @see #setFlushMode
     */
    public static final int FLUSH_ALWAYS = 4;

    /** Constants instance for HibernateAccessor */
    private static final Constants constants = new Constants(HibernateAccessor.class);

    /** Logger available to subclasses */
    protected final Log logger = LogFactory.getLog(getClass());

    private SessionFactory sessionFactory;

    private Object entityInterceptor;

    private SQLExceptionTranslator jdbcExceptionTranslator;

    private SQLExceptionTranslator defaultJdbcExceptionTranslator;

    private int flushMode = FLUSH_AUTO;

    private String[] filterNames;

    /**
     * Just needed for entityInterceptorBeanName.
     * @see #setEntityInterceptorBeanName
     */
    private BeanFactory beanFactory;

    /**
     * Set the Hibernate SessionFactory that should be used to create
     * Hibernate Sessions.
     */
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.sessionFactory = sessionFactory;
    }

    /**
     * Return the Hibernate SessionFactory that should be used to create
     * Hibernate Sessions.
     */
    public SessionFactory getSessionFactory() {
        return this.sessionFactory;
    }

    /**
     * Set the bean name of a Hibernate entity interceptor that allows to inspect
     * and change property values before writing to and reading from the database.
     * Will get applied to any new Session created by this transaction manager.
     * <p>Requires the bean factory to be known, to be able to resolve the bean
     * name to an interceptor instance on session creation. Typically used for
     * prototype interceptors, i.e. a new interceptor instance per session.
     * <p>Can also be used for shared interceptor instances, but it is recommended
     * to set the interceptor reference directly in such a scenario.
     * @param entityInterceptorBeanName the name of the entity interceptor in
     * the bean factory
     * @see #setBeanFactory
     * @see #setEntityInterceptor
     */
    public void setEntityInterceptorBeanName(String entityInterceptorBeanName) {
        this.entityInterceptor = entityInterceptorBeanName;
    }

    /**
     * Set a Hibernate entity interceptor that allows to inspect and change
     * property values before writing to and reading from the database.
     * Will get applied to any <b>new</b> Session created by this object.
     * <p>Such an interceptor can either be set at the SessionFactory level,
     * i.e. on LocalSessionFactoryBean, or at the Session level, i.e. on
     * HibernateTemplate, HibernateInterceptor, and HibernateTransactionManager.
     * It's preferable to set it on LocalSessionFactoryBean or HibernateTransactionManager
     * to avoid repeated configuration and guarantee consistent behavior in transactions.
     * @see #setEntityInterceptorBeanName
     * @see LocalSessionFactoryBean#setEntityInterceptor
     * @see HibernateTransactionManager#setEntityInterceptor
     */
    public void setEntityInterceptor(Interceptor entityInterceptor) {
        this.entityInterceptor = entityInterceptor;
    }

    /**
     * Return the current Hibernate entity interceptor, or {@code null} if none.
     * Resolves an entity interceptor bean name via the bean factory,
     * if necessary.
     * @throws IllegalStateException if bean name specified but no bean factory set
     * @throws org.springframework.beans.BeansException if bean name resolution via the bean factory failed
     * @see #setEntityInterceptor
     * @see #setEntityInterceptorBeanName
     * @see #setBeanFactory
     */
    public Interceptor getEntityInterceptor() throws IllegalStateException, BeansException {
        if (this.entityInterceptor instanceof String) {
            if (this.beanFactory == null) {
                throw new IllegalStateException(
                        "Cannot get entity interceptor via bean name if no bean factory set");
            }
            return this.beanFactory.getBean((String) this.entityInterceptor, Interceptor.class);
        }
        return (Interceptor) this.entityInterceptor;
    }

    /**
     * Set the JDBC exception translator for this instance.
     * <p>Applied to any SQLException root cause of a Hibernate JDBCException,
     * overriding Hibernate's default SQLException translation (which is
     * based on Hibernate's Dialect for a specific target database).
     * @param jdbcExceptionTranslator the exception translator
     * @see java.sql.SQLException
     * @see org.hibernate.JDBCException
     * @see org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator
     * @see org.springframework.jdbc.support.SQLStateSQLExceptionTranslator
     */
    public void setJdbcExceptionTranslator(SQLExceptionTranslator jdbcExceptionTranslator) {
        this.jdbcExceptionTranslator = jdbcExceptionTranslator;
    }

    /**
     * Return the JDBC exception translator for this instance, if any.
     */
    public SQLExceptionTranslator getJdbcExceptionTranslator() {
        return this.jdbcExceptionTranslator;
    }

    /**
     * Set the flush behavior by the name of the respective constant
     * in this class, e.g. "FLUSH_AUTO". Default is "FLUSH_AUTO".
     * @param constantName name of the constant
     * @see #setFlushMode
     * @see #FLUSH_AUTO
     */
    public void setFlushModeName(String constantName) {
        setFlushMode(constants.asNumber(constantName).intValue());
    }

    /**
     * Set the flush behavior to one of the constants in this class.
     * Default is FLUSH_AUTO.
     * @see #setFlushModeName
     * @see #FLUSH_AUTO
     */
    public void setFlushMode(int flushMode) {
        this.flushMode = flushMode;
    }

    /**
     * Return if a flush should be forced after executing the callback code.
     */
    public int getFlushMode() {
        return this.flushMode;
    }

    /**
     * Set the name of a Hibernate filter to be activated for all
     * Sessions that this accessor works with.
     * <p>This filter will be enabled at the beginning of each operation
     * and correspondingly disabled at the end of the operation.
     * This will work for newly opened Sessions as well as for existing
     * Sessions (for example, within a transaction).
     * @see #enableFilters(org.hibernate.Session)
     * @see org.hibernate.Session#enableFilter(String)
     * @see LocalSessionFactoryBean#setFilterDefinitions
     */
    public void setFilterName(String filter) {
        this.filterNames = new String[] { filter };
    }

    /**
     * Set one or more names of Hibernate filters to be activated for all
     * Sessions that this accessor works with.
     * <p>Each of those filters will be enabled at the beginning of each
     * operation and correspondingly disabled at the end of the operation.
     * This will work for newly opened Sessions as well as for existing
     * Sessions (for example, within a transaction).
     * @see #enableFilters(org.hibernate.Session)
     * @see org.hibernate.Session#enableFilter(String)
     * @see LocalSessionFactoryBean#setFilterDefinitions
     */
    public void setFilterNames(String... filterNames) {
        this.filterNames = filterNames;
    }

    /**
     * Return the names of Hibernate filters to be activated, if any.
     */
    public String[] getFilterNames() {
        return this.filterNames;
    }

    /**
     * The bean factory just needs to be known for resolving entity interceptor
     * bean names. It does not need to be set for any other mode of operation.
     * @see #setEntityInterceptorBeanName
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    @Override
    public void afterPropertiesSet() {
        if (getSessionFactory() == null) {
            throw new IllegalArgumentException("Property 'sessionFactory' is required");
        }
    }

    /**
     * Apply the flush mode that's been specified for this accessor
     * to the given Session.
     * @param session the current Hibernate Session
     * @param existingTransaction if executing within an existing transaction
     * @return the previous flush mode to restore after the operation,
     * or {@code null} if none
     * @see #setFlushMode
     * @see org.hibernate.Session#setFlushMode
     */
    protected FlushMode applyFlushMode(Session session, boolean existingTransaction) {
        if (getFlushMode() == FLUSH_NEVER) {
            if (existingTransaction) {
                FlushMode previousFlushMode = session.getFlushMode();
                if (!previousFlushMode.lessThan(FlushMode.COMMIT)) {
                    session.setFlushMode(FlushMode.MANUAL);
                    return previousFlushMode;
                }
            } else {
                session.setFlushMode(FlushMode.MANUAL);
            }
        } else if (getFlushMode() == FLUSH_EAGER) {
            if (existingTransaction) {
                FlushMode previousFlushMode = session.getFlushMode();
                if (!previousFlushMode.equals(FlushMode.AUTO)) {
                    session.setFlushMode(FlushMode.AUTO);
                    return previousFlushMode;
                }
            } else {
                // rely on default FlushMode.AUTO
            }
        } else if (getFlushMode() == FLUSH_COMMIT) {
            if (existingTransaction) {
                FlushMode previousFlushMode = session.getFlushMode();
                if (previousFlushMode.equals(FlushMode.AUTO) || previousFlushMode.equals(FlushMode.ALWAYS)) {
                    session.setFlushMode(FlushMode.COMMIT);
                    return previousFlushMode;
                }
            } else {
                session.setFlushMode(FlushMode.COMMIT);
            }
        } else if (getFlushMode() == FLUSH_ALWAYS) {
            if (existingTransaction) {
                FlushMode previousFlushMode = session.getFlushMode();
                if (!previousFlushMode.equals(FlushMode.ALWAYS)) {
                    session.setFlushMode(FlushMode.ALWAYS);
                    return previousFlushMode;
                }
            } else {
                session.setFlushMode(FlushMode.ALWAYS);
            }
        }
        return null;
    }

    /**
     * Flush the given Hibernate Session if necessary.
     * @param session the current Hibernate Session
     * @param existingTransaction if executing within an existing transaction
     * @throws HibernateException in case of Hibernate flushing errors
     */
    protected void flushIfNecessary(Session session, boolean existingTransaction) throws HibernateException {
        if (getFlushMode() == FLUSH_EAGER || (!existingTransaction && getFlushMode() != FLUSH_NEVER)) {
            logger.debug("Eagerly flushing Hibernate session");
            session.flush();
        }
    }

    /**
     * Convert the given HibernateException to an appropriate exception
     * from the {@code org.springframework.dao} hierarchy.
     * <p>Will automatically apply a specified SQLExceptionTranslator to a
     * Hibernate JDBCException, else rely on Hibernate's default translation.
     * @param ex HibernateException that occured
     * @return a corresponding DataAccessException
     * @see SessionFactoryUtils#convertHibernateAccessException
     * @see #setJdbcExceptionTranslator
     */
    public DataAccessException convertHibernateAccessException(HibernateException ex) {
        if (getJdbcExceptionTranslator() != null && ex instanceof JDBCException) {
            return convertJdbcAccessException((JDBCException) ex, getJdbcExceptionTranslator());
        } else if (GenericJDBCException.class.equals(ex.getClass())) {
            return convertJdbcAccessException((GenericJDBCException) ex, getDefaultJdbcExceptionTranslator());
        }
        return SessionFactoryUtils.convertHibernateAccessException(ex);
    }

    /**
     * Convert the given Hibernate JDBCException to an appropriate exception
     * from the {@code org.springframework.dao} hierarchy, using the
     * given SQLExceptionTranslator.
     * @param ex Hibernate JDBCException that occured
     * @param translator the SQLExceptionTranslator to use
     * @return a corresponding DataAccessException
     */
    protected DataAccessException convertJdbcAccessException(JDBCException ex, SQLExceptionTranslator translator) {
        return translator.translate("Hibernate operation: " + ex.getMessage(), ex.getSQL(), ex.getSQLException());
    }

    /**
     * Convert the given SQLException to an appropriate exception from the
     * {@code org.springframework.dao} hierarchy. Can be overridden in subclasses.
     * <p>Note that a direct SQLException can just occur when callback code
     * performs direct JDBC access via {@code Session.connection()}.
     * @param ex the SQLException
     * @return the corresponding DataAccessException instance
     * @see #setJdbcExceptionTranslator
     */
    protected DataAccessException convertJdbcAccessException(SQLException ex) {
        SQLExceptionTranslator translator = getJdbcExceptionTranslator();
        if (translator == null) {
            translator = getDefaultJdbcExceptionTranslator();
        }
        return translator.translate("Hibernate-related JDBC operation", null, ex);
    }

    /**
     * Obtain a default SQLExceptionTranslator, lazily creating it if necessary.
     * <p>Creates a default
     * {@link org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator}
     * for the SessionFactory's underlying DataSource.
     */
    protected synchronized SQLExceptionTranslator getDefaultJdbcExceptionTranslator() {
        if (this.defaultJdbcExceptionTranslator == null) {
            this.defaultJdbcExceptionTranslator = SessionFactoryUtils
                    .newJdbcExceptionTranslator(getSessionFactory());
        }
        return this.defaultJdbcExceptionTranslator;
    }

    /**
     * Enable the specified filters on the given Session.
     * @param session the current Hibernate Session
     * @see #setFilterNames
     * @see org.hibernate.Session#enableFilter(String)
     */
    protected void enableFilters(Session session) {
        String[] filterNames = getFilterNames();
        if (filterNames != null) {
            for (String filterName : filterNames) {
                session.enableFilter(filterName);
            }
        }
    }

    /**
     * Disable the specified filters on the given Session.
     * @param session the current Hibernate Session
     * @see #setFilterNames
     * @see org.hibernate.Session#disableFilter(String)
     */
    protected void disableFilters(Session session) {
        String[] filterNames = getFilterNames();
        if (filterNames != null) {
            for (String filterName : filterNames) {
                session.disableFilter(filterName);
            }
        }
    }

}