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: public int getOrder() {
096: return SessionFactoryUtils.SESSION_SYNCHRONIZATION_ORDER;
097: }
098:
099: public void suspend() {
100: if (this .holderActive) {
101: TransactionSynchronizationManager
102: .unbindResource(this .sessionFactory);
103: }
104: }
105:
106: public void resume() {
107: if (this .holderActive) {
108: TransactionSynchronizationManager.bindResource(
109: this .sessionFactory, this .sessionHolder);
110: }
111: }
112:
113: public void beforeCommit(boolean readOnly)
114: throws DataAccessException {
115: if (!readOnly) {
116: Session session = null;
117: // Check whether there is a Hibernate Session for the current JTA
118: // transaction. Else, fall back to the default thread-bound Session.
119: if (this .jtaTransaction != null) {
120: session = this .sessionHolder
121: .getSession(this .jtaTransaction);
122: }
123: if (session == null) {
124: session = this .sessionHolder.getSession();
125: }
126: // Read-write transaction -> flush the Hibernate Session.
127: // Further check: only flush when not FlushMode.NEVER/MANUAL.
128: if (!session.getFlushMode().lessThan(FlushMode.COMMIT)) {
129: try {
130: SessionFactoryUtils.logger
131: .debug("Flushing Hibernate Session on transaction synchronization");
132: session.flush();
133: } catch (HibernateException ex) {
134: if (this .jdbcExceptionTranslator != null
135: && ex instanceof JDBCException) {
136: JDBCException jdbcEx = (JDBCException) ex;
137: throw this .jdbcExceptionTranslator.translate(
138: "Hibernate flushing: "
139: + jdbcEx.getMessage(), jdbcEx
140: .getSQL(), jdbcEx
141: .getSQLException());
142: }
143: throw SessionFactoryUtils
144: .convertHibernateAccessException(ex);
145: }
146: }
147: }
148: }
149:
150: public void beforeCompletion() {
151: if (this .jtaTransaction != null) {
152: // Typically in case of a suspended JTA transaction:
153: // Remove the Session for the current JTA transaction, but keep the holder.
154: Session session = this .sessionHolder
155: .removeSession(this .jtaTransaction);
156: if (session != null) {
157: if (this .sessionHolder.isEmpty()) {
158: // No Sessions for JTA transactions bound anymore -> could remove it.
159: if (TransactionSynchronizationManager
160: .hasResource(this .sessionFactory)) {
161: // Explicit check necessary because of remote transaction propagation:
162: // The synchronization callbacks will execute in a different thread
163: // in such a scenario, as they're triggered by a remote server.
164: // The best we can do is to leave the SessionHolder bound to the
165: // thread that originally performed the data access. It will be
166: // reused when a new data access operation starts on that thread.
167: TransactionSynchronizationManager
168: .unbindResource(this .sessionFactory);
169: }
170: this .holderActive = false;
171: }
172: // Do not close a pre-bound Session. In that case, we'll find the
173: // transaction-specific Session the same as the default Session.
174: if (session != this .sessionHolder.getSession()) {
175: SessionFactoryUtils
176: .closeSessionOrRegisterDeferredClose(
177: session, this .sessionFactory);
178: } else if (this .sessionHolder.getPreviousFlushMode() != null) {
179: // In case of pre-bound Session, restore previous flush mode.
180: session.setFlushMode(this .sessionHolder
181: .getPreviousFlushMode());
182: }
183: return;
184: }
185: }
186: // We'll only get here if there was no specific JTA transaction to handle.
187: if (this .newSession) {
188: // Default behavior: unbind and close the thread-bound Hibernate Session.
189: TransactionSynchronizationManager
190: .unbindResource(this .sessionFactory);
191: this .holderActive = false;
192: if (this .hibernateTransactionCompletion) {
193: // Close the Hibernate Session here in case of a Hibernate TransactionManagerLookup:
194: // Hibernate will automatically defer the actual closing until JTA transaction completion.
195: // Else, the Session will be closed in the afterCompletion method, to provide the
196: // correct transaction status for releasing the Session's cache locks.
197: SessionFactoryUtils
198: .closeSessionOrRegisterDeferredClose(
199: this .sessionHolder.getSession(),
200: this .sessionFactory);
201: }
202: } else if (this .sessionHolder.getPreviousFlushMode() != null) {
203: // In case of pre-bound Session, restore previous flush mode.
204: this .sessionHolder.getSession().setFlushMode(
205: this .sessionHolder.getPreviousFlushMode());
206: }
207: }
208:
209: public void afterCompletion(int status) {
210: if (!this .hibernateTransactionCompletion || !this .newSession) {
211: // No Hibernate TransactionManagerLookup: apply afterTransactionCompletion callback.
212: // Always perform explicit afterTransactionCompletion callback for pre-bound Session,
213: // even with Hibernate TransactionManagerLookup (which only applies to new Sessions).
214: Session session = this .sessionHolder.getSession();
215: // Provide correct transaction status for releasing the Session's cache locks,
216: // if possible. Else, closing will release all cache locks assuming a rollback.
217: if (session instanceof SessionImplementor) {
218: ((SessionImplementor) session)
219: .afterTransactionCompletion(
220: status == STATUS_COMMITTED, null);
221: }
222: // Close the Hibernate Session here if necessary
223: // (closed in beforeCompletion in case of TransactionManagerLookup).
224: if (this .newSession) {
225: SessionFactoryUtils
226: .closeSessionOrRegisterDeferredClose(session,
227: this .sessionFactory);
228: }
229: }
230: if (!this .newSession && status != STATUS_COMMITTED) {
231: // Clear all pending inserts/updates/deletes in the Session.
232: // Necessary for pre-bound Sessions, to avoid inconsistent state.
233: this .sessionHolder.getSession().clear();
234: }
235: if (this .sessionHolder.doesNotHoldNonDefaultSession()) {
236: this .sessionHolder.setSynchronizedWithTransaction(false);
237: }
238: }
239:
240: }
|