Spring 事务是我们实际开发中必须要使用到的一项技能,了解底层如何运行更有助于我们解决实际开发中的问题。

什么是事务?

事务是一个并发控制单位,是用户定义的一个操作序列,这些操作要么全部完成,要不全部不完成,是一个不可分割的工作单位。事务有 ACID 四个特性,即:
  1. Atomicity(原子性):事务中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。
  2. 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
  3. 事务隔离(Isolation):多个事务之间是独立的,不相互影响的。
  4. 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
而 Spring 事务,其实是事务在 Spring 中的实现。

为什么要有 Spring 事务?

举个简单的例子:张三要给李四从银行转 10 块钱,赚钱的执行流程如下:
  1. 将张三的账户余额减少 10 元。
  2. 将李四的账户余额增加 10 元。
这两个操作,要么一起都完成,要么都不完成。如果其中某个成功,另外一个失败,那么就会出现严重的问题。而要保证这个操作的原子性,就必须通过 Spring 事务来完成,这就是 Spring 事务存在的原因。 如果深入了解过 MySQL 事务,那么应该知道:MySQL 默认情况下,对于所有的单条语句都作为一个单独的事务来执行。要使用 MySQL 事务的时候,可以通过手动提交事务来控制事务范围。Spring 事务的本质,其实就是通过 Spring AOP 切面技术,在合适的地方开启事务,接着在合适的地方提交事务或回滚事务,从而实现了业务编程层面的事务操作。 # 环境搭建 1. 建表,创建 北京图书表和上海图书表 sql CREATE TABLE `bj_book` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '图书名称', `author` varchar(10) DEFAULT NULL COMMENT '作者', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3; CREATE TABLE `sh_book` ( `id` bigint NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '图书名称', `author` varchar(10) DEFAULT NULL COMMENT '作者', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3; 2. 创建spring boot项目,这里省略 3. 引入mybatis-plus依赖 xml <!--导入MyBatisPlus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.2.0</version> </dependency> 4. 添加配置文件 yaml # DataSource Config spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8 username: root password: cj123456789 mybatis-plus: #打印日志 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl 5. 主启动类添加扫描注解 yaml @MapperScan("com.chen.springtransaction.mapper") @SpringBootApplication public class SpringTransactionApplication { public static void main(String[] args) { SpringApplication.run(SpringTransactionApplication.class, args); } } 6. 创建两张表对应的基本对象信息 java //BjBook.java 北京图书表 @Data public class BjBook { private Integer id; private String name; private String author; } //ShBook.java 上海图书表 @Data public class ShBook { private Integer id; private String name; private String author; } sql public interface BjBookMapper extends BaseMapper<BjBook> { } public interface ShBookMapper extends BaseMapper<BjBook> { } java //ShBookService public interface ShBookService { public int add(BjBook book); } @Service public class BjBookServiceImpl implements BjBookService { @Autowired private BjBookMapper bjBookMapper; @Override public int add(BjBook book) { int result = bjBookMapper.insert(book); return result; } } //ShBookService public interface ShBookService { public int add(ShBook book); } @Service public class ShBookServiceImpl implements ShBookService { @Autowired private ShBookMapper shBookMapper; @Override public int add(ShBook book) { int result = shBookMapper.insert(book); result = 1/0; return result; } } 7. 测试 javascript @SpringBootTest class SpringTransactionApplicationTests { @Autowired private BjBookMapper bjBookMapper; @Autowired private ShBookMapper shBookMapper; @Test public void testSelect() { System.out.println(("----- selectAll BjBook test ------")); List<BjBook> userList1 = bjBookMapper.selectList(null); userList1.forEach(System.out::println); System.out.println(("----- selectAll ShBook test ------")); List<ShBook> userList2 = shBookMapper.selectList(null); userList2.forEach(System.out::println); } } ok,上面我们基于springboot项目把环境搭建起来了,下面会用到此案例进行探索spring中的事务。 # SpringBoot事务 Spring Boot 中操作事务有两种方式:编程式事务或声明式事务。

编程式事务

在 Spring Boot 中实现编程式事务又有两种实现方法:
  1. 使用 <font style="color:rgb(1, 1, 1);">TransactionTemplate</font> 对象实现编程式事务;
  2. 使用更加底层的 <font style="color:rgb(1, 1, 1);">TransactionManager</font> 对象实现编程式事务。

TransactionTemplate

要使用 <font style="color:rgb(74, 74, 74);">TransactionTemplate</font> 对象需要先将 <font style="color:rgb(74, 74, 74);">TransactionTemplate</font> 注入到当前类中 ,然后再使用它提供的 <font style="color:rgb(74, 74, 74);">execute</font> 方法执行事务并返回相应的执行结果,如果程序在执行途中出现了异常,那么就可以使用代码手动回滚事务,具体实现代码如下:
  1. @Autowired
  2. private TransactionTemplate transactionTemplate;
  3. @Test
  4. public void testTransactionTemplate(){
  5. BjBook book = new BjBook();
  6. book.setId(1);
  7. book.setName("西游记");
  8. book.setAuthor("吴承恩");
  9. transactionTemplate.execute(status -> {
  10. int result = 0;
  11. try {
  12. result = bjBookMapper.insert(book);
  13. //int i = 1/0;
  14. }catch (Exception e){
  15. status.setRollbackOnly();
  16. }
  17. return result;
  18. });
  19. }

TransactionManager

<font style="color:rgb(74, 74, 74);">TransactionManager</font> 实现编程式事务相对麻烦一点,它需要使用两个对象:<font style="color:rgb(74, 74, 74);">TransactionManager</font> 的子类,加上 <font style="color:rgb(74, 74, 74);">TransactionDefinition</font> 事务定义对象,再通过调用 <font style="color:rgb(74, 74, 74);">TransactionManager</font><font style="color:rgb(74, 74, 74);">getTransaction</font> 获取并开启事务,然后调用 <font style="color:rgb(74, 74, 74);">TransactionManager</font> 提供的 <font style="color:rgb(74, 74, 74);">commit</font> 方法提交事务,或使用它的另一个方法 <font style="color:rgb(74, 74, 74);">rollback</font> 回滚事务,它的具体实现代码如下:

  1. @Autowired
  2. private DataSourceTransactionManager transactionManager;
  3. @Autowired
  4. private TransactionDefinition transactionDefinition;
  5. @Test
  6. public void testTransactionManager(){
  7. BjBook book = new BjBook();
  8. book.setId(1);
  9. book.setName("西游记");
  10. book.setAuthor("吴承恩");
  11. //获取事务(1. 开启事务)
  12. TransactionStatus transaction = transactionManager.getTransaction(transactionDefinition);
  13. int result = bjBookMapper.insert(book);
  14. //2. 提交事务
  15. transactionManager.commit(transaction);
  16. //3. 回滚事务
  17. //transactionManager.rollback(transaction);
  18. }

使用编程式事务更加灵活,但写法比较麻烦。

声明式事务

声明式事务只需要在方法上或类上添加 **<font style="color:rgb(74, 74, 74);">@Transactional</font>** 注解即可,当加入了 **<font style="color:rgb(74, 74, 74);">@Transactional</font>** 注解就可以实现在方法执行前,自动开启事务;在方法成功执行完,自动提交事务;如果方法在执行期间,出现了异常,那么它会自动回滚事务。它的具体使用如下:
  1. @Autowired
  2. private BjBookService bjBookService;
  3. //注意,不能子在单元测试上使用此注解。否则操作的数据会回滚无法提交。
  4. //如果你就想直接提交,可以直接在方法上加上 @Rollback(false) 或者 @Commit ,这样事务就不会回滚了
  5. // 我这里通过调用service的add方法进行测试的
  6. @Test
  7. public void testTransactional(){
  8. BjBook book = new BjBook();
  9. book.setId(1);
  10. book.setName("西游记");
  11. book.setAuthor("吴承恩");
  12. bjBookService.add(book);
  13. }
  14. @Service
  15. public class BjBookServiceImpl implements BjBookService {
  16. @Autowired
  17. private BjBookMapper bjBookMapper;
  18. @Transactional
  19. @Override
  20. public int add(BjBook book) {
  21. int result = bjBookMapper.insert(book);
  22. result = 1/0;
  23. return result;
  24. }
  25. }

**<font style="color:rgb(74, 74, 74);">@Transactional</font>**参数

参数 说明
value 指定要使用的事务管理器的可选限定符。
propagation 事务传播机制,默认为REQUIRED
isolation 事务隔离级别,默认为DEFAULT。仅适用于传播值REQUIRED 或者 REQUIRES_NEW
timeout 事务超时,单位秒。仅适用于传播值REQUIRED 或者 REQUIRES_NEW
readonly 读写事务与只读事务。仅适用于以下值REQUIRED 或者 REQUIRES_NEW
rollbackFor 一组异常类,遇到时回滚,默认为{}
rollbackForClassName 一组异常类名,遇到时回滚,默认为{}
noRollbackFor 一组异常类,遇到时不回滚,默认为{}
noRollbackForClassName 一组异常类名,遇到时不回滚,默认为{}
  1. @Transactional(propagation = Propagation.NESTED)

事务传播类型

事务传播类型,指的是事务与事务之间的交互策略。 例如:在事务方法 A 中调用事务方法 B,当事务方法 B 失败回滚时,事务方法 A 应该如何操作?这就是事务传播类型。 Spring 事务中定义了 7 种事务传播类型,分别是:REQUIRED、SUPPORTS、MANDATORY、REQUIRES_NEW、NOT_SUPPORTED、NEVER、NESTED。其中最常用的只有 3 种,即:REQUIRED、REQUIRES_NEW、NESTED。 针对事务传播类型,要弄明白的是 4 个点:
  1. 子事务与父事务的关系,是否会启动一个新的事务?
  2. 子事务异常时,父事务是否会回滚?
  3. 父事务异常时,子事务是否会回滚?
  4. 父事务捕捉异常后,父事务是否还会回滚?

REQUIRED

REQUIRED 是 Spring 默认的事务传播类型,该传播类型的特点是:当前方法存在事务时,子方法加入该事务。此时父子方法共用一个事务,无论父子方法哪个发生异常回滚,整个事务都回滚。即使父方法捕捉了异常,也是会回滚。而当前方法不存在事务时,子方法新建一个事务。 为了验证 REQUIRED 事务传播类型的特点,来做几个测试。 testA 不开启事务,testB 开启事务,这时候 testB 就是独立的事务,而 testA 并不在事务之中。因此当 testB 发生异常回滚时,methodA 中的内容就不会被回滚。用如下的代码就可以验证我们所说的。
  1. @Override
  2. public void testA() {
  3. System.out.println("testA ~~~");
  4. bjBookMapper.insert(new BjBook(1,"西游记","吴承恩"));
  5. shBookService.testB();
  6. }
  7. @Transactional
  8. @Override
  9. public void testB() {
  10. System.out.println("testB ~~~");
  11. shBookMapper.insert(new ShBook(2,"三体","刘慈欣"));
  12. throw new RuntimeException("测试异常");
  13. }
最终的结果是:bjBook 插入了数据,shBook 没有插入数据。
testA 开启事务,testB 也开启事务。按照结论,此时 testB 会加入 testA 的事务。此时,验证当父子事务分别回滚时,另外一个事务是否会回滚。 先验证第一个:当父方法事务回滚时,子方法事务是否会回滚?
  1. @Transactional
  2. @Override
  3. public void testA() {
  4. System.out.println("testA ~~~");
  5. bjBookMapper.insert(new BjBook(1,"西游记","吴承恩"));
  6. shBookService.testB();
  7. throw new RuntimeException("测试异常");
  8. }
  9. @Transactional
  10. @Override
  11. public void testB() {
  12. System.out.println("testB ~~~");
  13. shBookMapper.insert(new ShBook(2,"三体","刘慈欣"));
  14. }
结果是:bjBook 和 shBook 都没有插入数据,即:父事务回滚时,子事务也回滚了。 继续验证第二个:当子方法事务回滚时,父方法事务是否会回滚?
  1. @Transactional
  2. @Override
  3. public void testA() {
  4. System.out.println("testA ~~~");
  5. bjBookMapper.insert(new BjBook(1,"西游记","吴承恩"));
  6. shBookService.testB();
  7. }
  8. @Transactional
  9. @Override
  10. public void testB() {
  11. System.out.println("testB ~~~");
  12. shBookMapper.insert(new ShBook(2,"三体","刘慈欣"));
  13. throw new RuntimeException("测试异常");
  14. }
结果是:bjBook 和 shBook 都没有插入数据,即:子事务回滚时,父事务也回滚了。 继续验证第三个:当字方法事务回滚时,父方法捕捉了异常,父方法事务是否会回滚?
  1. @Transactional
  2. @Override
  3. public void testA() {
  4. System.out.println("testA ~~~");
  5. bjBookMapper.insert(new BjBook(1,"西游记","吴承恩"));
  6. try {
  7. shBookService.testB();
  8. }catch (Exception e){
  9. System.out.println(e);
  10. }
  11. }
  12. @Transactional
  13. @Override
  14. public void testB() {
  15. System.out.println("testB ~~~");
  16. shBookMapper.insert(new ShBook(2,"三体","刘慈欣"));
  17. throw new RuntimeException("测试异常");
  18. }
结果是:bjBook 和 shBook 都没有插入数据,即:子事务回滚时,父事务也回滚了。所以说,这也进一步验证了之前所说的:REQUIRED 传播类型,它是父子方法共用同一个事务的。 ## REQUIRES_NEW REQUIRES_NEW 也是常用的一个传播类型,该传播类型的特点是:无论当前方法是否存在事务,子方法都新建一个事务。此时父子方法的事务时独立的,它们都不会相互影响。但父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。 测试如下。 首先,来验证一下:当父方法事务发生异常时,子方法事务是否会回滚?
  1. @Transactional
  2. @Override
  3. public void testA() {
  4. System.out.println("testA ~~~");
  5. bjBookMapper.insert(new BjBook(1,"西游记","吴承恩"));
  6. shBookService.testB();
  7. throw new RuntimeException("测试异常");
  8. }
  9. @Transactional(propagation = Propagation.REQUIRES_NEW)
  10. @Override
  11. public void testB() {
  12. System.out.println("testB ~~~");
  13. shBookMapper.insert(new ShBook(2,"三体","刘慈欣"));
  14. }
结果是:bjBook 没有插入数据,shBook 插入了数据,即:父方法事务回滚了,但子方法事务没回滚。这可以证明父子方法的事务是独立的,不相互影响。
下面,来看看:当子方法事务发生异常时,父方法事务是否会回滚?
  1. @Transactional
  2. @Override
  3. public void testA() {
  4. System.out.println("testA ~~~");
  5. bjBookMapper.insert(new BjBook(1,"西游记","吴承恩"));
  6. shBookService.testB();
  7. }
  8. @Transactional(propagation = Propagation.REQUIRES_NEW)
  9. @Override
  10. public void testB() {
  11. System.out.println("testB ~~~");
  12. shBookMapper.insert(new ShBook(2,"三体","刘慈欣"));
  13. throw new RuntimeException("测试异常");
  14. }
结果是:bjBook 没有插入了数据,shBook 没有插入数据。 从结果来看,貌似是子方法事务回滚,导致父方法事务也回滚了。但不是说父子事务都是独立的,不会相互影响么? 其实是因为子方法抛出了异常,而父方法并没有做异常捕捉,此时父方法同时也抛出异常了,于是 Spring 就会将父方法事务也回滚了。如果在父方法中捕捉异常,那么父方法的事务就不会回滚了,修改之后的代码如下所示。
  1. @Transactional
  2. @Override
  3. public void testA() {
  4. System.out.println("testA ~~~");
  5. bjBookMapper.insert(new BjBook(1,"西游记","吴承恩"));
  6. //捕获异常
  7. try {
  8. shBookService.testB();
  9. }catch (Exception e){
  10. System.out.println(e);
  11. }
  12. }
  13. @Transactional(propagation = Propagation.REQUIRES_NEW)
  14. @Override
  15. public void testB() {
  16. System.out.println("testB ~~~");
  17. shBookMapper.insert(new ShBook(2,"三体","刘慈欣"));
  18. throw new RuntimeException("测试异常");
  19. }
结果是:bjBook 插入了数据,shBook 没有插入数据。这正符合刚刚所说的:父子事务是独立的,并不会相互影响。 这其实就是上面所说的:父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。因为如果执行过程中发生 RuntimeException 异常和 Error 的话,那么 Spring 事务是会自动回滚的。

NESTED

NESTED 也是常用的一个传播类型,该方法的特性与 REQUIRED 非常相似,其特性是:当前方法存在事务时,子方法加入在嵌套事务执行。当父方法事务回滚时,子方法事务也跟着回滚。当子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。如果捕捉了异常,那么就不回滚,否则回滚。 可以看到 NESTED 与 REQUIRED 的区别在于:父方法与子方法对于共用事务的描述是不一样的,REQUIRED 说的是共用同一个事务,而 NESTED 说的是在嵌套事务执行。这一个区别的具体体现是:在子方法事务发生异常回滚时,父方法有着不同的反应动作。 对于 REQUIRED 来说,无论父子方法哪个发生异常,全都会回滚。而 REQUIRED 则是:父方法发生异常回滚时,子方法事务会回滚。而子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。 首先,来验证一下:当父方法事务发生异常时,子方法事务是否会回滚?
  1. @Transactional
  2. @Override
  3. public void testA() {
  4. System.out.println("testA ~~~");
  5. bjBookMapper.insert(new BjBook(1,"西游记","吴承恩"));
  6. shBookService.testB();
  7. throw new RuntimeException("测试异常");
  8. }
  9. @Transactional(propagation = Propagation.NESTED)
  10. @Override
  11. public void testB() {
  12. System.out.println("testB ~~~");
  13. shBookMapper.insert(new ShBook(2,"三体","刘慈欣"));
  14. }
结果是:bjBookshBook 都没有插入数据,即:父子方法事务都回滚了。这说明父方法发送异常时,子方法事务会回滚。
接着,继续验证一下:当子方法事务发生异常时,如果父方法没有捕捉异常,父方法事务是否会回滚?
  1. @Transactional
  2. @Override
  3. public void testA() {
  4. System.out.println("testA ~~~");
  5. bjBookMapper.insert(new BjBook(1,"西游记","吴承恩"));
  6. shBookService.testB();
  7. }
  8. @Transactional(propagation = Propagation.NESTED)
  9. @Override
  10. public void testB() {
  11. System.out.println("testB ~~~");
  12. shBookMapper.insert(new ShBook(2,"三体","刘慈欣"));
  13. throw new RuntimeException("测试异常");
  14. }
结果是:bjBookshBook 都没有插入数据,即:父子方法事务都回滚了。这说明子方法发送异常回滚时,如果父方法没有捕捉异常,那么父方法事务也会回滚。
最后,验证一下:当子方法事务发生异常时,如果父方法捕捉了异常,父方法事务是否会回滚?
  1. @Transactional
  2. @Override
  3. public void testA() {
  4. System.out.println("testA ~~~");
  5. bjBookMapper.insert(new BjBook(1,"西游记","吴承恩"));
  6. //捕获异常
  7. try {
  8. shBookService.testB();
  9. }catch (Exception e){
  10. System.out.println(e);
  11. }
  12. }
  13. @Transactional(propagation = Propagation.NESTED)
  14. @Override
  15. public void testB() {
  16. System.out.println("testB ~~~");
  17. shBookMapper.insert(new ShBook(2,"三体","刘慈欣"));
  18. throw new RuntimeException("测试异常");
  19. }
结果是:bjBook 插入了数据,shBook 没有插入数据,即:父方法事务没有回滚,子方法事务回滚了。这说明子方法发送异常回滚时,如果父方法捕捉了异常,那么父方法事务就不会回滚。 总结一下:
事务传播类型 特性
REQUIRED 当前方法存在事务时,子方法加入该事务。此时父子方法共用一个事务,无论父子方法哪个发生异常回滚,整个事务都回滚。即使父方法捕捉了异常,也是会回滚。而当前方法不存在事务时,子方法新建一个事务。
REQUIRES_NEW 无论当前方法是否存在事务,子方法都新建一个事务。此时父子方法的事务时独立的,它们都不会相互影响。但父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。
NESTED 当前方法存在事务时,子方法加入在嵌套事务执行。当父方法事务回滚时,子方法事务也跟着回滚。当子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。如果捕捉了异常,那么就不回滚,否则回滚。

什么时候 Spring 事务会失效?

  1. 若同一类中的其他没有 <font style="color:black;">@Transactional</font> 注解的方法内部调用有 <font style="color:black;">@Transactional</font> 注解的方法,有 <font style="color:black;">@Transactional</font> 注解的方法的事务会失效。这是由于 Spring AOP 代理的原因造成的,因为只有当 <font style="color:black;">@Transactional</font> 注解的方法在类以外被调用的时候,Spring 事务管理才生效。
  2. 另外,如果直接调用,不通过对象调用,也是会失效的。因为 Spring 事务是通过 AOP 实现的。
  3. <font style="color:black;">@Transactional</font> 注解只有作用到 <font style="color:black;">public</font> 方法上事务才生效。
  4. <font style="color:black;">@Transactional</font> 注解的方法所在的类必须被 Spring 管理。
  5. 底层使用的数据库必须支持事务机制,否则不生效。
Spring 事务执行过程中,如果抛出非 RuntimeException 和非 Error 错误的其他异常,也不会回滚

其他四个需要了解

PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。

PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。

事务隔离级别

隔离级别是指若干个并发的事务之间的隔离程度,与我们开发时候主要相关的场景包括:脏读取、重复读、幻读。

Isolation 的 Enum类中定义了五个表示隔离级别的值,如下:

  1. Isolation.DEFAULT:使用各个数据库默认的隔离级别【默认】。MySQL 默认采用的是 REPEATABLE_READ,也就是可重复读。
  2. Isolation.READ_UNCOMMITTED:读取未提交数据(会出现脏读, 不可重复读)(基本不使用)
  3. Isolation.READ_COMMITTED:读取已提交数据(会出现不可重复读和幻读)
  4. Isolation.REPEATABLE_READ:可重复读(会出现幻读)
  5. Isolation.SERIALIZABLE:串行化。最高的隔离级别,虽然可以阻止脏读、幻读和不可重复读,但会严重影响程序性能。
通常情况下,采用默认的隔离级别 <font style="color:black;">ISOLATION_DEFAULT</font> 就可以了,也就是交给数据库来决定,可以通过 <font style="color:rgb(53, 179, 120);">SELECT @@transaction_isolation;</font> 命令来查看 MySQL 的默认隔离级别,结果为 <font style="color:black;">REPEATABLE-READ</font>,也就是可重复读。

简单解释下什么是脏读、不可重复读、幻读:

  • 脏读:一个事务读取到另一事务未提交的更新数据;
  • 不可重复读: 在同一事务中,多次读取同一数据返回的结果有所不同。换句话说:后续读取可以读到另一事务已提交的更新数据。相反,”可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一事务已提交的更新数据;
  • 幻读: 一个事务读到另一个事务已提交的 insert 数据;

事务超时时间

事务超时是指一个事务所允许执行的最长时间,如果在超时时间内还没有完成的话,就自动回滚。 假如事务的执行时间格外的长,由于事务涉及到对数据库的锁定,就会导致长时间运行的事务占用数据库资源。

事务只读属性

如果一个事务只是对数据库执行读操作,那么该数据库就可以利用事务的只读属性,采取优化措施,适用于多条数据库查询操作中。 为什么一个查询操作还要启用事务支持呢? 这是因为 MySQL(innodb)默认对每一个连接都启用了 autocommit 模式,在该模式下,每一个发送到 MySQL 服务器的 SQL 语句都会在一个单独的事务中进行处理,执行结束后会自动提交事务。 那如果给方法加上了 <font style="color:black;">@Transactional</font> 注解,那这个方法中所有的 SQL 都会放在一个事务里。否则,每条 SQL 都会单独开启一个事务,中间被其他事务修改了数据,都会实时读取到。 有些情况下,当一次执行多条查询语句时,需要保证数据一致性时,就需要启用事务支持。否则上一条 SQL 查询后,被其他用户改变了数据,那么下一个 SQL 查询可能就会出现不一致的状态。

事务的回滚策略

默认情况,事务只在出现运行时异常(RuntimeException)时回滚,以及 Error,出现检查异常(checked exception,需要主动捕获处理或者向上抛出)时不回滚。 如果想要回滚特定的异常类型的话,可以这样设置:
  1. @Transactional(rollbackFor=MyException.class)

指定使用的事务管理器

value 主要用来指定不同的事务管理器,主要用来满足在同一个系统中,存在不同的事务管理器的场景需要。

比如,在Spring中声明了两种事务管理器txManager1,txManager2。然后,用户可以根据需要,修改这个参数来指定特定的txManager。

存在多个事务管理器的情况:在一个系统中,需要访问多个数据源,则必然会配置多个事务管理器。