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