001/* 002 * Copyright 2002-2016 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.orm.hibernate5.support; 018 019import java.io.IOException; 020import javax.servlet.FilterChain; 021import javax.servlet.ServletException; 022import javax.servlet.http.HttpServletRequest; 023import javax.servlet.http.HttpServletResponse; 024 025import org.hibernate.FlushMode; 026import org.hibernate.HibernateException; 027import org.hibernate.Session; 028import org.hibernate.SessionFactory; 029 030import org.springframework.dao.DataAccessResourceFailureException; 031import org.springframework.orm.hibernate5.SessionFactoryUtils; 032import org.springframework.orm.hibernate5.SessionHolder; 033import org.springframework.transaction.support.TransactionSynchronizationManager; 034import org.springframework.web.context.WebApplicationContext; 035import org.springframework.web.context.request.async.WebAsyncManager; 036import org.springframework.web.context.request.async.WebAsyncUtils; 037import org.springframework.web.context.support.WebApplicationContextUtils; 038import org.springframework.web.filter.OncePerRequestFilter; 039 040/** 041 * Servlet Filter that binds a Hibernate Session to the thread for the entire 042 * processing of the request. Intended for the "Open Session in View" pattern, 043 * i.e. to allow for lazy loading in web views despite the original transactions 044 * already being completed. 045 * 046 * <p>This filter makes Hibernate Sessions available via the current thread, which 047 * will be autodetected by transaction managers. It is suitable for service layer 048 * transactions via {@link org.springframework.orm.hibernate5.HibernateTransactionManager} 049 * as well as for non-transactional execution (if configured appropriately). 050 * 051 * <p><b>NOTE</b>: This filter will by default <i>not</i> flush the Hibernate Session, 052 * with the flush mode set to {@code FlushMode.NEVER}. It assumes to be used 053 * in combination with service layer transactions that care for the flushing: The 054 * active transaction manager will temporarily change the flush mode to 055 * {@code FlushMode.AUTO} during a read-write transaction, with the flush 056 * mode reset to {@code FlushMode.NEVER} at the end of each transaction. 057 * 058 * <p><b>WARNING:</b> Applying this filter to existing logic can cause issues that 059 * have not appeared before, through the use of a single Hibernate Session for the 060 * processing of an entire request. In particular, the reassociation of persistent 061 * objects with a Hibernate Session has to occur at the very beginning of request 062 * processing, to avoid clashes with already loaded instances of the same objects. 063 * 064 * <p>Looks up the SessionFactory in Spring's root web application context. 065 * Supports a "sessionFactoryBeanName" filter init-param in {@code web.xml}; 066 * the default bean name is "sessionFactory". 067 * 068 * @author Juergen Hoeller 069 * @since 4.2 070 * @see #lookupSessionFactory 071 * @see OpenSessionInViewInterceptor 072 * @see OpenSessionInterceptor 073 * @see org.springframework.orm.hibernate5.HibernateTransactionManager 074 * @see TransactionSynchronizationManager 075 * @see SessionFactory#getCurrentSession() 076 */ 077public class OpenSessionInViewFilter extends OncePerRequestFilter { 078 079 public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory"; 080 081 private String sessionFactoryBeanName = DEFAULT_SESSION_FACTORY_BEAN_NAME; 082 083 084 /** 085 * Set the bean name of the SessionFactory to fetch from Spring's 086 * root application context. Default is "sessionFactory". 087 * @see #DEFAULT_SESSION_FACTORY_BEAN_NAME 088 */ 089 public void setSessionFactoryBeanName(String sessionFactoryBeanName) { 090 this.sessionFactoryBeanName = sessionFactoryBeanName; 091 } 092 093 /** 094 * Return the bean name of the SessionFactory to fetch from Spring's 095 * root application context. 096 */ 097 protected String getSessionFactoryBeanName() { 098 return this.sessionFactoryBeanName; 099 } 100 101 102 /** 103 * Returns "false" so that the filter may re-bind the opened Hibernate 104 * {@code Session} to each asynchronously dispatched thread and postpone 105 * closing it until the very last asynchronous dispatch. 106 */ 107 @Override 108 protected boolean shouldNotFilterAsyncDispatch() { 109 return false; 110 } 111 112 /** 113 * Returns "false" so that the filter may provide a Hibernate 114 * {@code Session} to each error dispatches. 115 */ 116 @Override 117 protected boolean shouldNotFilterErrorDispatch() { 118 return false; 119 } 120 121 @Override 122 protected void doFilterInternal( 123 HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 124 throws ServletException, IOException { 125 126 SessionFactory sessionFactory = lookupSessionFactory(request); 127 boolean participate = false; 128 129 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 130 String key = getAlreadyFilteredAttributeName(); 131 132 if (TransactionSynchronizationManager.hasResource(sessionFactory)) { 133 // Do not modify the Session: just set the participate flag. 134 participate = true; 135 } 136 else { 137 boolean isFirstRequest = !isAsyncDispatch(request); 138 if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) { 139 logger.debug("Opening Hibernate Session in OpenSessionInViewFilter"); 140 Session session = openSession(sessionFactory); 141 SessionHolder sessionHolder = new SessionHolder(session); 142 TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); 143 144 AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder); 145 asyncManager.registerCallableInterceptor(key, interceptor); 146 asyncManager.registerDeferredResultInterceptor(key, interceptor); 147 } 148 } 149 150 try { 151 filterChain.doFilter(request, response); 152 } 153 154 finally { 155 if (!participate) { 156 SessionHolder sessionHolder = 157 (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory); 158 if (!isAsyncStarted(request)) { 159 logger.debug("Closing Hibernate Session in OpenSessionInViewFilter"); 160 SessionFactoryUtils.closeSession(sessionHolder.getSession()); 161 } 162 } 163 } 164 } 165 166 /** 167 * Look up the SessionFactory that this filter should use, 168 * taking the current HTTP request as argument. 169 * <p>The default implementation delegates to the {@link #lookupSessionFactory()} 170 * variant without arguments. 171 * @param request the current request 172 * @return the SessionFactory to use 173 */ 174 protected SessionFactory lookupSessionFactory(HttpServletRequest request) { 175 return lookupSessionFactory(); 176 } 177 178 /** 179 * Look up the SessionFactory that this filter should use. 180 * <p>The default implementation looks for a bean with the specified name 181 * in Spring's root application context. 182 * @return the SessionFactory to use 183 * @see #getSessionFactoryBeanName 184 */ 185 protected SessionFactory lookupSessionFactory() { 186 if (logger.isDebugEnabled()) { 187 logger.debug("Using SessionFactory '" + getSessionFactoryBeanName() + "' for OpenSessionInViewFilter"); 188 } 189 WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); 190 return wac.getBean(getSessionFactoryBeanName(), SessionFactory.class); 191 } 192 193 /** 194 * Open a Session for the SessionFactory that this filter uses. 195 * <p>The default implementation delegates to the {@link SessionFactory#openSession} 196 * method and sets the {@link Session}'s flush mode to "MANUAL". 197 * @param sessionFactory the SessionFactory that this filter uses 198 * @return the Session to use 199 * @throws DataAccessResourceFailureException if the Session could not be created 200 * @see FlushMode#MANUAL 201 */ 202 @SuppressWarnings("deprecation") 203 protected Session openSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException { 204 try { 205 Session session = sessionFactory.openSession(); 206 session.setFlushMode(FlushMode.MANUAL); 207 return session; 208 } 209 catch (HibernateException ex) { 210 throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex); 211 } 212 } 213 214 private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, String key) { 215 if (asyncManager.getCallableInterceptor(key) == null) { 216 return false; 217 } 218 ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); 219 return true; 220 } 221 222}