001/* 002 * Copyright 2002-2015 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.hibernate3.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.Session; 027import org.hibernate.SessionFactory; 028 029import org.springframework.dao.DataAccessResourceFailureException; 030import org.springframework.transaction.support.TransactionSynchronizationManager; 031import org.springframework.util.Assert; 032import org.springframework.web.context.WebApplicationContext; 033import org.springframework.web.context.request.async.WebAsyncManager; 034import org.springframework.web.context.request.async.WebAsyncUtils; 035import org.springframework.web.context.support.WebApplicationContextUtils; 036import org.springframework.web.filter.OncePerRequestFilter; 037 038/** 039 * Servlet Filter that binds a Hibernate Session to the thread for the entire 040 * processing of the request. Intended for the "Open Session in View" pattern, 041 * i.e. to allow for lazy loading in web views despite the original transactions 042 * already being completed. 043 * 044 * <p>This filter makes Hibernate Sessions available via the current thread, which 045 * will be autodetected by transaction managers. It is suitable for service layer 046 * transactions via {@link org.springframework.orm.hibernate3.HibernateTransactionManager} 047 * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well 048 * as for non-transactional execution (if configured appropriately). 049 * 050 * <p><b>NOTE</b>: This filter will by default <i>not</i> flush the Hibernate Session, 051 * with the flush mode set to {@code FlushMode.NEVER}. It assumes to be used 052 * in combination with service layer transactions that care for the flushing: The 053 * active transaction manager will temporarily change the flush mode to 054 * {@code FlushMode.AUTO} during a read-write transaction, with the flush 055 * mode reset to {@code FlushMode.NEVER} at the end of each transaction. 056 * If you intend to use this filter without transactions, consider changing 057 * the default flush mode (through the "flushMode" property). 058 * 059 * <p><b>WARNING:</b> Applying this filter to existing logic can cause issues that 060 * have not appeared before, through the use of a single Hibernate Session for the 061 * processing of an entire request. In particular, the reassociation of persistent 062 * objects with a Hibernate Session has to occur at the very beginning of request 063 * processing, to avoid clashes with already loaded instances of the same objects. 064 * 065 * <p>Alternatively, turn this filter into deferred close mode, by specifying 066 * "singleSession"="false": It will not use a single session per request then, 067 * but rather let each data access operation or transaction use its own session 068 * (like without Open Session in View). Each of those sessions will be registered 069 * for deferred close, though, actually processed at request completion. 070 * 071 * <p>A single session per request allows for most efficient first-level caching, 072 * but can cause side effects, for example on {@code saveOrUpdate} or when 073 * continuing after a rolled-back transaction. The deferred close strategy is as safe 074 * as no Open Session in View in that respect, while still allowing for lazy loading 075 * in views (but not providing a first-level cache for the entire request). 076 * 077 * <p>Looks up the SessionFactory in Spring's root web application context. 078 * Supports a "sessionFactoryBeanName" filter init-param in {@code web.xml}; 079 * the default bean name is "sessionFactory". 080 * 081 * @author Juergen Hoeller 082 * @since 1.2 083 * @see #setSingleSession 084 * @see #setFlushMode 085 * @see #lookupSessionFactory 086 * @see OpenSessionInViewInterceptor 087 * @see OpenSessionInterceptor 088 * @see org.springframework.orm.hibernate3.HibernateTransactionManager 089 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession 090 * @see org.springframework.transaction.support.TransactionSynchronizationManager 091 * @see org.hibernate.SessionFactory#getCurrentSession() 092 * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x 093 */ 094@Deprecated 095public class OpenSessionInViewFilter extends OncePerRequestFilter { 096 097 public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory"; 098 099 100 private String sessionFactoryBeanName = DEFAULT_SESSION_FACTORY_BEAN_NAME; 101 102 private boolean singleSession = true; 103 104 private FlushMode flushMode = FlushMode.MANUAL; 105 106 107 /** 108 * Set the bean name of the SessionFactory to fetch from Spring's 109 * root application context. Default is "sessionFactory". 110 * @see #DEFAULT_SESSION_FACTORY_BEAN_NAME 111 */ 112 public void setSessionFactoryBeanName(String sessionFactoryBeanName) { 113 this.sessionFactoryBeanName = sessionFactoryBeanName; 114 } 115 116 /** 117 * Return the bean name of the SessionFactory to fetch from Spring's 118 * root application context. 119 */ 120 protected String getSessionFactoryBeanName() { 121 return this.sessionFactoryBeanName; 122 } 123 124 /** 125 * Set whether to use a single session for each request. Default is "true". 126 * <p>If set to "false", each data access operation or transaction will use 127 * its own session (like without Open Session in View). Each of those 128 * sessions will be registered for deferred close, though, actually 129 * processed at request completion. 130 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#initDeferredClose 131 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#processDeferredClose 132 */ 133 public void setSingleSession(boolean singleSession) { 134 this.singleSession = singleSession; 135 } 136 137 /** 138 * Return whether to use a single session for each request. 139 */ 140 protected boolean isSingleSession() { 141 return this.singleSession; 142 } 143 144 /** 145 * Specify the Hibernate FlushMode to apply to this filter's 146 * {@link org.hibernate.Session}. Only applied in single session mode. 147 * <p>Can be populated with the corresponding constant name in XML bean 148 * definitions: e.g. "AUTO". 149 * <p>The default is "MANUAL". Specify "AUTO" if you intend to use 150 * this filter without service layer transactions. 151 * @see org.hibernate.Session#setFlushMode 152 * @see org.hibernate.FlushMode#MANUAL 153 * @see org.hibernate.FlushMode#AUTO 154 */ 155 public void setFlushMode(FlushMode flushMode) { 156 this.flushMode = flushMode; 157 } 158 159 /** 160 * Return the Hibernate FlushMode that this filter applies to its 161 * {@link org.hibernate.Session} (in single session mode). 162 */ 163 protected FlushMode getFlushMode() { 164 return this.flushMode; 165 } 166 167 168 /** 169 * Returns "false" so that the filter may re-bind the opened Hibernate 170 * {@code Session} to each asynchronously dispatched thread and postpone 171 * closing it until the very last asynchronous dispatch. 172 */ 173 @Override 174 protected boolean shouldNotFilterAsyncDispatch() { 175 return false; 176 } 177 178 /** 179 * Returns "false" so that the filter may provide a Hibernate 180 * {@code Session} to each error dispatches. 181 */ 182 @Override 183 protected boolean shouldNotFilterErrorDispatch() { 184 return false; 185 } 186 187 @Override 188 protected void doFilterInternal( 189 HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 190 throws ServletException, IOException { 191 192 SessionFactory sessionFactory = lookupSessionFactory(request); 193 boolean participate = false; 194 195 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 196 String key = getAlreadyFilteredAttributeName(); 197 198 if (isSingleSession()) { 199 // single session mode 200 if (TransactionSynchronizationManager.hasResource(sessionFactory)) { 201 // Do not modify the Session: just set the participate flag. 202 participate = true; 203 } 204 else { 205 boolean isFirstRequest = !isAsyncDispatch(request); 206 if (isFirstRequest || !applySessionBindingInterceptor(asyncManager, key)) { 207 logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter"); 208 Session session = getSession(sessionFactory); 209 org.springframework.orm.hibernate3.SessionHolder sessionHolder = new org.springframework.orm.hibernate3.SessionHolder(session); 210 TransactionSynchronizationManager.bindResource(sessionFactory, sessionHolder); 211 212 AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(sessionFactory, sessionHolder); 213 asyncManager.registerCallableInterceptor(key, interceptor); 214 asyncManager.registerDeferredResultInterceptor(key, interceptor); 215 } 216 } 217 } 218 else { 219 // deferred close mode 220 Assert.state(!isAsyncStarted(request), "Deferred close mode is not supported on async dispatches"); 221 if (org.springframework.orm.hibernate3.SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) { 222 // Do not modify deferred close: just set the participate flag. 223 participate = true; 224 } 225 else { 226 org.springframework.orm.hibernate3.SessionFactoryUtils.initDeferredClose(sessionFactory); 227 } 228 } 229 230 try { 231 filterChain.doFilter(request, response); 232 } 233 finally { 234 if (!participate) { 235 if (isSingleSession()) { 236 // single session mode 237 org.springframework.orm.hibernate3.SessionHolder sessionHolder = 238 (org.springframework.orm.hibernate3.SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory); 239 if (!isAsyncStarted(request)) { 240 logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter"); 241 closeSession(sessionHolder.getSession(), sessionFactory); 242 } 243 } 244 else { 245 // deferred close mode 246 org.springframework.orm.hibernate3.SessionFactoryUtils.processDeferredClose(sessionFactory); 247 } 248 } 249 } 250 } 251 252 /** 253 * Look up the SessionFactory that this filter should use, 254 * taking the current HTTP request as argument. 255 * <p>The default implementation delegates to the {@link #lookupSessionFactory()} 256 * variant without arguments. 257 * @param request the current request 258 * @return the SessionFactory to use 259 */ 260 protected SessionFactory lookupSessionFactory(HttpServletRequest request) { 261 return lookupSessionFactory(); 262 } 263 264 /** 265 * Look up the SessionFactory that this filter should use. 266 * <p>The default implementation looks for a bean with the specified name 267 * in Spring's root application context. 268 * @return the SessionFactory to use 269 * @see #getSessionFactoryBeanName 270 */ 271 protected SessionFactory lookupSessionFactory() { 272 if (logger.isDebugEnabled()) { 273 logger.debug("Using SessionFactory '" + getSessionFactoryBeanName() + "' for OpenSessionInViewFilter"); 274 } 275 WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); 276 return wac.getBean(getSessionFactoryBeanName(), SessionFactory.class); 277 } 278 279 /** 280 * Get a Session for the SessionFactory that this filter uses. 281 * Note that this just applies in single session mode! 282 * <p>The default implementation delegates to the 283 * {@code SessionFactoryUtils.getSession} method and 284 * sets the {@code Session}'s flush mode to "MANUAL". 285 * <p>Can be overridden in subclasses for creating a Session with a 286 * custom entity interceptor or JDBC exception translator. 287 * @param sessionFactory the SessionFactory that this filter uses 288 * @return the Session to use 289 * @throws DataAccessResourceFailureException if the Session could not be created 290 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean) 291 * @see org.hibernate.FlushMode#MANUAL 292 */ 293 protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException { 294 Session session = org.springframework.orm.hibernate3.SessionFactoryUtils.getSession(sessionFactory, true); 295 FlushMode flushMode = getFlushMode(); 296 if (flushMode != null) { 297 session.setFlushMode(flushMode); 298 } 299 return session; 300 } 301 302 /** 303 * Close the given Session. 304 * Note that this just applies in single session mode! 305 * <p>Can be overridden in subclasses, e.g. for flushing the Session before 306 * closing it. See class-level javadoc for a discussion of flush handling. 307 * Note that you should also override getSession accordingly, to set 308 * the flush mode to something else than NEVER. 309 * @param session the Session used for filtering 310 * @param sessionFactory the SessionFactory that this filter uses 311 */ 312 protected void closeSession(Session session, SessionFactory sessionFactory) { 313 org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(session); 314 } 315 316 private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, String key) { 317 if (asyncManager.getCallableInterceptor(key) == null) { 318 return false; 319 } 320 ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); 321 return true; 322 } 323 324}