org.springframework.orm.hibernate.support.OpenSessionInViewFilter Maven / Gradle / Ivy
/*
* Copyright 2002-2005 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.hibernate.support;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.hibernate.FlushMode;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import org.springframework.dao.DataAccessResourceFailureException;
import org.springframework.orm.hibernate.SessionFactoryUtils;
import org.springframework.orm.hibernate.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* Servlet 2.3 Filter that binds a Hibernate Session to the thread for the entire
* processing of the request. Intended for the "Open Session in View" pattern,
* i.e. to allow for lazy loading in web views despite the original transactions
* already being completed.
*
* This filter works similar to the AOP HibernateInterceptor: It just makes
* Hibernate Sessions available via the thread. It is suitable for non-transactional
* execution but also for business layer transactions via HibernateTransactionManager
* or JtaTransactionManager. In the latter case, Sessions pre-bound by this filter
* will automatically be used for the transactions and flushed accordingly.
*
*
WARNING: Applying this filter to existing logic can cause issues that
* have not appeared before, through the use of a single Hibernate Session for the
* processing of an entire request. In particular, the reassociation of persistent
* objects with a Hibernate Session has to occur at the very beginning of request
* processing, to avoid clashes will already loaded instances of the same objects.
*
*
Alternatively, turn this filter into deferred close mode, by specifying
* "singleSession"="false": It will not use a single session per request then,
* but rather let each data access operation or transaction use its own session
* (like without Open Session in View). Each of those sessions will be registered
* for deferred close, though, actually processed at request completion.
*
*
A single session per request allows for most efficient first-level caching,
* but can cause side effects, for example on saveOrUpdate or if continuing
* after a rolled-back transaction. The deferred close strategy is as safe as
* no Open Session in View in that respect, while still allowing for lazy loading
* in views (but not providing a first-level cache for the entire request).
*
*
Looks up the SessionFactory in Spring's root web application context.
* Supports a "sessionFactoryBeanName" filter init-param in web.xml
;
* the default bean name is "sessionFactory". Looks up the SessionFactory on each
* request, to avoid initialization order issues (when using ContextLoaderServlet,
* the root application context will get initialized after this filter).
*
*
NOTE: This filter will by default not flush the Hibernate session, as
* it assumes to be used in combination with business layer transactions that care for
* the flushing, or HibernateAccessors with flushMode FLUSH_EAGER. If you want this
* filter to flush after completed request processing, override closeSession and
* invoke flush on the Session before closing it. Note that closeSession will just
* be invoked in single session mode!
*
* @author Juergen Hoeller
* @since 06.12.2003
* @see #setSingleSession
* @see #closeSession
* @see #lookupSessionFactory
* @see OpenSessionInViewInterceptor
* @see org.springframework.orm.hibernate.HibernateInterceptor
* @see org.springframework.orm.hibernate.HibernateTransactionManager
* @see org.springframework.orm.hibernate.SessionFactoryUtils#getSession
* @see org.springframework.transaction.support.TransactionSynchronizationManager
*/
public class OpenSessionInViewFilter extends OncePerRequestFilter {
public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory";
private String sessionFactoryBeanName = DEFAULT_SESSION_FACTORY_BEAN_NAME;
private boolean singleSession = true;
/**
* Set the bean name of the SessionFactory to fetch from Spring's
* root application context. Default is "sessionFactory".
* @see #DEFAULT_SESSION_FACTORY_BEAN_NAME
*/
public void setSessionFactoryBeanName(String sessionFactoryBeanName) {
this.sessionFactoryBeanName = sessionFactoryBeanName;
}
/**
* Return the bean name of the SessionFactory to fetch from Spring's
* root application context.
*/
protected String getSessionFactoryBeanName() {
return sessionFactoryBeanName;
}
/**
* Set whether to use a single session for each request. Default is true.
*
If set to false, each data access operation or transaction will use
* its own session (like without Open Session in View). Each of those
* sessions will be registered for deferred close, though, actually
* processed at request completion.
* @see SessionFactoryUtils#initDeferredClose
* @see SessionFactoryUtils#processDeferredClose
*/
public void setSingleSession(boolean singleSession) {
this.singleSession = singleSession;
}
/**
* Return whether to use a single session for each request.
*/
protected boolean isSingleSession() {
return singleSession;
}
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
SessionFactory sessionFactory = lookupSessionFactory(request);
Session session = null;
boolean participate = false;
if (isSingleSession()) {
// single session mode
if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
// Do not modify the Session: just set the participate flag.
participate = true;
}
else {
logger.debug("Opening single Hibernate session in OpenSessionInViewFilter");
session = getSession(sessionFactory);
TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
}
}
else {
// deferred close mode
if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
// Do not modify deferred close: just set the participate flag.
participate = true;
}
else {
SessionFactoryUtils.initDeferredClose(sessionFactory);
}
}
try {
filterChain.doFilter(request, response);
}
finally {
if (!participate) {
if (isSingleSession()) {
// single session mode
TransactionSynchronizationManager.unbindResource(sessionFactory);
logger.debug("Closing single Hibernate session in OpenSessionInViewFilter");
try {
closeSession(session, sessionFactory);
}
catch (RuntimeException ex) {
logger.error("Unexpected exception on closing Hibernate Session", ex);
}
}
else {
// deferred close mode
SessionFactoryUtils.processDeferredClose(sessionFactory);
}
}
}
}
/**
* Look up the SessionFactory that this filter should use,
* taking the current HTTP request as argument.
*
Default implementation delegates to the lookupSessionFactory
* without arguments.
* @return the SessionFactory to use
* @see #lookupSessionFactory()
*/
protected SessionFactory lookupSessionFactory(HttpServletRequest request) {
return lookupSessionFactory();
}
/**
* Look up the SessionFactory that this filter should use.
*
Default implementation looks for a bean with the specified name
* in Spring's root application context.
* @return the SessionFactory to use
* @see #getSessionFactoryBeanName
*/
protected SessionFactory lookupSessionFactory() {
if (logger.isDebugEnabled()) {
logger.debug("Using session factory '" + getSessionFactoryBeanName() + "' for OpenSessionInViewFilter");
}
WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
return (SessionFactory) wac.getBean(getSessionFactoryBeanName(), SessionFactory.class);
}
/**
* Get a Session for the SessionFactory that this filter uses.
* Note that this just applies in single session mode!
*
The default implementation delegates to SessionFactoryUtils'
* getSession method and sets the Session's flushMode to NEVER.
*
Can be overridden in subclasses for creating a Session with a custom
* entity interceptor or JDBC exception translator.
* @param sessionFactory the SessionFactory that this filter uses
* @return the Session to use
* @throws DataAccessResourceFailureException if the Session could not be created
* @see org.springframework.orm.hibernate.SessionFactoryUtils#getSession(SessionFactory, boolean)
* @see net.sf.hibernate.FlushMode#NEVER
*/
protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
return openSession(sessionFactory);
}
/**
* Get a Session for the SessionFactory that this filter uses.
* Note that this just applies in single session mode!
*
The default implementation delegates to SessionFactoryUtils'
* getSession method and sets the Session's flushMode to NEVER.
*
Can be overridden in subclasses for creating a Session with a custom
* entity interceptor or JDBC exception translator.
* @param sessionFactory the SessionFactory that this filter uses
* @return the Session to use
* @throws DataAccessResourceFailureException if the Session could not be created
* @see org.springframework.orm.hibernate.SessionFactoryUtils#getSession(SessionFactory, boolean)
* @see net.sf.hibernate.FlushMode#NEVER
*/
protected Session openSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
session.setFlushMode(FlushMode.NEVER);
return session;
}
/**
* Close the given Session.
* Note that this just applies in single session mode!
*
The default implementation delegates to SessionFactoryUtils'
* releaseSession method.
*
Can be overridden in subclasses, e.g. for flushing the Session before
* closing it. See class-level javadoc for a discussion of flush handling.
* Note that you should also override getSession accordingly, to set
* the flush mode to something else than NEVER.
* @param session the Session used for filtering
* @param sessionFactory the SessionFactory that this filter uses
*/
protected void closeSession(Session session, SessionFactory sessionFactory) {
SessionFactoryUtils.releaseSession(session, sessionFactory);
}
}