001: /*
002: * Copyright 2002-2006 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;
018:
019: import javax.transaction.SystemException;
020: import javax.transaction.Transaction;
021: import javax.transaction.TransactionManager;
022:
023: import org.hibernate.FlushMode;
024: import org.hibernate.HibernateException;
025: import org.hibernate.JDBCException;
026: import org.hibernate.Session;
027: import org.hibernate.SessionFactory;
028: import org.hibernate.engine.SessionImplementor;
029:
030: import org.springframework.core.Ordered;
031: import org.springframework.dao.DataAccessException;
032: import org.springframework.dao.DataAccessResourceFailureException;
033: import org.springframework.jdbc.support.SQLExceptionTranslator;
034: import org.springframework.transaction.support.TransactionSynchronizationAdapter;
035: import org.springframework.transaction.support.TransactionSynchronizationManager;
036:
037: /**
038: * Callback for resource cleanup at the end of a Spring-managed JTA transaction,
039: * that is, when participating in a JtaTransactionManager transaction.
040: *
041: * @author Juergen Hoeller
042: * @since 1.2
043: * @see SessionFactoryUtils
044: * @see org.springframework.transaction.jta.JtaTransactionManager
045: */
046: class SpringSessionSynchronization extends
047: TransactionSynchronizationAdapter implements Ordered {
048:
049: private final SessionHolder sessionHolder;
050:
051: private final SessionFactory sessionFactory;
052:
053: private final SQLExceptionTranslator jdbcExceptionTranslator;
054:
055: private final boolean newSession;
056:
057: /**
058: * Whether Hibernate has a looked-up JTA TransactionManager that it will
059: * automatically register CacheSynchronizations with on Session connect.
060: */
061: private boolean hibernateTransactionCompletion = false;
062:
063: private Transaction jtaTransaction;
064:
065: private boolean holderActive = true;
066:
067: public SpringSessionSynchronization(SessionHolder sessionHolder,
068: SessionFactory sessionFactory,
069: SQLExceptionTranslator jdbcExceptionTranslator,
070: boolean newSession) {
071:
072: this .sessionHolder = sessionHolder;
073: this .sessionFactory = sessionFactory;
074: this .jdbcExceptionTranslator = jdbcExceptionTranslator;
075: this .newSession = newSession;
076:
077: // Check whether the SessionFactory has a JTA TransactionManager.
078: TransactionManager jtaTm = SessionFactoryUtils
079: .getJtaTransactionManager(sessionFactory, sessionHolder
080: .getAnySession());
081: if (jtaTm != null) {
082: this .hibernateTransactionCompletion = true;
083: // Fetch current JTA Transaction object
084: // (just necessary for JTA transaction suspension, with an individual
085: // Hibernate Session per currently active/suspended transaction).
086: try {
087: this .jtaTransaction = jtaTm.getTransaction();
088: } catch (SystemException ex) {
089: throw new DataAccessResourceFailureException(
090: "Could not access JTA transaction", ex);
091: }
092: }
093: }
094:
095: /**
096: * Check whether there is a Hibernate Session for the current JTA
097: * transaction. Else, fall back to the default thread-bound Session.
098: */
099: private Session getCurrentSession() {
100: Session session = null;
101: if (this .jtaTransaction != null) {
102: session = this .sessionHolder
103: .getSession(this .jtaTransaction);
104: }
105: if (session == null) {
106: session = this .sessionHolder.getSession();
107: }
108: return session;
109: }
110:
111: public int getOrder() {
112: return SessionFactoryUtils.SESSION_SYNCHRONIZATION_ORDER;
113: }
114:
115: public void suspend() {
116: if (this .holderActive) {
117: TransactionSynchronizationManager
118: .unbindResource(this .sessionFactory);
119: // Eagerly disconnect the Session here, to make release mode "on_close" work on JBoss.
120: getCurrentSession().disconnect();
121: }
122: }
123:
124: public void resume() {
125: if (this .holderActive) {
126: TransactionSynchronizationManager.bindResource(
127: this .sessionFactory, this .sessionHolder);
128: }
129: }
130:
131: public void beforeCommit(boolean readOnly)
132: throws DataAccessException {
133: if (!readOnly) {
134: Session session = getCurrentSession();
135: // Read-write transaction -> flush the Hibernate Session.
136: // Further check: only flush when not FlushMode.NEVER/MANUAL.
137: if (!session.getFlushMode().lessThan(FlushMode.COMMIT)) {
138: try {
139: SessionFactoryUtils.logger
140: .debug("Flushing Hibernate Session on transaction synchronization");
141: session.flush();
142: } catch (HibernateException ex) {
143: if (this .jdbcExceptionTranslator != null
144: && ex instanceof JDBCException) {
145: JDBCException jdbcEx = (JDBCException) ex;
146: throw this .jdbcExceptionTranslator.translate(
147: "Hibernate flushing: "
148: + jdbcEx.getMessage(), jdbcEx
149: .getSQL(), jdbcEx
150: .getSQLException());
151: }
152: throw SessionFactoryUtils
153: .convertHibernateAccessException(ex);
154: }
155: }
156: }
157: }
158:
159: public void beforeCompletion() {
160: if (this .jtaTransaction != null) {
161: // Typically in case of a suspended JTA transaction:
162: // Remove the Session for the current JTA transaction, but keep the holder.
163: Session session = this .sessionHolder
164: .removeSession(this .jtaTransaction);
165: if (session != null) {
166: if (this .sessionHolder.isEmpty()) {
167: // No Sessions for JTA transactions bound anymore -> could remove it.
168: if (TransactionSynchronizationManager
169: .hasResource(this .sessionFactory)) {
170: // Explicit check necessary because of remote transaction propagation:
171: // The synchronization callbacks will execute in a different thread
172: // in such a scenario, as they're triggered by a remote server.
173: // The best we can do is to leave the SessionHolder bound to the
174: // thread that originally performed the data access. It will be
175: // reused when a new data access operation starts on that thread.
176: TransactionSynchronizationManager
177: .unbindResource(this .sessionFactory);
178: }
179: this .holderActive = false;
180: }
181: // Do not close a pre-bound Session. In that case, we'll find the
182: // transaction-specific Session the same as the default Session.
183: if (session != this .sessionHolder.getSession()) {
184: SessionFactoryUtils
185: .closeSessionOrRegisterDeferredClose(
186: session, this .sessionFactory);
187: } else {
188: if (this .sessionHolder.getPreviousFlushMode() != null) {
189: // In case of pre-bound Session, restore previous flush mode.
190: session.setFlushMode(this .sessionHolder
191: .getPreviousFlushMode());
192: }
193: // Eagerly disconnect the Session here, to make release mode "on_close" work nicely.
194: session.disconnect();
195: }
196: return;
197: }
198: }
199: // We'll only get here if there was no specific JTA transaction to handle.
200: if (this .newSession) {
201: // Default behavior: unbind and close the thread-bound Hibernate Session.
202: TransactionSynchronizationManager
203: .unbindResource(this .sessionFactory);
204: this .holderActive = false;
205: if (this .hibernateTransactionCompletion) {
206: // Close the Hibernate Session here in case of a Hibernate TransactionManagerLookup:
207: // Hibernate will automatically defer the actual closing until JTA transaction completion.
208: // Else, the Session will be closed in the afterCompletion method, to provide the
209: // correct transaction status for releasing the Session's cache locks.
210: SessionFactoryUtils
211: .closeSessionOrRegisterDeferredClose(
212: this .sessionHolder.getSession(),
213: this .sessionFactory);
214: }
215: } else {
216: Session session = this .sessionHolder.getSession();
217: if (this .sessionHolder.getPreviousFlushMode() != null) {
218: // In case of pre-bound Session, restore previous flush mode.
219: session.setFlushMode(this .sessionHolder
220: .getPreviousFlushMode());
221: }
222: if (this .hibernateTransactionCompletion) {
223: // Eagerly disconnect the Session here, to make release mode "on_close" work nicely.
224: // We know that this is appropriate if a TransactionManagerLookup has been specified.
225: session.disconnect();
226: }
227: }
228: }
229:
230: public void afterCompletion(int status) {
231: if (!this .hibernateTransactionCompletion || !this .newSession) {
232: // No Hibernate TransactionManagerLookup: apply afterTransactionCompletion callback.
233: // Always perform explicit afterTransactionCompletion callback for pre-bound Session,
234: // even with Hibernate TransactionManagerLookup (which only applies to new Sessions).
235: Session session = this .sessionHolder.getSession();
236: // Provide correct transaction status for releasing the Session's cache locks,
237: // if possible. Else, closing will release all cache locks assuming a rollback.
238: if (session instanceof SessionImplementor) {
239: ((SessionImplementor) session)
240: .afterTransactionCompletion(
241: status == STATUS_COMMITTED, null);
242: }
243: // Close the Hibernate Session here if necessary
244: // (closed in beforeCompletion in case of TransactionManagerLookup).
245: if (this .newSession) {
246: SessionFactoryUtils
247: .closeSessionOrRegisterDeferredClose(session,
248: this .sessionFactory);
249: } else if (!this .hibernateTransactionCompletion) {
250: session.disconnect();
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: if (this .sessionHolder.doesNotHoldNonDefaultSession()) {
259: this .sessionHolder.setSynchronizedWithTransaction(false);
260: }
261: }
262:
263: }
|