(一)电商系统的服务端开发中,经常会遇到需要同时更改多个数据源的场景,比如订单支付系统,用户转账等等。在服务器出现异常的情况下,也需要保证数据的一致性,因而需要把对于多个数据源的修改封装在同一个事务中进行。Spring中的事物管理包括编程式和声明式两种。
(二)Spring编程式事务管理
- 事务定义:TransactionDefinition
- 事务状态:TransactionStatus
- 事务管理者:PlatformTransactionManager
1)TransactionDefinition
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
int TIMEOUT_DEFAULT = -1;
int getPropagationBehavior();
int getIsolationLevel();
int getTimeout();
boolean isReadOnly();
String getName();
}
接口TransactionDefinition定义了事务的7种传播行为和4种隔离级别
- TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
- TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
- 四种隔离级别为读未提交、读提交、重复读、串行化。
- getTimeout()返回超时时间,事物若不能按时完成,则自动回滚,默认设置为底层事物系统的超时值,如底层数据库的超时值。
- isReadOnly()返回该事物是否为只读的事物。
“只读事务”并不是一个强制选项,它只是一个“暗示”,提示数据库驱动程序和数据库系统,这个事务并不包含更改数据的操作,那么JDBC驱动程序和数据库就有可能根据这种情况对该事务进行一些特定的优化,比方说不安排相应的数据库锁,以减轻事务对数据库的压力,毕竟事务也是要消耗数据库的资源的。
但是你非要在“只读事务”里面修改数据,也并非不可以,只不过对于数据一致性的保护不像“读写事务”那样保险而已。
因此,“只读事务”仅仅是一个性能优化的推荐配置而已,并非强制你要这样做不可
- 回滚规则:可以明确配置抛出哪些异常时进行回滚,抛出哪些异常时不回滚,默认配置下,unchecked异常(运行期异常RunTimeException.class,编译器不会检查到的异常)会回滚,checked(非运行期异常Exception.class,可以被编译器检查到,需要捕获)异常不会回滚。还可以通过调用setRollbackOnly()方法来指示一个事物必须回滚。
2)TransactionStatus
package org.springframework.transaction;
import java.io.Flushable;
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction();
boolean hasSavepoint();
void setRollbackOnly();
boolean isRollbackOnly();
@Override
void flush();
boolean isCompleted();
}
- setRollbackOnly()设置该事务为只能回滚,当对该事务执行commit时,实际执行的是回滚操作。
- isRollbackOnly()返回该事务是否为只能回滚。
3)PlatformTransactionManager
package org.springframework.transaction;
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
getTransaction根据事务的定义返回一个已有的或是新建一个事务。我估计是根据事务的名称返回一个已有的事务,否则则根据事务定义的传播行为、隔离级别、超时时间新建一个事务。getTransaction的实现若遇到了一个自己不支持的事务定义,则需要抛出异常。
commit提交事务。若事务被标记为回滚,则执行回滚;若事务是新事务,那么提交之后会重新执行先前因生成该事务而暂停的其他事务;事务提交前产生的异常会导致事务提交失败。
rollback回滚事务。若先前此事务提交失败则不可以执行回滚操作。
直接使用PlatformTransactionManager接口的实现去进行事务管理比较麻烦,Spring提供了TransactionTemplate事务模版,该模板封装了事物管理者生成事物、提交事物、回滚事物的操作,方便我们在业务代码中适用事务。所以若要使用编程式的事物管理,一般推荐使用TransactionTemplate而不是直接使用PlatformTransactionManager。
4)TransactionTemplate
package org.springframework.transaction.support;
import java.lang.reflect.UndeclaredThrowableException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.TransactionSystemException;
@SuppressWarnings("serial")
public class TransactionTemplate extends DefaultTransactionDefinition
implements TransactionOperations, InitializingBean {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
private PlatformTransactionManager transactionManager;
public TransactionTemplate() {
}
public TransactionTemplate(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public TransactionTemplate(PlatformTransactionManager transactionManager, TransactionDefinition transactionDefinition) {
super(transactionDefinition);
this.transactionManager = transactionManager;
}
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public PlatformTransactionManager getTransactionManager() {
return this.transactionManager;
}
@Override
public void afterPropertiesSet() {
if (this.transactionManager == null) {
throw new IllegalArgumentException("Property 'transactionManager' is required");
}
}
@Override
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
result = action.doInTransaction(status);
}
catch (RuntimeException ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Error err) {
// Transactional code threw error -> rollback
rollbackOnException(status, err);
throw err;
}
catch (Throwable ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
return result;
}
}
private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
logger.debug("Initiating transaction rollback on application exception", ex);
try {
this.transactionManager.rollback(status);
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
catch (Error err) {
logger.error("Application exception overridden by rollback error", ex);
throw err;
}
}
}
TransactionTemplate内部包含了事物管理者PlatformTransactionManager transactionManager,并且继承了DefaultTransactionDefinition,即拥有了事物定义的信息。
<T> T execute(TransactionCallback<T> action)是TransactionTemplate内部的一个公有方法,事物的提交或回滚,以及具体的业务逻辑都在该方法中实现,该方法的参数TransactionCallback<T> action是一个接口,需要开发人员自行实现该接口的一个方法doInTransaction(TransactionStatus status)。
在execute方法中,首先通过transactionManager.getTransaction(this)开启一个事物,获取事物状态status;然后具体的业务逻辑在doInTransaction(TransactionStatus status)中编写;最后,若无任何异常,则执行事物提交的操作transactionManager.commit(TransactionStatus status),否则,捕获异常,执行事物的回滚操作transactionManager.rollback(TransactionStatus status);在业务逻辑中,可根据具体业务需要,设置status.setRollbackOnly(),指定事物回滚。
若不需要返回值,则可以使用TransactionCallbackWithoutResult代替TransactionCallback,并调用方法doInTransactionWithoutResult代替doInTransaction。
(三)Spring声明式事务管理
声明式事物管理是基于AOP的,即对调用方法进行拦截,在执行目标方法前开启一个事物,在执行目标方法后根据情况提交事物或是回滚事物。
声明式的事物管理器有两种,一种是通过在类、接口或方法上添加@Transactional注解,一种是通过xml配置文件。
1)@Transactional注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
boolean readOnly() default false;
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
value | 事物管理器 |
propagation | 设置事务的传播行为。 @Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation | 设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况 |
timeout | 设置事物的超时时间,-1代表永远不超时 |
readOnly | 设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。@Transactional(readOnly=true) |
rollbackFo | 设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。 @Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
rollbackForClassName | 设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。 @Transactional(rollbackForClassName={"RuntimeException","Exception"}) |
noRollbackFor | 设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。 @Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName | 设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。 @Transactional(noRollbackForClassName={"RuntimeException","Exception"}) |
2)使用xml
3)注意
- @Transactional注解只对public方法有效;
- 只有来自外部的方法调用才会被捕获,并引起事物行为;
- 不建议在接口或接口方法上使用该注解,因为这只有在使用基于接口的代理时才会生效。
(三)两种事物管理方式的比较
使用编程式事物管理可以使得事物的粒度细化到代码块级别,但是在不可避免地会导致业务代码的实现和事物管理的代码耦合。
使用声明式事物管理可以避免侵入式编程,不需要在业务代码中掺杂事务管理的代码,只需要在配置文件中做相关的事物规则声明,使得业务代码不受污染,但是其封装粒度只能到达方法级别。
(四)自动提交
默认情况下,数据库处于自动提交模式。每一条语句就是一个单独的事物,该语句执行完毕后,若成功则隐式提交事物,若失败则隐式回滚事物。但是对于正常的事物管理,是一组相关的操作处于一个事物之中,因而需要关闭数据库的自动提交。spring会将底层连接的自动提交设置为false。