View Javadoc
1   /*
2    * Copyright 2002-2012 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;
18  
19  import javax.transaction.SystemException;
20  import javax.transaction.Transaction;
21  import javax.transaction.TransactionManager;
22  
23  import org.hibernate.FlushMode;
24  import org.hibernate.HibernateException;
25  import org.hibernate.JDBCException;
26  import org.hibernate.Session;
27  import org.hibernate.SessionFactory;
28  import org.hibernate.engine.SessionImplementor;
29  
30  import org.springframework.core.Ordered;
31  import org.springframework.dao.DataAccessException;
32  import org.springframework.dao.DataAccessResourceFailureException;
33  import org.springframework.jdbc.support.SQLExceptionTranslator;
34  import org.springframework.transaction.support.TransactionSynchronization;
35  import org.springframework.transaction.support.TransactionSynchronizationManager;
36  
37  /**
38   * Callback for resource cleanup at the end of a Spring-managed JTA transaction,
39   * that is, when participating in a JtaTransactionManager transaction.
40   *
41   * @author Juergen Hoeller
42   * @since 1.2
43   * @see SessionFactoryUtils
44   * @see org.springframework.transaction.jta.JtaTransactionManager
45   */
46  class SpringSessionSynchronization implements TransactionSynchronization, Ordered {
47  
48  	private final SessionHolder sessionHolder;
49  
50  	private final SessionFactory sessionFactory;
51  
52  	private final SQLExceptionTranslator jdbcExceptionTranslator;
53  
54  	private final boolean newSession;
55  
56  	/**
57  	 * Whether Hibernate has a looked-up JTA TransactionManager that it will
58  	 * automatically register CacheSynchronizations with on Session connect.
59  	 */
60  	private boolean hibernateTransactionCompletion = false;
61  
62  	private Transaction jtaTransaction;
63  
64  	private boolean holderActive = true;
65  
66  
67  	public SpringSessionSynchronization(
68  			SessionHolder sessionHolder, SessionFactory sessionFactory,
69  			SQLExceptionTranslator jdbcExceptionTranslator, boolean newSession) {
70  
71  		this.sessionHolder = sessionHolder;
72  		this.sessionFactory = sessionFactory;
73  		this.jdbcExceptionTranslator = jdbcExceptionTranslator;
74  		this.newSession = newSession;
75  
76  		// Check whether the SessionFactory has a JTA TransactionManager.
77  		TransactionManager jtaTm =
78  				SessionFactoryUtils.getJtaTransactionManager(sessionFactory, sessionHolder.getAnySession());
79  		if (jtaTm != null) {
80  			this.hibernateTransactionCompletion = true;
81  			// Fetch current JTA Transaction object
82  			// (just necessary for JTA transaction suspension, with an individual
83  			// Hibernate Session per currently active/suspended transaction).
84  			try {
85  				this.jtaTransaction = jtaTm.getTransaction();
86  			}
87  			catch (SystemException ex) {
88  				throw new DataAccessResourceFailureException("Could not access JTA transaction", ex);
89  			}
90  		}
91  	}
92  
93  	/**
94  	 * Check whether there is a Hibernate Session for the current JTA
95  	 * transaction. Else, fall back to the default thread-bound Session.
96  	 */
97  	private Session getCurrentSession() {
98  		Session session = null;
99  		if (this.jtaTransaction != null) {
100 			session = this.sessionHolder.getSession(this.jtaTransaction);
101 		}
102 		if (session == null) {
103 			session = this.sessionHolder.getSession();
104 		}
105 		return session;
106 	}
107 
108 
109 	@Override
110 	public int getOrder() {
111 		return SessionFactoryUtils.SESSION_SYNCHRONIZATION_ORDER;
112 	}
113 
114 	@Override
115 	public void suspend() {
116 		if (this.holderActive) {
117 			TransactionSynchronizationManager.unbindResource(this.sessionFactory);
118 			// Eagerly disconnect the Session here, to make release mode "on_close" work on JBoss.
119 			getCurrentSession().disconnect();
120 		}
121 	}
122 
123 	@Override
124 	public void resume() {
125 		if (this.holderActive) {
126 			TransactionSynchronizationManager.bindResource(this.sessionFactory, this.sessionHolder);
127 		}
128 	}
129 
130 	@Override
131 	public void flush() {
132 		try {
133 			SessionFactoryUtils.logger.debug("Flushing Hibernate Session on explicit request");
134 			getCurrentSession().flush();
135 		}
136 		catch (HibernateException ex) {
137 			throw translateException(ex);
138 		}
139 	}
140 
141 	@Override
142 	public void beforeCommit(boolean readOnly) throws DataAccessException {
143 		if (!readOnly) {
144 			Session session = getCurrentSession();
145 			// Read-write transaction -> flush the Hibernate Session.
146 			// Further check: only flush when not FlushMode.NEVER/MANUAL.
147 			if (!session.getFlushMode().lessThan(FlushMode.COMMIT)) {
148 				try {
149 					SessionFactoryUtils.logger.debug("Flushing Hibernate Session on transaction synchronization");
150 					session.flush();
151 				}
152 				catch (HibernateException ex) {
153 					throw translateException(ex);
154 				}
155 			}
156 		}
157 	}
158 
159 	private DataAccessException translateException(HibernateException ex) {
160 		if (this.jdbcExceptionTranslator != null && ex instanceof JDBCException) {
161 			JDBCException jdbcEx = (JDBCException) ex;
162 			return this.jdbcExceptionTranslator.translate(
163 					"Hibernate flushing: " + jdbcEx.getMessage(), jdbcEx.getSQL(), jdbcEx.getSQLException());
164 		}
165 		return SessionFactoryUtils.convertHibernateAccessException(ex);
166 	}
167 
168 	@Override
169 	public void beforeCompletion() {
170 		if (this.jtaTransaction != null) {
171 			// Typically in case of a suspended JTA transaction:
172 			// Remove the Session for the current JTA transaction, but keep the holder.
173 			Session session = this.sessionHolder.removeSession(this.jtaTransaction);
174 			if (session != null) {
175 				if (this.sessionHolder.isEmpty()) {
176 					// No Sessions for JTA transactions bound anymore -> could remove it.
177 					TransactionSynchronizationManager.unbindResourceIfPossible(this.sessionFactory);
178 					this.holderActive = false;
179 				}
180 				// Do not close a pre-bound Session. In that case, we'll find the
181 				// transaction-specific Session the same as the default Session.
182 				if (session != this.sessionHolder.getSession()) {
183 					SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, this.sessionFactory);
184 				}
185 				else {
186 					if (this.sessionHolder.getPreviousFlushMode() != null) {
187 						// In case of pre-bound Session, restore previous flush mode.
188 						session.setFlushMode(this.sessionHolder.getPreviousFlushMode());
189 					}
190 					// Eagerly disconnect the Session here, to make release mode "on_close" work nicely.
191 					session.disconnect();
192 				}
193 				return;
194 			}
195 		}
196 		// We'll only get here if there was no specific JTA transaction to handle.
197 		if (this.newSession) {
198 			// Default behavior: unbind and close the thread-bound Hibernate Session.
199 			TransactionSynchronizationManager.unbindResource(this.sessionFactory);
200 			this.holderActive = false;
201 			if (this.hibernateTransactionCompletion) {
202 				// Close the Hibernate Session here in case of a Hibernate TransactionManagerLookup:
203 				// Hibernate will automatically defer the actual closing until JTA transaction completion.
204 				// Else, the Session will be closed in the afterCompletion method, to provide the
205 				// correct transaction status for releasing the Session's cache locks.
206 				SessionFactoryUtils.closeSessionOrRegisterDeferredClose(this.sessionHolder.getSession(), this.sessionFactory);
207 			}
208 		}
209 		else  {
210 			Session session = this.sessionHolder.getSession();
211 			if (this.sessionHolder.getPreviousFlushMode() != null) {
212 				// In case of pre-bound Session, restore previous flush mode.
213 				session.setFlushMode(this.sessionHolder.getPreviousFlushMode());
214 			}
215 			if (this.hibernateTransactionCompletion) {
216 				// Eagerly disconnect the Session here, to make release mode "on_close" work nicely.
217 				// We know that this is appropriate if a TransactionManagerLookup has been specified.
218 				session.disconnect();
219 			}
220 		}
221 	}
222 
223 	@Override
224 	public void afterCommit() {
225 	}
226 
227 	@Override
228 	public void afterCompletion(int status) {
229 		try {
230 			if (!this.hibernateTransactionCompletion || !this.newSession) {
231 				// No Hibernate TransactionManagerLookup: apply afterTransactionCompletion callback.
232 				// Always perform explicit afterTransactionCompletion callback for pre-bound Session,
233 				// even with Hibernate TransactionManagerLookup (which only applies to new Sessions).
234 				Session session = this.sessionHolder.getSession();
235 				// Provide correct transaction status for releasing the Session's cache locks,
236 				// if possible. Else, closing will release all cache locks assuming a rollback.
237 				try {
238 					if (session instanceof SessionImplementor) {
239 						((SessionImplementor) session).afterTransactionCompletion(status == STATUS_COMMITTED, null);
240 					}
241 				}
242 				finally {
243 					// Close the Hibernate Session here if necessary
244 					// (closed in beforeCompletion in case of TransactionManagerLookup).
245 					if (this.newSession) {
246 						SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, this.sessionFactory);
247 					}
248 					else if (!this.hibernateTransactionCompletion) {
249 						session.disconnect();
250 					}
251 				}
252 			}
253 			if (!this.newSession && status != STATUS_COMMITTED) {
254 				// Clear all pending inserts/updates/deletes in the Session.
255 				// Necessary for pre-bound Sessions, to avoid inconsistent state.
256 				this.sessionHolder.getSession().clear();
257 			}
258 		}
259 		finally {
260 			if (this.sessionHolder.doesNotHoldNonDefaultSession()) {
261 				this.sessionHolder.setSynchronizedWithTransaction(false);
262 			}
263 		}
264 	}
265 
266 }