目录
一、声明式事务
Spring 的事务管理分为两种:
- 编程式事务管理:通过编写代码实现的事务管理,包括定义事务的开始、正常执行后的事务提交和异常时的事务回滚。
- 声明式事务管理:建立在 AOP 之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务管理不需要入侵代码,并且提供了 @Transactional 注解更为简单、快捷地进行事务操作,推荐使用。
事务属性介绍
事务隔离级别
事务隔离级别指的是若干个并发的事务之间的隔离程度,一般可分为以下五个级别:
- DEFAULT:默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这个值就是READ_COMMITTED。
- READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。
- READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
- REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
- SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
这里对脏读,不可重复读,幻读做个解释
脏读:
脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并一定最终存在的数据,这就是脏读。
脏读最大的问题就是可能读到不存在的数据,就像上图所说,B的更新数据被A读取了,但是B回滚了,更新数据全部还原,就是说A刚刚被读到的数据并没有存在于数据库中。
不可重复读:
不可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。
A 多次读取同一数据,但B在A多次读取的过程中,对数据作了更新并提交,导致A多次读取同一数据时,结果不一致。
幻读:
一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了一个“幻影”
事务传播行为(Propagation)
所谓事务传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时指定的一个事务性方法的执行行为
一般可分为以下六种行为:
- REQUIRED:默认值。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED。
事务常用配置
- readOnly:该属性用于设置当前事务是否为只读事务,设置为 true 表示只读,false 则表示可读写,默认值为 false。
@Transactional(readOnly=true)
- rollbackFor:该属性用于设置需要进行回滚操作的异常类数组,当方法中抛出指定异常时,则进行事务回滚操作。
// 指定单一异常类
@Transactional(rollbackFor=RuntimeException.class);
// 指定多个异常类
@Transactional(rollbackFor={RuntimeException.class, Exception.class});
事务回滚规则
Spring 事务管理器会捕获当前事务内未处理的异常,然后依据规则决定是否要进行事务回滚操作
写一个新增的实例
1.先配置文件
<!--对事务进行通知-->
<tx:advice id="advice" transaction-manager="transactionManager">
<!-- 事务配置
属性:name:对哪些方法起作用,例如:insert* 表示所有以 insert 开头的方法名称。
一般只需要对增、删、改方法添加事务
rollback-for:指定需要进行事务回滚的异常类,默认是 uncheck 异常
其它属性一般默认即可
-->
<tx:attributes>
<tx:method name="insert*" rollback-for="java.lang.Exception"/>
<tx:method name="delete*" rollback-for="java.lang.Exception"/>
<tx:method name="update*" rollback-for="java.lang.Exception"/>
</tx:attributes>
</tx:advice>
2.在Mapper接口中添加新增方法
@Insert("insert into t_sysuser(account,realName, password, roleId) " +
"values(#{account},#{realName}, #{password}, #{roleId})")
@Options(useGeneratedKeys = true, keyProperty = "id")
int insert(SysUser sysUser);
3.在Service接口中添加方法定义
// 正常插入
int insertTest1(SysUser sysUser);
// 插入后抛出异常
int insertTest2(SysUser sysUser);
4.在Service实现类添加方法
@Override
public int insertTest1(SysUser sysUser) {
return sysUserMapper.insert(sysUser);
}
@Override
public int insertTest2(SysUser sysUser) {
int count = sysUserMapper.insert(sysUser);
int i = 1 / 0; // By Zero,会抛出一个 RuntimeException 子类异常
return count;
}
5.编写测试类
public void insertTest() {
ApplicationContext context=new ClassPathXmlApplicationContext("applicationContext.xml");
SysUserService sysUserService= (SysUserService) context.getBean("sysUserService");
SysUser sysUser = new SysUser(null,"hh","123456","小哈哈",null,null,
null,null,3,null,null,null,null,null);
System.out.println("insertTest1:" + sysUserService.insertTest1(sysUser));
System.out.println();
SysUser sysUser1 = new SysUser(null,"mm","123456","绵绵",null,null,
null,null,2,null,null,null,null,null);
System.out.println("insertTest2:" + sysUserService.insertTest2(sysUser1));
}
最后测试结果就是只有sysUser() 方法的记录插入成功,而sysUser() 方法报错
二、注解事务
注解方式使用
使用注解方式的前提是开启事务的注解支持,有两种方式配置:
①XML 方式开启事务的注解支持
<!--开启注解事务-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
②注解方式开启事务的注解支持
// 其它注解
@EnableTransactionManagement // 开启事务的注解支持,会自动加载事务管理器
public class SpringConfig {
// 注册各种 bean
}
开启事务的注解支持之后,可以不用使用 XML,而是直接将 @Transactional 注解作用在类或者方法上。
// rollback-for 属性:指定需要进行事务回滚的异常类,默认是 uncheck 异常
@Override
@Transactional(rollbackFor = Exception.class)
public int insertTest1(SysUser sysUser) {
return sysUserMapper.insert(sysUser);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int insertTest2(SysUser sysUser) {
int count = sysUserMapper.insert(sysUser);
int i = 1 / 0; // By Zero,会抛出一个 RuntimeException 子类异常
return count;
}
然后进行测试,结果还是只有sysUser() 方法的记录插入成功,而sysUser() 方法报错
注意事项
注意1:@Transactional 注解仅对 public 方法有效,如果应用在 private 和 protected 方法上是不起作用的。
注意2:@Transactional 注解可以应用在类上,也可以应用在方法上;如果同时应用在类和方法上,方法上的配置会覆盖类上的配置。
注意3:@Transactional 注解默认捕获 unchecked 异常时才会进行事务回滚,一般需要使用 rollbackFor 指定异常。
注意4:@Transactional 注解的方法仅被外部类方法调用时才会起有效,同一个类方法间的内部调用是不起作用的。这是因为该注解的原理是 AOP,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。
- 同一个类中没有 @Transactional 注解的方法 A 内部调用有 @Transactional 注解的方法 B,B 方法的事务被忽略,相当于调用普通方法,不发生回滚。
- 同一个类中有 @Transactional 注解的方法 A 内部调用有 @Transactional 注解的方法 B,B 方法的事务被忽略,相当于调用普通方法,只有方法 A 的事务生效。
也就是说,一个方法内部调用同一个类的另一个方法,是没有通过代理类的,事务也就无法生效。