在实际开发中,当我们调用一个基于Spring
的Service
接口方法时,可能会产生服务接口方法的嵌套调用的情况,Spring通过事务传播行为控制为当前的事务如何传播到被嵌套调用的目标服务接口方法中
Spring在TransactionDefinition
接口中规定了7
种类型的事务传播行为,它们规定了事务方法和事务方法发生嵌套调用时事务如何进行传播。
Spring 事务机制
Spring 提供了统一的事务处理机制来处理不同的数据访问技术的事务处理。Spring 的事务机制提供了一个 PlatformTransactionManager接口,不同的数据访问技术的事务使用不同的接口实现。
Spring 提供了的事务处理有两种使用方式,分别是编程式事务和声明式事务。实际开发大多使用声明式事物,也称为注解式事务,通过在类上或方法上添加@Transaction
注解来开启事务。
事务访问及实现
Spring提供了一个抽象类AbstractPlatformTransactionManager
来实现这个统一的接口,其它具体的数据访问技术的事务处理类继承这个抽象类。
数据访问技术 | 事务实现 |
---|---|
jdbc | DataSourceTransactionManager |
hibernate | HibernateTransactionManager |
jpa | JpaTransactionManager |
jta(分布式事务) | JtaTransactionManager |
定义事务管理器
@Configuration
public class dataSource {
@Bean
public PlatformTransactionManager transactionManager(){
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setDataSource(dataSource());
return transactionManager;
}
}
声明式事务
Spring 支持声明式事务,在类或方法上使用@Transactional
注解来指标当前类下所有的方法或当前方法使用事务。声明式事务是基于AOP
来实现的。@Transactional来自于 org.springframework.transaction.annotation.Transactional 包。
Spring 提供了一个开启声明式事务的注解@EnableTransactionManagement
,使用该注解后,Spring 容器会自动扫描使用@Transactional注解的类和方法。
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@EnableTransactionManagement
public class AppConfig {
}
事务失效原因
@Transactional 注解只能应用到
public
可见度的方法上。 如果应用在protected、private
或者 package 可见度的方法上,也不会报错,不过事务设置不会起作用。@Transactional 注解默认只对
Error
和RuntimeException
生效 ,即对unchecked
异常生效。其他继承自 java.lang.Exception 得异常统称为 Checked Exception,如 IOException、TimeoutException 等。例如 读写文件异常,网络异常则不会回滚。
可指定回滚的异常类型,如下:
@Transactional(rollbackFor=Exception.class)
数据库引擎要能支持事务控制。例如,MySQL 的 InnoDB 支持事务,MyISAM 则不支持事务。
是否开启了事务注解解析。
Spring Boot 默认开启了事务自动配置,Spring 需要在 XML 文件配置注解驱动来开启对注解的解析。
添加事务注解的类所在的包是否被 Spring 扫描到。
方法内使用
try...catch...
捕获异常且没有抛出,事务则会失效。
事务控制
事务传播行为
事务传播行为类型 | 说明 |
---|---|
PROPAGATION_REQUIRED = 0 | 如果当前没有事务,则新建一个事务; 如果已经存在一个事务,则加入到这个事务中。这是最常见的选择。 (如果被调用端发生异常,那么调用端和被调用端事务都将回滚) |
PROPAGATION_SUPPORTS = 1 | 支持当前事务。 如果当前没有事务,则以非事务的方式执行。 |
PROPAGATION_MANDATORY = 2 | 使用当前的事务。 如果当前没有事务,则抛出异常。 |
PROPAGATION_REQUIRES_NEW = 3 | 新建自己的事务。如果当前存在事务,则把当前事务挂起。 (必须运行在自己的事务中,直到自己的事务提交或回滚,挂起的事务才恢复执行) |
PROPAGATION_NOT_SUPPORTED = 4 | 以非事务的方式执行操作。 如果当前存在事务,则把当前事务挂起。 |
PROPAGATION_NEVER = 5 | 以非事务的方式执行。 如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED = 6 | 如果当前存在事务,则在嵌套事务内执行; 如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 (如果封装事务存在,并且外层事务抛出异常回滚, 那么内层事务必须回滚,反之,内层事务并不影响外层事务) |
在使用PROPAGATION_NESTED
时,底层的数据源必须基于JDBC3.0
,并且实现者需要支持保存点事务机制。
事务隔离级别
事务隔离级别 | 说明 |
---|---|
ISOLATION_DEFAULT = -1 | 由底层数据库决定事务隔离级别作为默认设置 |
ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED = 1 | 可能出现脏读、不可重复读和幻读 |
ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED = 2 | 阻止脏读,可能出现不可重复读和幻读 |
ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ = 4 | 阻止脏读和不可重复读,可能出现幻读 |
ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE = 8 | 不允许脏读、不可重复读和幻读出现。级别最高,开销最大 |
脏读:读到另一个事务未提交的数据。如果另一个事务回滚,则当前事务读到的数据就无效。
不可重复读:在一个事务内多次读到的数据是不一致(原始读取不可重复, 读取数据本身的对比
)。如:t1事务读取了数据, t2事务改变了数据, t1事务检验数据再次读取,两次数据不一致。
幻读:在一个事务内多次读取的结果集不一致(读取数据条数的对比
)。 如:t1修改所有数据, t2又插入了数据, t1再次读取会发现还有数据未被修改,产生幻觉。在 READ_UNCOMMITTED 隔离级别下, 不管事务2的插入操作是否提交, 事务1在插入操作之前和之后执行相同的查询,取得的结果集是不同的, 所以READ_UNCOMMITTED 无法避免幻读的问题。
@Transactional属性
- propagation:指定事务传播行为, 默认是
Propagation.REQUIRED
。 - isolation:指定事务隔离级别, 默认是
Isolation.DEFAULT
。 - readOnly:指定事务是否只读, 默认是
false
。 - timeout:指定事务过期时间, 默认是
TIMEOUT_DEFAULT = -1
, 即由数据库底层决定事务的过期时间。 - rollbackFor:指定那些异常要引起事务回滚, 值必须是
Throwable
的子类, 默认是运行时异常和错误异常都会引起事务回滚, 。 - noRollbackFor:指定那些异常不引起事务回滚, 值必须是
Throwable
的子类。
@Transactional源码
@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;
//指定某些异常来执行事务回滚
Class<? extends Throwable>[] rollbackFor() default {};
//同上
String[] rollbackForClassName() default {};
//指定某些异常不执行事务回滚
Class<? extends Throwable>[] noRollbackFor() default {};
//同上
String[] noRollbackForClassName() default {};
}
Isolation 源码
Isolation:事务隔离级别源码
public enum Isolation {
//默认隔离级别,由底层数据库决定; 其它隔离级别与jdbc的隔离级别对应
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
//可能出现脏读、不可重复读和幻读
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
//不允许脏读(不允许读取未提交的数据), 但可能出现不可重复读和幻读
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
//不允许脏读和不可重复读, 但可能出现幻读
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
//不允许脏读、不可重复读和幻读出现(三种情况都被禁止)
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
//传入代表隔离级别的数值
Isolation(int value) { this.value = value; }
//同上
public int value() { return this.value; }
}
Propagation 源码
Propagation:事务传播行为
public enum Propagation {
//支持当前事务, 如果事务不存在则创建, 默认值
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
//支持当前事务, 如果事务不存在则以非事务方式执行
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
//支持当前事务, 如果没有事务则抛出异常
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
//如果当前已存在事务则暂停, 创建一个新事务
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
//如果当前已存在事务则暂停, 以非事务方式执行
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
//以非事务方式执行, 如果当前已存在事务则抛出异常
NEVER(TransactionDefinition.PROPAGATION_NEVER),
//如果当前已存在事务, 则在嵌套事务中执行(类似于REQUIRED), 注意:实际创建嵌套事务仅适用于特定事务
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
//传入代表传播行为的数字
Propagation(int value) { this.value = value; }
//同上
public int value() { return this.value; }
}
@Transactional使用
- 作用在类上,表示当前类的所有public(被外部调用)方法都开启事务。
- 作用在方法上,表示此方法(public)开启事务。
- 如果在类上和方法上同时使用,则类级别的注解会被方法级别的注解重载, 方法使用的是自己的事务。
Spring JPA 事务
查看JpaRepository
的实现类SimpleJpaRepository
源码,可以看到在类和 save, delete 方法上都添加了注解,开启了事务控制,其它查询事务默认启用readOnly = true
只读属性, 但方法的readOnly
仍为默认的false
。
@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
.....
}
参考资料
注意:本文归作者所有,未经作者允许,不得转载