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