Spring事务和事务传播机制

事务定义 :将⼀组操作封装成⼀个执⾏单元(封装到⼀起),要么全部成功,要么全部失败。

为什么要⽤事务?

⽐如转账分为两个操作:

第⼀步操作:A 账户 -100 元。

第⼆步操作:B 账户 +100 元。

如果没有事务,第⼀步执⾏成功了,第⼆步执⾏失败了,那么 A 账户平⽩⽆故的 100 元就"⼈间蒸发"了。⽽如果使⽤事务就可以解决这个问题,让这⼀组操作要么⼀起成功,要么⼀起失败

Spring 中事务的实现

Spring 中的事务操作分为两类:

  1. 编程式事务(⼿动写代码操作事务)。
  2. 声明式事务(利⽤注解⾃动开启和提交事务)

Spring 编程式事务(了解)

SpringBoot 内置了两个对象,DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或回滚事务的,⽽ TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus,实现代码如下:

java 复制代码
@Slf4j
@RestController
@RequestMapping("/trans")
public class TransactionalController {

//    数据库事务管理器
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;
    @Autowired
    private LoginService loginService;

    @RequestMapping("/addLogin")
    public Integer addLogin(String username,String password,String power){
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        Login login = new Login(username,password,power);
        Integer result = loginService.insert(login);
        log.info("影响行数:"+ result);
//        事务回滚
//        dataSourceTransactionManager.rollback(transaction);
//        事务提交
        dataSourceTransactionManager.commit(transaction);
        return result;
    }
}

从上述代码可以看出,以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现⽅法呢?请看下⾯声明式事务:

Spring 声明式事务(⾃动)

声明式事务的实现很简单,只需要在需要的⽅法上添加 @Transactional 注解就可以实现了,⽆需⼿动开启事务和提交事务,进⼊⽅法时⾃动开启事务,⽅法执⾏完会⾃动提交事务,如果中途发⽣了没有处理的异常会⾃动回滚事务,具体实现代码如下:

java 复制代码
@Slf4j
@RestController
@RequestMapping("/trans2")
public class TransactionalController2 {
    @Autowired
    private LoginService loginService;

    @Transactional
    @RequestMapping("/addLogin")
    public Integer addLogin(String username,String password,String power){
        Login login = new Login(username,password,power);
        Integer result = loginService.insert(login);
        log.info("影响行数:"+ result);
        return result;
    }
}

当发生异常时会自动回滚事务:

java 复制代码
@Slf4j
@RestController
@RequestMapping("/trans2")
public class TransactionalController2 {
    @Autowired
    private LoginService loginService;

    @Transactional
    @RequestMapping("/addLogin")
    public Integer addLogin(String username,String password,String power){
        Login login = new Login(username,password,power);
        Integer result = loginService.insert(login);
        log.info("影响行数:"+ result);
       int a = 10/0;
        return result;
    }
}

@Transactional 作⽤范围

@Transactional 可以⽤来修饰⽅法或类:

修饰⽅法时:需要注意只能应⽤到 public ⽅法上,否则不⽣效。推荐此种⽤法。

修饰类时:表明该注解对该类中所有的 public ⽅法都⽣效。

@Transactional 参数说明

事务不会⾃动回滚解决⽅案

解决⽅案1:对于捕获的异常,事务是会⾃动回滚的,因此解决⽅案1就是可以将异常重新抛出,具体实现如下:

java 复制代码
@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE)
public Object save(User user) {
 // 插⼊数据库
 int result = userService.save(user);
 try {
 // 执⾏了异常代码(0不能做除数)
 int i = 10 / 0;
 } catch (Exception e) {
 System.out.println(e.getMessage());
 // 将异常重新抛出去
 throw e;
 }
 return result;
}

解决⽅案2:⼿动回滚事务,在⽅法中使⽤ TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚⽅法 setRollbackOnly 就可以实现回滚了,具体实现代码如下

@Transactional ⼯作原理

@Transactional 是基于 AOP 实现的,AOP ⼜是使⽤动态代理实现的。如果⽬标对象实现了接⼝,默认情况下会采⽤ JDK 的动态代理,如果⽬标对象没有实现了接⼝,会使⽤ CGLIB 动态代理。

@Transactional 在开始执⾏业务之前,通过代理先开启事务,在执⾏成功之后再提交事务。如果中途遇到的异常,则回滚事务。

@Transactional 实现思路预览:

@Transactional 具体执⾏细节如下图所示:

MySQL 事务隔离级别有 4 种

脏读:⼀个事务读取到了另⼀个事务修改的数据之后,后⼀个事务⼜进⾏了回滚操作,从⽽导致第⼀个事务读取的数据是错误的。

● 不可重复读:⼀个事务两次查询得到的结果不同,因为在两次查询中间,有另⼀个事务把数据修改了。

● 幻读:⼀个事务两次查询中得到的结果集不同,因为在两次查询中另⼀个事务有新增了⼀部分数据。

在数据库中通过以下 SQL 查询全局事务隔离级别和当前连接的事务隔离级别:

java 复制代码
select @@global.tx_isolation,@@tx_isolation;

Spring 事务隔离级别有 5 种

⽽ Spring 中事务隔离级别包含以下 5 种:

**1. **Isolation.DEFAULT:以连接的数据库的事务隔离级别为主。

  1. Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读。

  2. Isolation.READ_COMMITTED:读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读。

  3. Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)。

  4. Isolation.SERIALIZABLE:串⾏化,可以解决所有并发问题,但性能太低。

从上述介绍可以看出,相⽐于 MySQL 的事务隔离级别,Spring 的事务隔离级别只是多了⼀个 Isolation.DEFAULT(以数据库的全局事务隔离级别为主)。

Spring 中事务隔离级别只需要设置 @Transactional ⾥的 isolation 属性即可,具体实现代码如下:

java 复制代码
@RequestMapping("/save")
@Transactional(isolation = Isolation.SERIALIZABLE)
public Object save(User user) {
 // 业务实现
}

Spring 事务传播机制

Spring 事务传播机制包含以下 7 种:

  1. Propagation.REQUIRED:默认的事务传播级别,它表示如果当前存在事务,则加⼊该事务;如果当前没有事务,则创建⼀个新的事务。
  2. Propagation.SUPPORTS:如果当前存在事务,则加⼊该事务;如果当前没有事务,则以⾮事务的⽅式继续运⾏。
  3. Propagation.MANDATORY:(mandatory:强制性)如果当前存在事务,则加⼊该事务;如果当前没有事务,则抛出异常。
  4. Propagation.REQUIRES_NEW:表示创建⼀个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部⽅法是否开启事务,Propagation.REQUIRES_NEW 修饰的内部⽅法会新开启⾃⼰的事务,且开启的事务相互独⽴,互不⼲扰。
  5. Propagation.NOT_SUPPORTED:以⾮事务⽅式运⾏,如果当前存在事务,则把当前事务挂起。
  6. Propagation.NEVER:以⾮事务⽅式运⾏,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED:如果当前存在事务,则创建⼀个事务作为当前事务的嵌套事务来运⾏;如果当前没有事务,则该取值等价于 PROPAGATION_REQUIRED

以上 7 种传播⾏为,可以根据是否⽀持当前事务分为以下 3 类:

相关推荐
架构师沉默2 小时前
别又牛逼了!AI 写 Java 代码真的行吗?
java·后端·架构
DolphinDB2 小时前
集成 Prometheus 与 DolphinDB 规则引擎,构建敏捷监控解决方案
数据库
IvorySQL3 小时前
PostgreSQL 技术日报 (3月10日)|IIoT 性能瓶颈与内核优化新讨论
数据库·postgresql·开源
Java水解5 小时前
微服务架构下Spring Session与Redis分布式会话实战全解析
后端·spring
DBA小马哥6 小时前
时序数据库是什么?能源行业国产化替换的入门必看
数据库·时序数据库
后端AI实验室6 小时前
我把一个生产Bug的排查过程,交给AI处理——20分钟后我关掉了它
java·ai
凉年技术8 小时前
Java 实现企业微信扫码登录
java·企业微信
爱可生开源社区8 小时前
某马来西亚游戏公司如何从 SQL Server 迁移至 OceanBase?
数据库
狂奔小菜鸡9 小时前
Day41 | Java中的锁分类
java·后端·java ee
hooknum9 小时前
学习记录:基于JWT简单实现登录认证功能-demo
java