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