一、创建事务
1、配置事务管理器
<!-- 配置数据源 dbcp2 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
<property name="driverClassName" value="${db.driver}"></property>
<property name="url" value="${db.url}"></property>
<property name="username" value="${db.username}"></property>
<property name="password" value="${db.password}"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务支持类 -->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
2、使用注解
2.1、注解类
注解类表示在该类中的所有方法都开启事务
@Transactional(propagation = Propagation.REQUIRED)
@Service("addUserImpl1")
public class AddUserImpl1 implements IUser1 {
@Override
public void addUser1() {
User user=new User();
user.setSn("aa");
user.setCn("bb");
user.setSex(null);
personMapper.insert(user);
}
}
2.2、注解方法
注解方法表示指定该方法开启事务
@Service("addUserImpl1")
public class AddUserImpl1 implements IUser1 {
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void addUser1() {
User user=new User();
user.setSn("aa");
user.setCn("bb");
user.setSex(null);
personMapper.insert(user);
}
}
二、事务的传播机制
1、PROPAGATION.REQUIRED (默认)
支持当前事务,如果当前没有事务,则新建事务
如果当前存在事务(外层方法存在事务),则加入当前事务,合并成一个事务
如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景
外层事务:@Transactional(propagation = Propagation.*)或没有事务
内层事务:@Transactional(propagation = Propagation.REQUIRED)
外层事务调用内层事务
如果外层有事务(当前事务),则内层事务加入到外层事务,一块提交,一块回滚(如果内层出现异常且不捕获,此时外层不能捕获该异常,因为 rollback-only = true 必须进行回滚,所以内外层一起回滚(此情况可改为 SUPPORT );如果内层事务捕获异常,则内外层事务不用回滚)。如果外层没有事务,新建一个事务执行(此时只有内层有事务,内外层互不影响;内层或外层无论谁出现异常且没有被捕获都不会影响到对方)
2、Propagation.REQUIRES_NEW
新建事务,如果当前存在事务,则把当前事务挂起
这个方法会独立提交事务,不受调用者的事务影响,父级异常,它也是正常提交
外层事务:@Transactional(propagation = Propagation.*)或没有事务
内层事务:@Transactional(propagation = Propagation.REQUIRES_NEW)
外层事务调用内层事务
无论外层是否有事务,都会在内层中创建一个事务。当内层事务出现异常且没有捕获该异常时,内层事务回滚,如果外层没有对内层抛出的异常进行捕获,则外层(有事务)就回滚;反之不回滚;当内层事务出现异常并捕获该异常时,则内外层都不用进行回滚
总结:内外层相互独立,如果某一层中出现异常但没有捕获异常,则该层进行回滚;如果都没有异常或已捕获异常则正常提交
3、Propagation.NESTED
如果当前存在事务,它将会成为外层事务的一个子事务,方法结束后并没有提交,只有等外层事务结束才提交
如果外层没有事务,则新建事务
如果它异常,父级可以捕获它的异常而不进行回滚,正常提交
但如果外层异常,内层必然回滚(外层有事务的情况下),这就是和 REQUIRES_NEW 的区别
是否回滚由外层事务决定,外层事务不出现异常或捕获异常则内外层事务都不回滚,否则内外层一起回滚
4、SUPPORTS
如果当前存在事务,则加入事务
如果当前不存在事务,则以非事务方式运行
5、NOT_SUPPORTED
以非事务方式运行
如果当前存在事务,则把当前事务挂起
6、MANDATORY
如果当前存在事务,则运行在当前事务中
如果当前无事务,则抛出异常,也即父级方法必须有事务
7、NEVER
以非事务方式运行,如果当前存在事务,则抛出异常,即父级方法必须无事务
三、其他参数
1、isolation
读未提交
读未提交(Read Uncommitted),是最低的隔离级别,所有的事务都可以看到其他未提交的事务的执行结果。只能防止第一类更新丢失,不能解决脏读,可重复读,幻读,所以很少应用于实际项目。(读事务和写事务同时执行,读事务可以访问写事务未提交的数据)
读已提交
读已提交(Read Committed), 在该隔离级别下,一个事务的更新操作结果只有在该事务提交之后,另一个事务才可能读取到同一笔数据更新后的结果。可以防止脏读,但是不能解决可重复读和幻读的问题。(读事务和写事务同时执行,但写事务未提交的数据, 读事务不能访问,可以防止脏读)
可重复读
可重复读(Repeatable Read),MySQL默认的隔离级别。在该隔离级别下,一个事务多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的。可以防止脏读、不可重复读的问题,不过还是会出现幻读。(读事务和写事务同时执行,读事务未提交, 写事务不能修改该数据,可以防止脏读、不可重复读)
串行化
串行化(Serializable),这是最高的隔离级别。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。在这个级别,可以解决上面提到的所有并发问题,但可能导致大量的超时现象和锁竞争,通常不会用这个隔离级别。
A、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
B、不可重复读:事务 A 多次读取同一数据,事务 B
在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。C、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
2、readOnly
是否为只读事务, 默认 false
3、rollbackFor
指定回滚的异常类型,遇到时必须回滚
如:
rollbackFor = {NullPointerException.class,ArrayIndexOutOfBoundsException.class}
4、rollbackForClassName
指定回滚的异常类名,遇到时必须回滚
如:
rollbackForClassName = {"NullPointerException","ArrayIndexOutOfBoundsException"}
5、noRollbackFor
指定不用回滚的异常类型,遇到时不用回滚
如:
noRollbackFor = {NullPointerException.class,ArrayIndexOutOfBoundsException.class}
6、noRollbackForClassName
指定不用回滚的异常类名,遇到时不用回滚
如:
noRollbackForClassName= {"NullPointerException","ArrayIndexOutOfBoundsException"}
7、timeout
事务超时时间