001/*
002 * Copyright 2002-2018 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 *      https://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
017package org.springframework.transaction.support;
018
019import java.lang.reflect.UndeclaredThrowableException;
020
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023
024import org.springframework.beans.factory.InitializingBean;
025import org.springframework.lang.Nullable;
026import org.springframework.transaction.PlatformTransactionManager;
027import org.springframework.transaction.TransactionDefinition;
028import org.springframework.transaction.TransactionException;
029import org.springframework.transaction.TransactionStatus;
030import org.springframework.transaction.TransactionSystemException;
031import org.springframework.util.Assert;
032
033/**
034 * Template class that simplifies programmatic transaction demarcation and
035 * transaction exception handling.
036 *
037 * <p>The central method is {@link #execute}, supporting transactional code that
038 * implements the {@link TransactionCallback} interface. This template handles
039 * the transaction lifecycle and possible exceptions such that neither the
040 * TransactionCallback implementation nor the calling code needs to explicitly
041 * handle transactions.
042 *
043 * <p>Typical usage: Allows for writing low-level data access objects that use
044 * resources such as JDBC DataSources but are not transaction-aware themselves.
045 * Instead, they can implicitly participate in transactions handled by higher-level
046 * application services utilizing this class, making calls to the low-level
047 * services via an inner-class callback object.
048 *
049 * <p>Can be used within a service implementation via direct instantiation with
050 * a transaction manager reference, or get prepared in an application context
051 * and passed to services as bean reference. Note: The transaction manager should
052 * always be configured as bean in the application context: in the first case given
053 * to the service directly, in the second case given to the prepared template.
054 *
055 * <p>Supports setting the propagation behavior and the isolation level by name,
056 * for convenient configuration in context definitions.
057 *
058 * @author Juergen Hoeller
059 * @since 17.03.2003
060 * @see #execute
061 * @see #setTransactionManager
062 * @see org.springframework.transaction.PlatformTransactionManager
063 */
064@SuppressWarnings("serial")
065public class TransactionTemplate extends DefaultTransactionDefinition
066                implements TransactionOperations, InitializingBean {
067
068        /** Logger available to subclasses. */
069        protected final Log logger = LogFactory.getLog(getClass());
070
071        @Nullable
072        private PlatformTransactionManager transactionManager;
073
074
075        /**
076         * Construct a new TransactionTemplate for bean usage.
077         * <p>Note: The PlatformTransactionManager needs to be set before
078         * any {@code execute} calls.
079         * @see #setTransactionManager
080         */
081        public TransactionTemplate() {
082        }
083
084        /**
085         * Construct a new TransactionTemplate using the given transaction manager.
086         * @param transactionManager the transaction management strategy to be used
087         */
088        public TransactionTemplate(PlatformTransactionManager transactionManager) {
089                this.transactionManager = transactionManager;
090        }
091
092        /**
093         * Construct a new TransactionTemplate using the given transaction manager,
094         * taking its default settings from the given transaction definition.
095         * @param transactionManager the transaction management strategy to be used
096         * @param transactionDefinition the transaction definition to copy the
097         * default settings from. Local properties can still be set to change values.
098         */
099        public TransactionTemplate(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition) {
100                super(transactionDefinition);
101                this.transactionManager = transactionManager;
102        }
103
104
105        /**
106         * Set the transaction management strategy to be used.
107         */
108        public void setTransactionManager(@Nullable PlatformTransactionManager transactionManager) {
109                this.transactionManager = transactionManager;
110        }
111
112        /**
113         * Return the transaction management strategy to be used.
114         */
115        @Nullable
116        public PlatformTransactionManager getTransactionManager() {
117                return this.transactionManager;
118        }
119
120        @Override
121        public void afterPropertiesSet() {
122                if (this.transactionManager == null) {
123                        throw new IllegalArgumentException("Property 'transactionManager' is required");
124                }
125        }
126
127
128        @Override
129        @Nullable
130        public <T> T execute(TransactionCallback<T> action) throws TransactionException {
131                Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
132
133                if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
134                        return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
135                }
136                else {
137                        TransactionStatus status = this.transactionManager.getTransaction(this);
138                        T result;
139                        try {
140                                result = action.doInTransaction(status);
141                        }
142                        catch (RuntimeException | Error ex) {
143                                // Transactional code threw application exception -> rollback
144                                rollbackOnException(status, ex);
145                                throw ex;
146                        }
147                        catch (Throwable ex) {
148                                // Transactional code threw unexpected exception -> rollback
149                                rollbackOnException(status, ex);
150                                throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
151                        }
152                        this.transactionManager.commit(status);
153                        return result;
154                }
155        }
156
157        /**
158         * Perform a rollback, handling rollback exceptions properly.
159         * @param status object representing the transaction
160         * @param ex the thrown application exception or error
161         * @throws TransactionException in case of a rollback error
162         */
163        private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
164                Assert.state(this.transactionManager != null, "No PlatformTransactionManager set");
165
166                logger.debug("Initiating transaction rollback on application exception", ex);
167                try {
168                        this.transactionManager.rollback(status);
169                }
170                catch (TransactionSystemException ex2) {
171                        logger.error("Application exception overridden by rollback exception", ex);
172                        ex2.initApplicationException(ex);
173                        throw ex2;
174                }
175                catch (RuntimeException | Error ex2) {
176                        logger.error("Application exception overridden by rollback exception", ex);
177                        throw ex2;
178                }
179        }
180
181
182        @Override
183        public boolean equals(@Nullable Object other) {
184                return (this == other || (super.equals(other) && (!(other instanceof TransactionTemplate) ||
185                                getTransactionManager() == ((TransactionTemplate) other).getTransactionManager())));
186        }
187
188}