Spring事务 Transaction
一:Spring事务定义,添加位置
1)事务:一整套操作,要么一起成功,要么一起失败。
2)@Transaction在 Spring中添加的位置:
类上:对类上所有的public方法配置事务
方法上:对public方法配置事务,方法上的@Transaction会覆盖类上的@Transaction
3)@Transaction Spring声明事务,通过Cglib,子类继承实现;
a) 涉及开启事务(begin/start transaction),提交事务(commit),出现RuntimeException/Error回滚事务(rollback);
b) MySQL默认自动提交事务;
c)Spring中,connection对象管理事务,在开启事务时,connection对象调用setAutoCommit(false);
connection.commit()才提交事务
二:事务四大特性ACID
原子性(Atomicity)
事务是一个原子操作,由一系列动作组成。要么全部完成,要么完全不起作用。
一致性(Consistency)
一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。
隔离性(Isolation)
可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
持久性(Durability)
一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,事务的结果被写到持久化磁盘中。
三:事务传播行为
定义:主要场景:一个方法调用另一个方法,两个方法都可以由事务控制,可以指定方法上事务行为
| 传播行为 | 含义 |
| Propogation.REQUIRED | 如果当前方法没有事务,新建一个事务,如果已经存在事务中,加入这个事务中 |
| Propogation.SUPPORTS | 支持当前事务,如果没有当前事务,就以非事务方法执行 |
| Propogation.MANDATORY | 使用当前事务,如果没有当前事务,就抛出异常 |
| Propogation.REQUIRES_NEW | 新建事务,如果存在当前事务,把当前事务挂起 |
| Propogation.NOT_SUPPORTED | 以非事务方式执行操作,如果以前存在事务,把当前事务挂起 |
| Propogation.NEVER | 以非事务方式执行操作,如果当前事务存在,则抛出异常 |
| Propogation.NESTED | 如果当前事务存在,则在嵌套事务内执行,如果当前没有事务,则执行REQUIRED类似操作 |
举例:
完全参考 https://blog.csdn.net/zhangsweet1991/article/details/83897211
1. MySQL建表:
CREATE TABLE account( userid VARCHAR(64) NOT NULL, username VARCHAR(64) NOT NULL, accountbalance DECIMAL(10,2) NOT NULL DEFAULT '0.00', createtime DATETIME DEFAULT NULL, updatetime DATETIME DEFAULT NULL, PRIMARY KEY(userid) ) ENGINE=INNODB DEFAULT CHARSET=utf8; INSERT INTO account VALUES ('1001','张三','1000.00','2018-11-09 09:00:00','2018-11-09 11:00:00'); INSERT INTO account VALUES ('1002','李四','1000.00','2018-11-09 09:02:00','2018-11-09 11:02:00');
2. Springboot搭建工程
POM
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis 启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
启动类
@MapperScan("com.example.dao")
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
yml文件
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/db1
username: root
password: root
mybatis:
type-aliases-package: com.example.domain
# log configuration
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
service层:
public interface TestServiceA {
public void dowork(Boolean isExce,String userId,double accountBalance);
}
public interface TestServiceB {
public void dowork(Boolean isExce, String userId, double accountBalance);
}
serviceImpl
@Service
public class TestServiceAImpl implements TestServiceA {
@Resource
private TestDao testDao;
@Resource
private TestServiceB testServiceB;
// A调用B,对B的7中事务级别进行测试
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void dowork(Boolean isExce,String userId,double accountBalance) {
System.out.println("进入方法doWorkA");
testDao.doWork(userId,accountBalance);
testServiceB.dowork(isExce,userId,accountBalance);
testDao.doWork(userId,-accountBalance);
System.out.println("方法workA执行结束");
}
}
@Service
public class TestServiceBImpl implements TestServiceB {
@Resource
private TestDao testDao;
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void dowork(Boolean isExce,String userId,double accountBalance) {
System.out.println("进入方法doWorkB");
testDao.doWork(userId,accountBalance);
if(isExce) {
throw new RuntimeException();
}
testDao.doWork(userId,-accountBalance);
System.out.println("方法workB执行结束");
}
}
dao层
public interface TestDao {
@Update("update account set accountBalance =accountBalance + #{accountBalance} where userid = #{userId}")
public void doWork(String userId,double accountBalance);
}
单元测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestServiceTest {
// 先不用controller,用测试类测试
@Resource
private TestServiceA testServiceA;
@Test
public void doWork() {
try {
testServiceA.dowork(true,"1001",200);
} catch (Exception e) {
System.out.println("异常" + e.getClass().getName());
}
System.out.println("success");
}
}
测试传播行为:
1)


每次测试前:表的数组都相同,如下图
结果:注 isExce=true,每次SQL 执行过程 对id='1001'用户的accountBalance+200
即serviceB的doWork存在事务,serviceB的dowork执行抛出RuntimeException,回滚;但serviceA没有事务管理
执行完默认提交;
![]()
2)


测试结果:注 isExce=true,每次SQL 执行过程 对id='1001'用户的accountBalance+200
![]()
ServiceA 和 ServiceB 的doWork方法都由事务管理,都会回滚;
3)

测试结果:注 isExce=true,SQL 执行过程 对id='1001'用户的accountBalance+200
![]()
ServiceA 和 ServiceB 的doWork方法都由没有事务管理,默认直接提交增删改;
4)


测试结果:注 isExce=true,SQL 执行过程 对id='1001'用户的accountBalance+200
![]()
A事务对dowork方法事务管理,都回滚;
5)


测试结果: 注 isExce=true,SQL 执行过程 对id='1001'用户的accountBalance+200
serviceA没有事务,执行SQL直接提交,所以为1200;因为没有事务管理,serviceB 由于@MANDATORY
抛出IllegalTransactionStateException
6)


测试结果: 注 isExce=true,SQL 执行过程 对id='1001'用户的accountBalance+200
![]()
都是ServiceA事务,都回滚;
其他情况
略
四:事务隔离级别
定义:多个事务之间相互隔离,如果多个事务操作同一批数据,可能会引发问题,需要设置事务隔离级别;
存在问题:
1. 脏读:一个事务读取到另一个事务没有提交之前的数据库中的数据
2. 不可重复读:同一个事务两次读取数据不一样,有其他并发事务进行了更新
3. 幻读:重点在于新增/删除,第1次读和第2次读的记录数不一样,有其他并发事务进行了新增/删除
隔离级别:
1)read uncommitted:读未提交
可能产生脏读,幻读,不可重复读
2)read committed
可能产生 幻读,不可重复读
3)repeatable read:可重复读(mysql默认,mysql有锁,不会幻读)
可能产生 幻读
4)serializable 串行化
对一个事务加锁,互斥
五:Spring事务回滚规则
1)默认配置,只有在抛出RuntimeException/Error时,才会回滚事务,而 checked异常则不会导致事务回滚,会被catch捕获;
2)事务方法,内部调用本来内部的其他方法,不会引起事务,因为此时的调用对象是this,而@Transaction 通过AOP,代理对象来调用时才会被事务管理
六:只读
@Transaction(readOnly = true/ false)如果事务只读,数据库会利用事务只读进行一些特定的优化;
七:事务超时
默认@Transaction(TransactionDefinition.TIMEOUT_DEFAULT),跟数据库相同;
事务超时就是事务的一个定时器,在特定时间内事务如果没有执行完毕,那么就会自动回滚,而不是一直等待其结束。

浙公网安备 33010602011771号