spring的事务处理能力,本质上是数据库提供的。
一个数据库具备事务,那么它必然支持4个特性,
事务的4大特性
- 原子性
- 一致性
- 隔离性
- 持久性
这4个特性,保证了数据库处理单事务是有保障的,正确的。更多应用处理场景下,是多事务并行出现的。同时,两个或两个以上的事务,操作同一张表,就会引起冲突。为了解决事务间交互产生的问题,数据库设置了4个隔离级别
数据库的4大隔离级别
- read uncommitted (读未提交)B事务可以读取到A事务未提交的修改。有脏读的问题
- read committed (读以提交)B事务不能读取其他事务未commit的修改。有不可重复读,幻读的问题
- repeated read (可重复读) 事务第一次读和第二次读,结果不受其他事务影响,结果是一样的。有幻读的问题
- Serializeble (串行化)幻读,比如事务A对表T所有行进行update,再次查询,发现有些行没有update,原因是这些行是新增行,是其他事务添加的,导致不会修改新增的行。串行化,就是顺序执行事务。这个隔离界别要慎用,因为及其影响数据库吞吐量,未能使用到CPU的高性能。
隔离界别,从低到高。级别越高,数据库处理多事务的规则越严格,意味着要牺牲性能为代价。不同的数据库,默认的数据隔离级别有区别,大多数据库默认是repeated read。mysql默认是read committed。
spring的事务隔离级别,本质上是依照数据库来设计的。
spring的5大事务隔离级别
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别。 |
ISOLATION_READ_UNCOMMITTED | 允许读取尚未提交的更改。可能导致脏读、幻影读或不可重复读。 |
ISOLATION_READ_COMMITTED | 允许从已经提交的并发事务读取。可防止脏读,但幻影读和不可重复读仍可能会发生。 |
ISOLATION_REPEATABLE_READ | 对相同字段的多次读取的结果是一致的,除非数据被当前事务本身改变。可防止脏读和不可重复读,但幻影读仍可能发生。 |
ISOLATION_SERIALIZABLE | 完全服从ACID的隔离级别,确保不发生脏读、不可重复读和幻影读。这在所有隔离级别中也是最慢的,因为它通常是通过完全锁定当前事务所涉及的数据表来完成的 |
理解数据库隔离级别,自然就明白了设置spring各个事务的原因。
怎么理解事务传播特性?
这要从编程的角度。一个方法执行,在它之间,假如已经创建了事务,那它要创建新事务还是加入原有事务;假如之前没有事务,那它又该如何处理;假如外层事务异常要回滚,内层事务要不要回滚;假如内层事务异常要回滚,外层事务要不要回滚。这就要求必须定义好事务的传播特性,使得程序任一步都知道如何处理事务问题
传播行为 | 意义 |
---|---|
PROPAGATION_REQUIRES | 表示当前方法必须在一个事务中运行。如果一个现有事务正在进行中,该方法将在那个事务中运行,否则就要开始一个新事务。 |
PROPAGATION_REQUIRES_NEW | 表示当前方法必须在它自己的事务里运行。一个新的事务将被启动,而且如果有一个现有事务在运行的话,则将在这个方法运行期间被挂起。 |
PROPAGATION_MANDATORY | 表示该方法必须运行在一个事务中。如果当前没有事务正在发生,将抛出一个异常 |
PROPAGATION_NESTED | 表示如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于封装事务进行提交或回滚。如果封装事务不存在,行为就像PROPAGATION_REQUIRES一样。他会和父事务一起commit,当它回滚时,父事务有条件的选择是否跟随回滚,或者继续执行 |
PROPAGATION_NEVER | 表示当前的方法不应该在一个事务中运行。如果一个事务正在进行,则会抛出一个异常。 |
PROPAGATION_NOT_SUPPORTED | 表示该方法不应该在一个事务中运行。如果一个现有事务正在进行中,它将在该方法的运行期间被挂起。 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务性上下文,但是如果有一个事务已经在运行的话,它也可以在这个事务里运行。 |
spring编写事务,有声明式事务和编程式事务,这篇文章,只介绍声明式事务。
声明式事务,先要添加bean配置
<!-- 将数据源绑定到事务实现类 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 开启事务配置 -->
<tx:annotation-driven/>
使用注解@Transactional,来声明事务。
@Transactional可以写在任何的类上,要求该类的bean是spring容器管理的。常见的bean是@Controller,@Service,@Repository。有类级别和方法级别的声明。常用spring的猿,我就不多解析了。
@Transactional有两点需要注意
- 只能声明在public的method。原因是spring是通过JDK代理或者CGLIB代理的,生成的代理类,只能处理public方法;
- 不能被类内部方法调用。还是因为代理的原因,类内部自调用,不会经过代理类,所以@Transactional不会生效
class A {
@Transactional
public void method1() {
method2();
}
@Transactional
public void method2() {
}
}
method1调用method2,不会触发method2的@Transactional声明。
在常见的ssm系统里,@Transactional会定义在controller的public方法上,因为一次请求往往意味着一连串的数据库操作,使用事务正好,一旦这次请求成功,则全部commit,一旦失败,则全部rooback。
还要继续深入 @Transactional,简单使用 @Transactional,只是使用默认的参数,不能完全满足业务需求。它含有的参数,是用来设置事务隔离级别和传播特性。
@Transactional的参数讲解
参数名称 | 功能描述 |
---|---|
readOnly | 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true) |
rollbackFor | 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class}) |
rollbackForClassName | 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:指定单一异常类名称:@Transactional(rollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,”Exception”}) |
noRollbackFor | 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class}) |
noRollbackForClassName | 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:指定单一异常类名称:@Transactional(noRollbackForClassName=”RuntimeException”)指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,”Exception”}) |
propagation | 该属性用于设置事务的传播行为。例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true) |
isolation | 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置 |
timeout | 该属性用于设置事务的超时秒数,默认值为-1表示永不超时 |
以上参数大多比较好理解。关注点就在于事务的传播特性,当一个拥有事务的方法调用另外一个事务的方法,要理解好传播特性,才不会导致错误的事务处理。比如使用PROPAGATION_REQUIRES_NEW,事务和事务之间是隔离开的,内层事务失败不会影响外层事务。