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