Java tutorial
/* * Copyright 2011-2013 SpringSource. * * 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.grails.orm.hibernate; import groovy.lang.Closure; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.codehaus.groovy.runtime.DefaultGroovyMethods; import org.grails.orm.hibernate.support.HibernateVersionSupport; import org.hibernate.*; import org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.event.spi.EventSource; import org.hibernate.exception.GenericJDBCException; import org.springframework.dao.DataAccessException; import org.springframework.dao.DataAccessResourceFailureException; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.jdbc.datasource.ConnectionHolder; import org.springframework.jdbc.datasource.DataSourceUtils; import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy; import org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator; import org.springframework.jdbc.support.SQLExceptionTranslator; import org.springframework.orm.hibernate5.SessionFactoryUtils; import org.springframework.orm.hibernate5.SessionHolder; import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.Assert; import javax.sql.DataSource; import java.io.Serializable; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class GrailsHibernateTemplate implements IHibernateTemplate { private static final Log LOG = LogFactory.getLog(GrailsHibernateTemplate.class); private boolean osivReadOnly; private boolean passReadOnlyToHibernate = false; protected boolean exposeNativeSession = true; protected boolean cacheQueries = false; protected SessionFactory sessionFactory; protected DataSource dataSource = null; protected SQLExceptionTranslator jdbcExceptionTranslator; protected int flushMode = FLUSH_AUTO; private boolean applyFlushModeOnlyToNonExistingTransactions = false; public interface HibernateCallback<T> { T doInHibernate(Session session) throws HibernateException, SQLException; } protected GrailsHibernateTemplate() { // for testing } public GrailsHibernateTemplate(SessionFactory sessionFactory) { Assert.notNull(sessionFactory, "Property 'sessionFactory' is required"); this.sessionFactory = sessionFactory; ConnectionProvider connectionProvider = ((SessionFactoryImplementor) sessionFactory).getServiceRegistry() .getService(ConnectionProvider.class); if (connectionProvider instanceof DatasourceConnectionProviderImpl) { this.dataSource = ((DatasourceConnectionProviderImpl) connectionProvider).getDataSource(); if (dataSource instanceof TransactionAwareDataSourceProxy) { this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource(); } jdbcExceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(dataSource); } else { // must be in unit test mode, setup default translator SQLErrorCodeSQLExceptionTranslator sqlErrorCodeSQLExceptionTranslator = new SQLErrorCodeSQLExceptionTranslator(); sqlErrorCodeSQLExceptionTranslator.setDatabaseProductName("H2"); jdbcExceptionTranslator = sqlErrorCodeSQLExceptionTranslator; } } public GrailsHibernateTemplate(SessionFactory sessionFactory, HibernateDatastore datastore) { this(sessionFactory, datastore, FLUSH_AUTO); } public GrailsHibernateTemplate(SessionFactory sessionFactory, HibernateDatastore datastore, int defaultFlushMode) { this(sessionFactory); if (datastore != null) { cacheQueries = datastore.isCacheQueries(); this.osivReadOnly = datastore.isOsivReadOnly(); this.passReadOnlyToHibernate = datastore.isPassReadOnlyToHibernate(); } this.flushMode = defaultFlushMode; } @Override public <T> T execute(Closure<T> callable) { HibernateCallback<T> hibernateCallback = DefaultGroovyMethods.asType(callable, HibernateCallback.class); return execute(hibernateCallback); } @Override public <T> T executeWithNewSession(final Closure<T> callable) { SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SessionHolder previousHolder = sessionHolder; ConnectionHolder previousConnectionHolder = (ConnectionHolder) TransactionSynchronizationManager .getResource(dataSource); Session newSession = null; boolean previousActiveSynchronization = TransactionSynchronizationManager.isSynchronizationActive(); List<TransactionSynchronization> transactionSynchronizations = previousActiveSynchronization ? TransactionSynchronizationManager.getSynchronizations() : null; try { // if there are any previous synchronizations active we need to clear them and restore them later (see finally block) if (previousActiveSynchronization) { TransactionSynchronizationManager.clearSynchronization(); // init a new synchronization to ensure that any opened database connections are closed by the synchronization TransactionSynchronizationManager.initSynchronization(); } // if there are already bound holders, unbind them so they can be restored later if (sessionHolder != null) { TransactionSynchronizationManager.unbindResource(sessionFactory); if (previousConnectionHolder != null) { TransactionSynchronizationManager.unbindResource(dataSource); } } // create and bind a new session holder for the new session newSession = sessionFactory.openSession(); sessionHolder = new SessionHolder(newSession); TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); return execute(new HibernateCallback<T>() { @Override public T doInHibernate(Session session) throws HibernateException, SQLException { return callable.call(session); } }); } finally { try { // if an active synchronization was registered during the life time of the new session clear it if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager.clearSynchronization(); } // If there is a synchronization active then leave it to the synchronization to close the session if (newSession != null) { SessionFactoryUtils.closeSession(newSession); } // Clear any bound sessions and connections TransactionSynchronizationManager.unbindResource(sessionFactory); ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager .unbindResourceIfPossible(dataSource); // if there is a connection holder and it holds an open connection close it try { if (connectionHolder != null && !connectionHolder.getConnection().isClosed()) { Connection conn = connectionHolder.getConnection(); DataSourceUtils.releaseConnection(conn, dataSource); } } catch (SQLException e) { // ignore, connection closed already? if (LOG.isDebugEnabled()) { LOG.debug( "Could not close opened JDBC connection. Did the application close the connection manually?: " + e.getMessage()); } } } finally { // if there were previously active synchronizations then register those again if (previousActiveSynchronization) { TransactionSynchronizationManager.initSynchronization(); for (TransactionSynchronization transactionSynchronization : transactionSynchronizations) { TransactionSynchronizationManager.registerSynchronization(transactionSynchronization); } } // now restore any previous state if (previousHolder != null) { TransactionSynchronizationManager.bindResource(sessionFactory, previousHolder); if (previousConnectionHolder != null) { TransactionSynchronizationManager.bindResource(dataSource, previousConnectionHolder); } } } } } public SessionFactory getSessionFactory() { return sessionFactory; } @Override public void applySettings(Query query) { if (exposeNativeSession) { prepareQuery(query); } } @Override public void applySettings(Criteria criteria) { if (exposeNativeSession) { prepareCriteria(criteria); } } public void setCacheQueries(boolean cacheQueries) { this.cacheQueries = cacheQueries; } public boolean isCacheQueries() { return cacheQueries; } public <T> T execute(HibernateCallback<T> action) throws DataAccessException { return doExecute(action, false); } public List<?> executeFind(HibernateCallback<?> action) throws DataAccessException { Object result = doExecute(action, false); if (result != null && !(result instanceof List)) { throw new InvalidDataAccessApiUsageException( "Result object returned from HibernateCallback isn't a List: [" + result + "]"); } return (List<?>) result; } protected boolean shouldPassReadOnlyToHibernate() { if ((passReadOnlyToHibernate || osivReadOnly) && TransactionSynchronizationManager.hasResource(getSessionFactory())) { if (TransactionSynchronizationManager.isActualTransactionActive()) { return passReadOnlyToHibernate && TransactionSynchronizationManager.isCurrentTransactionReadOnly(); } else { return osivReadOnly; } } else { return false; } } public boolean isOsivReadOnly() { return osivReadOnly; } public void setOsivReadOnly(boolean osivReadOnly) { this.osivReadOnly = osivReadOnly; } /** * Execute the action specified by the given action object within a Session. * * @param action callback object that specifies the Hibernate action * @param enforceNativeSession whether to enforce exposure of the native Hibernate Session to callback code * @return a result object returned by the action, or <code>null</code> * @throws org.springframework.dao.DataAccessException in case of Hibernate errors */ protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNativeSession) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Session session = getSession(); boolean existingTransaction = isSessionTransactional(session); if (existingTransaction) { LOG.debug("Found thread-bound Session for HibernateTemplate"); } FlushMode previousFlushMode = null; try { previousFlushMode = applyFlushMode(session, existingTransaction); if (shouldPassReadOnlyToHibernate()) { session.setDefaultReadOnly(true); } Session sessionToExpose = (enforceNativeSession || exposeNativeSession ? session : createSessionProxy(session)); T result = action.doInHibernate(sessionToExpose); flushIfNecessary(session, existingTransaction); return result; } catch (HibernateException ex) { throw convertHibernateAccessException(ex); } catch (SQLException ex) { throw jdbcExceptionTranslator.translate("Hibernate-related JDBC operation", null, ex); } catch (RuntimeException ex) { // Callback code threw application exception... throw ex; } finally { if (existingTransaction) { LOG.debug("Not closing pre-bound Hibernate Session after HibernateTemplate"); if (previousFlushMode != null) { session.setFlushMode(previousFlushMode); } } else { SessionFactoryUtils.closeSession(session); } } } protected boolean isSessionTransactional(Session session) { SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); return sessionHolder != null && sessionHolder.getSession() == session; } protected Session getSession() { try { return sessionFactory.getCurrentSession(); } catch (HibernateException ex) { throw new DataAccessResourceFailureException("Could not obtain current Hibernate Session", ex); } } /** * Create a close-suppressing proxy for the given Hibernate Session. The * proxy also prepares returned Query and Criteria objects. * * @param session the Hibernate Session to create a proxy for * @return the Session proxy * @see org.hibernate.Session#close() * @see #prepareQuery * @see #prepareCriteria */ protected Session createSessionProxy(Session session) { Class<?>[] sessionIfcs = null; Class<?> mainIfc = Session.class; if (session instanceof EventSource) { sessionIfcs = new Class[] { mainIfc, EventSource.class }; } else if (session instanceof SessionImplementor) { sessionIfcs = new Class[] { mainIfc, SessionImplementor.class }; } else { sessionIfcs = new Class[] { mainIfc }; } return (Session) Proxy.newProxyInstance(session.getClass().getClassLoader(), sessionIfcs, new CloseSuppressingInvocationHandler(session)); } public <T> T get(final Class<T> entityClass, final Serializable id) throws DataAccessException { return doExecute(new HibernateCallback<T>() { @SuppressWarnings("unchecked") public T doInHibernate(Session session) throws HibernateException { return (T) session.get(entityClass, id); } }, true); } public <T> T get(final Class<T> entityClass, final Serializable id, final LockMode mode) { return lock(entityClass, id, mode); } public void delete(final Object entity) throws DataAccessException { doExecute(new HibernateCallback<Object>() { public Object doInHibernate(Session session) throws HibernateException { session.delete(entity); return null; } }, true); } public void flush(final Object entity) throws DataAccessException { doExecute(new HibernateCallback<Object>() { public Object doInHibernate(Session session) throws HibernateException { session.flush(); return null; } }, true); } public <T> T load(final Class<T> entityClass, final Serializable id) throws DataAccessException { return doExecute(new HibernateCallback<T>() { @SuppressWarnings("unchecked") public T doInHibernate(Session session) throws HibernateException { return (T) session.load(entityClass, id); } }, true); } public <T> T lock(final Class<T> entityClass, final Serializable id, final LockMode lockMode) throws DataAccessException { return doExecute(new HibernateCallback<T>() { @SuppressWarnings("unchecked") public T doInHibernate(Session session) throws HibernateException { return (T) session.get(entityClass, id, new LockOptions(lockMode)); } }, true); } public <T> List<T> loadAll(final Class<T> entityClass) throws DataAccessException { return doExecute(new HibernateCallback<List<T>>() { @SuppressWarnings("unchecked") public List<T> doInHibernate(Session session) throws HibernateException { Criteria criteria = session.createCriteria(entityClass); criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); prepareCriteria(criteria); return criteria.list(); } }, true); } public boolean contains(final Object entity) throws DataAccessException { return doExecute(new HibernateCallback<Boolean>() { public Boolean doInHibernate(Session session) { return session.contains(entity); } }, true); } public void evict(final Object entity) throws DataAccessException { doExecute(new HibernateCallback<Object>() { public Object doInHibernate(Session session) throws HibernateException { session.evict(entity); return null; } }, true); } public void lock(final Object entity, final LockMode lockMode) throws DataAccessException { doExecute(new HibernateCallback<Object>() { public Object doInHibernate(Session session) throws HibernateException { session.buildLockRequest(new LockOptions(lockMode)).lock(entity);//LockMode.PESSIMISTIC_WRITE return null; } }, true); } public void refresh(final Object entity) throws DataAccessException { refresh(entity, null); } public void refresh(final Object entity, final LockMode lockMode) throws DataAccessException { doExecute(new HibernateCallback<Object>() { public Object doInHibernate(Session session) throws HibernateException { if (lockMode == null) { session.refresh(entity); } else { session.refresh(entity, new LockOptions(lockMode)); } return null; } }, true); } public void setExposeNativeSession(boolean exposeNativeSession) { this.exposeNativeSession = exposeNativeSession; } public boolean isExposeNativeSession() { return exposeNativeSession; } /** * Prepare the given Query object, applying cache settings and/or a * transaction timeout. * * @param query the Query object to prepare */ protected void prepareQuery(Query query) { if (cacheQueries) { query.setCacheable(true); } if (shouldPassReadOnlyToHibernate()) { query.setReadOnly(true); } SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if (sessionHolder != null && sessionHolder.hasTimeout()) { query.setTimeout(sessionHolder.getTimeToLiveInSeconds()); } } /** * Prepare the given Criteria object, applying cache settings and/or a * transaction timeout. * * @param criteria the Criteria object to prepare */ protected void prepareCriteria(Criteria criteria) { if (cacheQueries) { criteria.setCacheable(true); } if (shouldPassReadOnlyToHibernate()) { criteria.setReadOnly(true); } SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if (sessionHolder != null && sessionHolder.hasTimeout()) { criteria.setTimeout(sessionHolder.getTimeToLiveInSeconds()); } } /** * Invocation handler that suppresses close calls on Hibernate Sessions. * Also prepares returned Query and Criteria objects. * * @see org.hibernate.Session#close */ protected class CloseSuppressingInvocationHandler implements InvocationHandler { protected final Session target; protected CloseSuppressingInvocationHandler(Session target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Invocation on Session interface coming in... if (method.getName().equals("equals")) { // Only consider equal when proxies are identical. return (proxy == args[0]); } if (method.getName().equals("hashCode")) { // Use hashCode of Session proxy. return System.identityHashCode(proxy); } if (method.getName().equals("close")) { // Handle close method: suppress, not valid. return null; } // Invoke method on target Session. try { Object retVal = method.invoke(target, args); // If return value is a Query or Criteria, apply transaction timeout. // Applies to createQuery, getNamedQuery, createCriteria. if (retVal instanceof Query) { prepareQuery(((Query) retVal)); } if (retVal instanceof Criteria) { prepareCriteria(((Criteria) retVal)); } return retVal; } catch (InvocationTargetException ex) { throw ex.getTargetException(); } } } /** * 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; /** * Set the flush behavior to one of the constants in this class. Default is * FLUSH_AUTO. * * @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 flushMode; } /** * 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</code> if none * @see #setFlushMode * @see org.hibernate.Session#setFlushMode */ protected FlushMode applyFlushMode(Session session, boolean existingTransaction) { if (isApplyFlushModeOnlyToNonExistingTransactions() && existingTransaction) { return null; } if (getFlushMode() == FLUSH_NEVER) { if (existingTransaction) { FlushMode previousFlushMode = HibernateVersionSupport.getFlushMode(session); 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 = HibernateVersionSupport.getFlushMode(session); 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 = HibernateVersionSupport.getFlushMode(session); 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 = HibernateVersionSupport.getFlushMode(session); if (!previousFlushMode.equals(FlushMode.ALWAYS)) { session.setFlushMode(FlushMode.ALWAYS); return previousFlushMode; } } else { session.setFlushMode(FlushMode.ALWAYS); } } return null; } protected void flushIfNecessary(Session session, boolean existingTransaction) throws HibernateException { if (getFlushMode() == FLUSH_EAGER || (!existingTransaction && getFlushMode() != FLUSH_NEVER)) { LOG.debug("Eagerly flushing Hibernate session"); session.flush(); } } protected DataAccessException convertHibernateAccessException(HibernateException ex) { if (ex instanceof JDBCException) { return convertJdbcAccessException((JDBCException) ex, jdbcExceptionTranslator); } if (GenericJDBCException.class.equals(ex.getClass())) { return convertJdbcAccessException((GenericJDBCException) ex, jdbcExceptionTranslator); } return SessionFactoryUtils.convertHibernateAccessException(ex); } protected DataAccessException convertJdbcAccessException(JDBCException ex, SQLExceptionTranslator translator) { String msg = ex.getMessage(); String sql = ex.getSQL(); SQLException sqlException = ex.getSQLException(); return translator.translate("Hibernate operation: " + msg, sql, sqlException); } public Serializable save(Object o) { return sessionFactory.getCurrentSession().save(o); } public void flush() { sessionFactory.getCurrentSession().flush(); } public void clear() { sessionFactory.getCurrentSession().clear(); } public void deleteAll(final Collection<?> objects) { execute(new GrailsHibernateTemplate.HibernateCallback<Void>() { public Void doInHibernate(Session session) throws HibernateException { for (Object entity : getIterableAsCollection(objects)) { session.delete(entity); } return null; } }); } @SuppressWarnings({ "rawtypes", "unchecked" }) protected Collection getIterableAsCollection(Iterable objects) { Collection list; if (objects instanceof Collection) { list = (Collection) objects; } else { list = new ArrayList(); for (Object object : objects) { list.add(object); } } return list; } public boolean isApplyFlushModeOnlyToNonExistingTransactions() { return applyFlushModeOnlyToNonExistingTransactions; } public void setApplyFlushModeOnlyToNonExistingTransactions( boolean applyFlushModeOnlyToNonExistingTransactions) { this.applyFlushModeOnlyToNonExistingTransactions = applyFlushModeOnlyToNonExistingTransactions; } }