001/* 002 * Copyright 2002-2014 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 org.hibernate.HibernateException; 020import org.hibernate.Session; 021 022import org.springframework.dao.DataAccessException; 023import org.springframework.transaction.support.TransactionSynchronizationManager; 024import org.springframework.ui.ModelMap; 025import org.springframework.web.context.request.AsyncWebRequestInterceptor; 026import org.springframework.web.context.request.WebRequest; 027import org.springframework.web.context.request.async.WebAsyncManager; 028import org.springframework.web.context.request.async.WebAsyncUtils; 029 030/** 031 * Spring web request interceptor that binds a Hibernate {@code Session} to the 032 * thread for the entire processing of the request. 033 * 034 * <p>This class is a concrete expression of the "Open Session in View" pattern, which 035 * is a pattern that allows for the lazy loading of associations in web views despite 036 * the original transactions already being completed. 037 * 038 * <p>This interceptor makes Hibernate {@code Sessions} available via the current 039 * thread, which will be autodetected by transaction managers. It is suitable for 040 * service layer transactions via 041 * {@link org.springframework.orm.hibernate3.HibernateTransactionManager} or 042 * {@link org.springframework.transaction.jta.JtaTransactionManager} as well as for 043 * non-transactional execution (if configured appropriately). 044 * 045 * <p><b>NOTE</b>: This interceptor will by default <i>not</i> flush the Hibernate 046 * {@code Session}, with the flush mode being set to {@code FlushMode.NEVER}. 047 * It assumes that it will be used in combination with service layer transactions 048 * that handle the flushing: the active transaction manager will temporarily change 049 * the flush mode to {@code FlushMode.AUTO} during a read-write transaction, 050 * with the flush mode reset to {@code FlushMode.NEVER} at the end of each 051 * transaction. If you intend to use this interceptor without transactions, consider 052 * changing the default flush mode (through the 053 * {@link #setFlushMode(int) "flushMode"} property). 054 * 055 * <p>In contrast to {@link OpenSessionInViewFilter}, this interceptor is 056 * configured in a Spring application context and can thus take advantage of bean 057 * wiring. It inherits common Hibernate configuration properties from 058 * {@link org.springframework.orm.hibernate3.HibernateAccessor}, 059 * to be configured in a bean definition. 060 * 061 * <p><b>WARNING:</b> Applying this interceptor to existing logic can cause issues 062 * that have not appeared before, through the use of a single Hibernate 063 * {@code Session} for the processing of an entire request. In particular, the 064 * reassociation of persistent objects with a Hibernate {@code Session} has to 065 * occur at the very beginning of request processing, to avoid clashes with already 066 * loaded instances of the same objects. 067 * 068 * <p>Alternatively, turn this interceptor into deferred close mode, by specifying 069 * "singleSession"="false": It will not use a single session per request then, 070 * but rather let each data access operation or transaction use its own session 071 * (as would be the case without Open Session in View). Each of those sessions will 072 * be registered for deferred close though, which will actually be processed at 073 * request completion. 074 * 075 * <p>A single session per request allows for the most efficient first-level caching, 076 * but can cause side effects, for example on {@code saveOrUpdate} or when 077 * continuing after a rolled-back transaction. The deferred close strategy is as safe 078 * as no Open Session in View in that respect, while still allowing for lazy loading 079 * in views (but not providing a first-level cache for the entire request). 080 * 081 * @author Juergen Hoeller 082 * @since 1.2 083 * @see #setSingleSession 084 * @see #setFlushMode 085 * @see OpenSessionInViewFilter 086 * @see OpenSessionInterceptor 087 * @see org.springframework.orm.hibernate3.HibernateTransactionManager 088 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession 089 * @see org.springframework.transaction.support.TransactionSynchronizationManager 090 * @see org.hibernate.SessionFactory#getCurrentSession() 091 * @deprecated as of Spring 4.3, in favor of Hibernate 4.x/5.x 092 */ 093@Deprecated 094public class OpenSessionInViewInterceptor extends org.springframework.orm.hibernate3.HibernateAccessor implements AsyncWebRequestInterceptor { 095 096 /** 097 * Suffix that gets appended to the {@code SessionFactory} 098 * {@code toString()} representation for the "participate in existing 099 * session handling" request attribute. 100 * @see #getParticipateAttributeName 101 */ 102 public static final String PARTICIPATE_SUFFIX = ".PARTICIPATE"; 103 104 105 private boolean singleSession = true; 106 107 108 /** 109 * Create a new {@code OpenSessionInViewInterceptor}, 110 * turning the default flushMode to {@code FLUSH_NEVER}. 111 * @see #setFlushMode 112 */ 113 public OpenSessionInViewInterceptor() { 114 setFlushMode(FLUSH_NEVER); 115 } 116 117 /** 118 * Set whether to use a single session for each request. Default is "true". 119 * <p>If set to false, each data access operation or transaction will use 120 * its own session (like without Open Session in View). Each of those 121 * sessions will be registered for deferred close, though, actually 122 * processed at request completion. 123 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#initDeferredClose 124 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#processDeferredClose 125 */ 126 public void setSingleSession(boolean singleSession) { 127 this.singleSession = singleSession; 128 } 129 130 /** 131 * Return whether to use a single session for each request. 132 */ 133 protected boolean isSingleSession() { 134 return singleSession; 135 } 136 137 138 /** 139 * Open a new Hibernate {@code Session} according to the settings of this 140 * {@code HibernateAccessor} and bind it to the thread via the 141 * {@link TransactionSynchronizationManager}. 142 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession 143 */ 144 @Override 145 public void preHandle(WebRequest request) throws DataAccessException { 146 String participateAttributeName = getParticipateAttributeName(); 147 148 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); 149 if (asyncManager.hasConcurrentResult()) { 150 if (applySessionBindingInterceptor(asyncManager, participateAttributeName)) { 151 return; 152 } 153 } 154 155 if ((isSingleSession() && TransactionSynchronizationManager.hasResource(getSessionFactory())) || 156 org.springframework.orm.hibernate3.SessionFactoryUtils.isDeferredCloseActive(getSessionFactory())) { 157 // Do not modify the Session: just mark the request accordingly. 158 Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); 159 int newCount = (count != null ? count + 1 : 1); 160 request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST); 161 } 162 else { 163 if (isSingleSession()) { 164 // single session mode 165 logger.debug("Opening single Hibernate Session in OpenSessionInViewInterceptor"); 166 Session session = org.springframework.orm.hibernate3.SessionFactoryUtils.getSession( 167 getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator()); 168 applyFlushMode(session, false); 169 org.springframework.orm.hibernate3.SessionHolder sessionHolder = new org.springframework.orm.hibernate3.SessionHolder(session); 170 TransactionSynchronizationManager.bindResource(getSessionFactory(), sessionHolder); 171 172 AsyncRequestInterceptor asyncRequestInterceptor = 173 new AsyncRequestInterceptor(getSessionFactory(), sessionHolder); 174 asyncManager.registerCallableInterceptor(participateAttributeName, asyncRequestInterceptor); 175 asyncManager.registerDeferredResultInterceptor(participateAttributeName, asyncRequestInterceptor); 176 } 177 else { 178 // deferred close mode 179 org.springframework.orm.hibernate3.SessionFactoryUtils.initDeferredClose(getSessionFactory()); 180 } 181 } 182 } 183 184 /** 185 * Flush the Hibernate {@code Session} before view rendering, if necessary. 186 * <p>Note that this just applies in {@link #isSingleSession() single session mode}! 187 * <p>The default is {@code FLUSH_NEVER} to avoid this extra flushing, 188 * assuming that service layer transactions have flushed their changes on commit. 189 * @see #setFlushMode 190 */ 191 @Override 192 public void postHandle(WebRequest request, ModelMap model) throws DataAccessException { 193 if (isSingleSession()) { 194 // Only potentially flush in single session mode. 195 org.springframework.orm.hibernate3.SessionHolder sessionHolder = 196 (org.springframework.orm.hibernate3.SessionHolder) TransactionSynchronizationManager.getResource(getSessionFactory()); 197 logger.debug("Flushing single Hibernate Session in OpenSessionInViewInterceptor"); 198 try { 199 flushIfNecessary(sessionHolder.getSession(), false); 200 } 201 catch (HibernateException ex) { 202 throw convertHibernateAccessException(ex); 203 } 204 } 205 } 206 207 /** 208 * Unbind the Hibernate {@code Session} from the thread and close it (in 209 * single session mode), or process deferred close for all sessions that have 210 * been opened during the current request (in deferred close mode). 211 * @see org.springframework.transaction.support.TransactionSynchronizationManager 212 */ 213 @Override 214 public void afterCompletion(WebRequest request, Exception ex) throws DataAccessException { 215 if (!decrementParticipateCount(request)) { 216 if (isSingleSession()) { 217 // single session mode 218 org.springframework.orm.hibernate3.SessionHolder sessionHolder = 219 (org.springframework.orm.hibernate3.SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory()); 220 logger.debug("Closing single Hibernate Session in OpenSessionInViewInterceptor"); 221 org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(sessionHolder.getSession()); 222 } 223 else { 224 // deferred close mode 225 org.springframework.orm.hibernate3.SessionFactoryUtils.processDeferredClose(getSessionFactory()); 226 } 227 } 228 } 229 230 @Override 231 public void afterConcurrentHandlingStarted(WebRequest request) { 232 if (!decrementParticipateCount(request)) { 233 if (isSingleSession()) { 234 TransactionSynchronizationManager.unbindResource(getSessionFactory()); 235 } 236 else { 237 throw new IllegalStateException("Deferred close mode is not supported with async requests."); 238 } 239 240 } 241 } 242 243 private boolean decrementParticipateCount(WebRequest request) { 244 String participateAttributeName = getParticipateAttributeName(); 245 Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); 246 if (count == null) { 247 return false; 248 } 249 // Do not modify the Session: just clear the marker. 250 if (count > 1) { 251 request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST); 252 } 253 else { 254 request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); 255 } 256 return true; 257 } 258 259 /** 260 * Return the name of the request attribute that identifies that a request is 261 * already intercepted. 262 * <p>The default implementation takes the {@code toString()} representation 263 * of the {@code SessionFactory} instance and appends {@link #PARTICIPATE_SUFFIX}. 264 */ 265 protected String getParticipateAttributeName() { 266 return getSessionFactory().toString() + PARTICIPATE_SUFFIX; 267 } 268 269 private boolean applySessionBindingInterceptor(WebAsyncManager asyncManager, String key) { 270 if (asyncManager.getCallableInterceptor(key) == null) { 271 return false; 272 } 273 ((AsyncRequestInterceptor) asyncManager.getCallableInterceptor(key)).bindSession(); 274 return true; 275 } 276 277}