1 /*
2 * Copyright 2002-2007 the original author or authors.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package org.springframework.orm.hibernate3.support;
18
19 import java.io.IOException;
20
21 import javax.servlet.FilterChain;
22 import javax.servlet.ServletException;
23 import javax.servlet.http.HttpServletRequest;
24 import javax.servlet.http.HttpServletResponse;
25
26 import org.hibernate.FlushMode;
27 import org.hibernate.Session;
28 import org.hibernate.SessionFactory;
29
30 import org.springframework.dao.DataAccessResourceFailureException;
31 import org.springframework.orm.hibernate3.SessionFactoryUtils;
32 import org.springframework.orm.hibernate3.SessionHolder;
33 import org.springframework.transaction.support.TransactionSynchronizationManager;
34 import org.springframework.web.context.WebApplicationContext;
35 import org.springframework.web.context.support.WebApplicationContextUtils;
36 import org.springframework.web.filter.OncePerRequestFilter;
37
38 /**
39 * Servlet 2.3 Filter that binds a Hibernate Session to the thread for the entire
40 * processing of the request. Intended for the "Open Session in View" pattern,
41 * i.e. to allow for lazy loading in web views despite the original transactions
42 * already being completed.
43 *
44 * <p>This filter makes Hibernate Sessions available via the current thread, which
45 * will be autodetected by transaction managers. It is suitable for service layer
46 * transactions via {@link org.springframework.orm.hibernate3.HibernateTransactionManager}
47 * or {@link org.springframework.transaction.jta.JtaTransactionManager} as well
48 * as for non-transactional execution (if configured appropriately).
49 *
50 * <p><b>NOTE</b>: This filter will by default <i>not</i> flush the Hibernate Session,
51 * with the flush mode set to <code>FlushMode.NEVER</code>. It assumes to be used
52 * in combination with service layer transactions that care for the flushing: The
53 * active transaction manager will temporarily change the flush mode to
54 * <code>FlushMode.AUTO</code> during a read-write transaction, with the flush
55 * mode reset to <code>FlushMode.NEVER</code> at the end of each transaction.
56 * If you intend to use this filter without transactions, consider changing
57 * the default flush mode (through the "flushMode" property).
58 *
59 * <p><b>WARNING:</b> Applying this filter to existing logic can cause issues that
60 * have not appeared before, through the use of a single Hibernate Session for the
61 * processing of an entire request. In particular, the reassociation of persistent
62 * objects with a Hibernate Session has to occur at the very beginning of request
63 * processing, to avoid clashes with already loaded instances of the same objects.
64 *
65 * <p>Alternatively, turn this filter into deferred close mode, by specifying
66 * "singleSession"="false": It will not use a single session per request then,
67 * but rather let each data access operation or transaction use its own session
68 * (like without Open Session in View). Each of those sessions will be registered
69 * for deferred close, though, actually processed at request completion.
70 *
71 * <p>A single session per request allows for most efficient first-level caching,
72 * but can cause side effects, for example on <code>saveOrUpdate</code> or when
73 * continuing after a rolled-back transaction. The deferred close strategy is as safe
74 * as no Open Session in View in that respect, while still allowing for lazy loading
75 * in views (but not providing a first-level cache for the entire request).
76 *
77 * <p>Looks up the SessionFactory in Spring's root web application context.
78 * Supports a "sessionFactoryBeanName" filter init-param in <code>web.xml</code>;
79 * the default bean name is "sessionFactory". Looks up the SessionFactory on each
80 * request, to avoid initialization order issues (when using ContextLoaderServlet,
81 * the root application context will get initialized <i>after</i> this filter).
82 *
83 * @author Juergen Hoeller
84 * @since 1.2
85 * @see #setSingleSession
86 * @see #setFlushMode
87 * @see #lookupSessionFactory
88 * @see OpenSessionInViewInterceptor
89 * @see org.springframework.orm.hibernate3.HibernateInterceptor
90 * @see org.springframework.orm.hibernate3.HibernateTransactionManager
91 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession
92 * @see org.springframework.transaction.support.TransactionSynchronizationManager
93 */
94 public class OpenSessionInViewFilter extends OncePerRequestFilter {
95
96 public static final String DEFAULT_SESSION_FACTORY_BEAN_NAME = "sessionFactory";
97
98
99 private String sessionFactoryBeanName = DEFAULT_SESSION_FACTORY_BEAN_NAME;
100
101 private boolean singleSession = true;
102
103 private FlushMode flushMode = FlushMode.NEVER;
104
105
106 /**
107 * Set the bean name of the SessionFactory to fetch from Spring's
108 * root application context. Default is "sessionFactory".
109 * @see #DEFAULT_SESSION_FACTORY_BEAN_NAME
110 */
111 public void setSessionFactoryBeanName(String sessionFactoryBeanName) {
112 this.sessionFactoryBeanName = sessionFactoryBeanName;
113 }
114
115 /**
116 * Return the bean name of the SessionFactory to fetch from Spring's
117 * root application context.
118 */
119 protected String getSessionFactoryBeanName() {
120 return this.sessionFactoryBeanName;
121 }
122
123 /**
124 * Set whether to use a single session for each request. Default is "true".
125 * <p>If set to "false", each data access operation or transaction will use
126 * its own session (like without Open Session in View). Each of those
127 * sessions will be registered for deferred close, though, actually
128 * processed at request completion.
129 * @see SessionFactoryUtils#initDeferredClose
130 * @see SessionFactoryUtils#processDeferredClose
131 */
132 public void setSingleSession(boolean singleSession) {
133 this.singleSession = singleSession;
134 }
135
136 /**
137 * Return whether to use a single session for each request.
138 */
139 protected boolean isSingleSession() {
140 return this.singleSession;
141 }
142
143 /**
144 * Specify the Hibernate FlushMode to apply to this filter's
145 * {@link org.hibernate.Session}. Only applied in single session mode.
146 * <p>Can be populated with the corresponding constant name in XML bean
147 * definitions: e.g. "AUTO".
148 * <p>The default is "NEVER". Specify "AUTO" if you intend to use
149 * this filter without service layer transactions.
150 * @see org.hibernate.Session#setFlushMode
151 * @see org.hibernate.FlushMode#NEVER
152 * @see org.hibernate.FlushMode#AUTO
153 */
154 public void setFlushMode(FlushMode flushMode) {
155 this.flushMode = flushMode;
156 }
157
158 /**
159 * Return the Hibernate FlushMode that this filter applies to its
160 * {@link org.hibernate.Session} (in single session mode).
161 */
162 protected FlushMode getFlushMode() {
163 return this.flushMode;
164 }
165
166
167 protected void doFilterInternal(
168 HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
169 throws ServletException, IOException {
170
171 SessionFactory sessionFactory = lookupSessionFactory(request);
172 boolean participate = false;
173
174 if (isSingleSession()) {
175 // single session mode
176 if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
177 // Do not modify the Session: just set the participate flag.
178 participate = true;
179 }
180 else {
181 logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
182 Session session = getSession(sessionFactory);
183 TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
184 }
185 }
186 else {
187 // deferred close mode
188 if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
189 // Do not modify deferred close: just set the participate flag.
190 participate = true;
191 }
192 else {
193 SessionFactoryUtils.initDeferredClose(sessionFactory);
194 }
195 }
196
197 try {
198 filterChain.doFilter(request, response);
199 }
200
201 finally {
202 if (!participate) {
203 if (isSingleSession()) {
204 // single session mode
205 SessionHolder sessionHolder =
206 (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
207 logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
208 closeSession(sessionHolder.getSession(), sessionFactory);
209 }
210 else {
211 // deferred close mode
212 SessionFactoryUtils.processDeferredClose(sessionFactory);
213 }
214 }
215 }
216 }
217
218 /**
219 * Look up the SessionFactory that this filter should use,
220 * taking the current HTTP request as argument.
221 * <p>The default implementation delegates to the {@link #lookupSessionFactory()}
222 * variant without arguments.
223 * @param request the current request
224 * @return the SessionFactory to use
225 */
226 protected SessionFactory lookupSessionFactory(HttpServletRequest request) {
227 return lookupSessionFactory();
228 }
229
230 /**
231 * Look up the SessionFactory that this filter should use.
232 * <p>The default implementation looks for a bean with the specified name
233 * in Spring's root application context.
234 * @return the SessionFactory to use
235 * @see #getSessionFactoryBeanName
236 */
237 protected SessionFactory lookupSessionFactory() {
238 if (logger.isDebugEnabled()) {
239 logger.debug("Using SessionFactory '" + getSessionFactoryBeanName() + "' for OpenSessionInViewFilter");
240 }
241 WebApplicationContext wac =
242 WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
243 return (SessionFactory) wac.getBean(getSessionFactoryBeanName(), SessionFactory.class);
244 }
245
246 /**
247 * Get a Session for the SessionFactory that this filter uses.
248 * Note that this just applies in single session mode!
249 * <p>The default implementation delegates to the
250 * <code>SessionFactoryUtils.getSession</code> method and
251 * sets the <code>Session</code>'s flush mode to "NEVER".
252 * <p>Can be overridden in subclasses for creating a Session with a
253 * custom entity interceptor or JDBC exception translator.
254 * @param sessionFactory the SessionFactory that this filter uses
255 * @return the Session to use
256 * @throws DataAccessResourceFailureException if the Session could not be created
257 * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean)
258 * @see org.hibernate.FlushMode#NEVER
259 */
260 protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
261 Session session = SessionFactoryUtils.getSession(sessionFactory, true);
262 FlushMode flushMode = getFlushMode();
263 if (flushMode != null) {
264 session.setFlushMode(flushMode);
265 }
266 return session;
267 }
268
269 /**
270 * Close the given Session.
271 * Note that this just applies in single session mode!
272 * <p>Can be overridden in subclasses, e.g. for flushing the Session before
273 * closing it. See class-level javadoc for a discussion of flush handling.
274 * Note that you should also override getSession accordingly, to set
275 * the flush mode to something else than NEVER.
276 * @param session the Session used for filtering
277 * @param sessionFactory the SessionFactory that this filter uses
278 */
279 protected void closeSession(Session session, SessionFactory sessionFactory) {
280 SessionFactoryUtils.closeSession(session);
281 }
282
283 }