Spring事务
Spring的两种事务管理方式
编程式事务管理
通过TransactionTemplate
或者TransactionManager
手动管理事务,实际应用中很少使用。
使用 TransactionTemplate
进行编程式事务管理:
|
使用 TransactionManager
进行编程式事务管理:
|
声明式事务管理
推荐使用(代码侵入性最小),实际是通过 AOP 实现(基于@Transactional
的全注解方式使用最多)。
Spring事务管理接口
PlatformTransactionManager:(平台)事务管理器,Spring事务策略的核心;
public interface PlatformTransactionManager extends TransactionManager {
TransactionStatus getTransaction( TransactionDefinition var1)throws TransactionException;
void commit(TransactionStatus var1) throws TransactionException;
void rollback(TransactionStatus var1) throws TransactionException;
}通过这个接口,Spring 为各个平台如 JDBC(
DataSourceTransactionManager
),Hibernate(HibernateTransactionManager
),JPA(JpaTransactionManager
)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。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 = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;
// 返回事务的传播特性,默认值为REQUIRED
default int getPropagationBehavior() {
return 0;
}
// 返回事务的隔离级别,默认值是DEFAULT
default int getIsolationLevel() {
return -1;
}
// 返回事务的超时时间,默认值为-1。
// 如果超过该时间限制但事务还没有完成,则自动回滚事务。
default int getTimeout() {
return -1;
}
// 返回是否为只读事务,默认值为 false
default boolean isReadOnly() {
return false;
}
default String getName() {
return null;
}
static TransactionDefinition withDefaults() {
return StaticTransactionDefinition.INSTANCE;
}
}TransactionStatus:事务运行状态;
@Transactional 属性详解
事务传播行为
事务传播行为是为了解决业务层方法之间互相调用的事务问题。
示例:在A类的aMethod()
方法中调用了B类的bMethod()
方法。这时就涉及到业务层方法之间互相调用的事务问题。如果bMethod()
发生异常需要回滚,如何配置事务传播行为才能让aMethod()
也跟着回滚呢?
Class A { |
TransactionDefinition.PROPAGATION_REQUIRED
使用的最多的一个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:
- 如果外部方法没有开启事务的话,
Propagation.REQUIRED
修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。 - 如果外部方法开启事务并且被
Propagation.REQUIRED
修饰,则所有Propagation.REQUIRED
修饰的内部方法和外部方法均属于同一事务,只要一个方法回滚,整个事务均回滚。
- 如果外部方法没有开启事务的话,
TransactionDefinition.PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。即不论外部方法是否开启事务,
Propagation.REQUIRES_NEW
修饰的内部方法会开启自己的事务,且开启的事务相互独立,互不干扰。示例:上面的
bMethod()
使用PROPAGATION_REQUIRES_NEW
事务传播行为修饰,aMethod
还是用PROPAGATION_REQUIRED
修饰。如果aMethod()
发生异常回滚,bMethod()
不会跟着回滚,因为bMethod()
开启了独立的事务。但是,如果bMethod()
抛出了未被捕获的异常并且这个异常满足事务回滚规则,aMethod()
同样也会回滚,因为这个异常被aMethod()
的事务管理机制检测到了。Class A {
public void aMethod {
//do something
B b = new B();
b.bMethod();
}
}
Class B {
public void bMethod {
//do something
}
}TransactionDefinition.PROPAGATION_NESTED
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则等价于
TransactionDefinition.PROPAGATION_REQUIRED
。也就是说:- 在外部方法未开启事务的情况下
Propagation.NESTED
和Propagation.REQUIRED
作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰; - 如果外部方法开启事务,
Propagation.NESTED
修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务;
Class A {
// 如果aMethod回滚,bMethod和bMethod2都要回滚;
// 如果bMethod回滚,不会造成aMethod和bMethod2回滚;
public void aMethod {
//do something
B b = new B();
b.bMethod();
b.bMethod2();
}
}
Class B {
public void bMethod {
//do something
}
public void bMethod2 {
//do something
}
}- 在外部方法未开启事务的情况下
TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_SUPPORTS
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER
以非事务方式运行,如果当前存在事务,则抛出异常。
事务隔离级别
TransactionDefinition.ISOLATION_DEFAULT
:使用后端数据库默认的隔离级别,MySQL 默认采用的REPEATABLE_READ
隔离级别,Oracle 默认采用的READ_COMMITTED
隔离级别;TransactionDefinition.ISOLATION_READ_UNCOMMITTED
:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读;TransactionDefinition.ISOLATION_READ_COMMITTED
: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生;TransactionDefinition.ISOLATION_REPEATABLE_READ
: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生;TransactionDefinition.ISOLATION_SERIALIZABLE
: 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别;
MySQL InnoDB存储引擎默认支持的隔离级别是REPEATABLE-READ,在该隔离级别下InnoDB引擎使用的是Next-Key Lock锁算法,因为可以避免幻读的产生。
事务超时属性
指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition
中以int的值来表示超时时间,单位是秒,默认值为-1。
事务的只读属性
对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
为什么数据查询操作需要启用事务支持?
如果不加
Transactional
,每条SQL会开启一个单独的事务,中间被其它事务改了数据,都会实时读取到最新值。
- 如果一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持 SQL执行期间的读一致性;
- 如果一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持;
事务的回滚规则
默认情况下,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error也会导致事务回滚,但是在遇到检查型(Checked)异常时不会回滚。
// 回滚自定义的异常类型 |
@Transactional的使用
@Transactional的作用范围
方法:推荐将注解使用于方法上,注意,该注解只能应用到public方法上,否则不生效;
类:如果这个注解使用在类上,表明该注解对该类中所有的public方法都生效;
接口:不推荐在接口上使用;
参数配置
属性名 说明 propagation 事务的传播行为,默认值为 REQUIRED isolation 事务的隔离级别,默认值采用 DEFAULT timeout 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。 readOnly 指定事务是否为只读事务,默认值为 false。 rollbackFor 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。 @Transactional事务注解原理
@Transactional的工作机制是基于AOP实现的,AOP是使用动态代理实现的。如果目标对象实现了接口,默认情况下会采用JDK的动态代理,如果目标对象没有实现了接口,会使用CGLIB动态代理。
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
} else {
return new JdkDynamicAopProxy(config);
}
}
.......
}如果一个类或者类中的public方法上被标注注解
@Transactional
,Spring 容器就会在启动的时候为其创建一个代理类,在调用被@Transactional
注解的public方法时,实际调用的是TransactionInterceptor
类中的invoke()
方法。这个方法的作用就是在目标方法之前开启事务,方法执行过程中如果遇到异常的时候回滚事务,方法调用完成之后提交事务。Spring AOP自调用的问题
若同一类中的其他没有
@Transactional
注解的方法内部调用有@Transactional
注解的方法,有@Transactional
注解的方法的事务会失效。这是由于Spring AOP代理造成的,因为只有当
@Transactional
注解的方法在类以外被调用的时候,Spring事务管理才生效。示例:MyService类中的
method1()
调用method2()
就会导致method2()
的事务失效。
public class MyService {
private void method1() {
method2();
//......
}
public void method2() {
//......
}
}解决办法:避免同一类中自调用,或者使用AspectJ取代Spring AOP代理。