Spring事务

目录

1.Spring 中的事务

1.1 回顾 MySQL 中的事务

1.2 Spring 编程式事务(了解)

1.3 Spring 声明式事务@Transactional 注解(重要)

1.3.1 @Transactional 作用范围

1.3.2 异常捕获事务不回滚

1.3.3 解决异常捕获事务不回滚

1.4 @Transactional 工作原理

1.5 Spring 事务隔离级别

2.Spring 事务传播机制

2.1 传播机制


回顾 MySQL 之前学过的事务:

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

为什么要用事务?

比如转账分为两个操作:

  • 第⼀步操作:A 账户 -100 元
  • 第⼆步操作:B 账户 +100 元

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

1.Spring 中的事务

Spring 中的事务分为两类:

  • 编程式事务(手动写代码操作事务)
  • 声明式事务(利用注解自动开启和提交事务) 

在这里,我们要使用的就是声明式事务,一个注解即可搞定,编程式事务会存在代码写错误的时候,因此主要学习声明式事务,而编程式事务只需要了解即可

1.1 回顾 MySQL 中的事务

1️⃣开启事务

start transaction;

2️⃣提交事务

commit;

3️⃣回滚事务

rollback;

1.2 Spring 编程式事务(了解)

编程式事务只需要了解即可

1️⃣开启事务2️⃣提交事务3️⃣回滚事务

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

@RestController
public class UserController {
     @Resource
     private UserService userService;
     // JDBC 事务管理器
     @Resource //自动注入
     private DataSourceTransactionManager dataSourceTransactionManager;
     // 定义事务属性
     @Resource
     private TransactionDefinition transactionDefinition;
     @RequestMapping("/sava")
     public Object save(User user) {
         // 开启事务
         TransactionStatus transactionStatus = dataSourceTransactionManager
             .getTransaction(transactionDefinition);
         // 插⼊数据库
         int result = userService.save(user);
         // 提交事务
         dataSourceTransactionManager.commit(transactionStatus);
         // 回滚事务
         // dataSourceTransactionManager.rollback(transactionStatus);
         return result;
     }
}

1.3 Spring 声明式事务@Transactional 注解(重要)

声明式事务需要添加 @Transactional 注解无需手动开启事务和提交事务,进入方法时自动开启事务,方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务

设置常用配置 application.properties:

#设置数据库的相关连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/mycnblog2023?characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 设置 MyBatis XML 存放路径和命名格式
mybatis.mapper-locations=classpath:mybatis/*Mapper.xml
# 将数据库中的下划线换成驼峰,比如 user_name -> username
mybatis-plus.configuration.map-underscore-to-camel-case=true
# 配置打印 MyBatis 执行的 SQL
mybatis.configuration.map-underscore-to-camel-case=true
# 配置打印 MyBatis 执行的 SQL
logging.level.com.example.demo=debug

创建实体类 Userinfo :

package com.example.demo.model;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Userinfo {
    private int id;
    private String username;
    private String password;
    private String photo;
    private LocalDateTime createtime;
    private LocalDateTime updatetime;
    private int state;
}

添加 Mapper(UserMapper 类):

package com.example.demo.mapper;
import com.example.demo.model.Userinfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserMapper {
    @Insert("insert into userinfo(username, password) values(#{username}, #{password})")
    int add(Userinfo userinfo);
}

创建 UserService 类:

package com.example.demo.service;
import com.example.demo.mapper.UserMapper;
import com.example.demo.model.Userinfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
    //注入 Mapper
    @Autowired
    private UserMapper userMapper;
    public int add(Userinfo userinfo) {
        return userMapper.add(userinfo);
    }
}

创建 UserController 类:

package com.example.demo.controller;

import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/add")
    @Transactional
    public int add() {
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("张三");
        userinfo.setPassword("123");
        //1.调用 service 执行添加
        int result = userService.add(userinfo);
        System.out.println("result:" + result);
        int num = 10/0;//异常
        //2.将结果给前端
        return 0;
    }
}

访问 localhost:8080/user/add

  • 我们可以看到已经把 张三和123 添加到数据库中,但是发生了异常
  • 这个时候我们看数据库中是否发生回滚操作:如果回滚则和预期一样;如果不回滚则 @Transactional 注解有问题

此时数据库中没有张三,则说明发生回滚操作

1.3.1 @Transactional 作用范围

@Transactional 可以用来修饰方法或类

  • 修饰方法时:需要注意只能应用到 public 方法上,否则不生效。推荐此种用法
  • 修饰类时:表明该注解对该类中所有的 public 方法都生效

1.3.2 异常捕获事务不回滚

期望只要发生异常,就进行回滚操作,接下来我们捕获异常

package com.example.demo.controller;
import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {

    @Autowired
    private UserService userService;

    @RequestMapping("/add")
    @Transactional
    public int add() {
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("张三");
        userinfo.setPassword("123");
        //1.调用 service 执行添加
        int result = userService.add(userinfo);
        System.out.println("result:" + result);
        try {
            int num = 10 / 0;
        }catch (Exception e) {

        }
        //2.将结果给前端
        return result;
    }
}

这个时候我们看到没有发生回滚操作:@Transactional 在异常被捕获的情况下,不会进行事务自动回滚

  • @Transactional 是一个 AOP,AOP执行是环绕通知:在执行方法之前开启事务,如果没有异常就提交事务;出现异常进行事务回滚
  • 异常被捕获,代理对象检测不出异常,不会进行事务回滚

1.3.3 解决异常捕获事务不回滚

有两种解决方案:

1️⃣将异常继续抛出去(代理对象就能够感知到异常,也就能自动的回滚事务)

package com.example.demo.controller;
import com.example.demo.model.Userinfo;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @RequestMapping("/add")
    @Transactional
    public int add() {
        Userinfo userinfo = new Userinfo();
        userinfo.setUsername("小温");
        userinfo.setPassword("123");
        //1.调用 service 执行添加
        int result = userService.add(userinfo);
        System.out.println("result:" + result);
        try {
            int num = 10 / 0;
        }catch (Exception e) {
            throw e;
        }
        //2.将结果给前端
        return result;
    }
}

 这个时候我们看到没有 小温 这个用户,则发生了回滚操作

2️⃣使用代码手动回滚事务使用 TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法 setRollbackOnly 就可以实现回滚了

try {
    int num = 10 / 0;
}catch (Exception e) {
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}

1.4 @Transactional 工作原理

@Transactional 是基于 AOP 实现的,AOP 又是使用动态代理实现的。如果目标对象实现了接口,默认情况下会使用 JDK 的动态代理,如果目标对象没有实现了接口,会使用 CGLIB 动态代理。

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

具体细节:

bean进行实例化的时候, 在方法中是否有 @Transactional  ,如果有会在这个类中生成一个代理类,代理类去访问方法(@Transactional)会开启事务,并且执行目标方法而后提交事务;如果这个类的方法中没有 @Transactional,代理类会直接执行目标方法不会开启事务

1.5 Spring 事务隔离级别

  • Isolation.DEFAULT:以连接的数据库的事务隔离级别为主
  • Isolation.READ_UNCOMMITTED:读未提交,可以读取到未提交的事务,存在脏读
  • Isolation.READ_COMMITTED读已提交,只能读取到已经提交的事务,解决了脏读,存在不可重复读
  • Isolation.REPEATABLE_READ:可重复读,解决了不可重复读,但存在幻读(MySQL默认级别)
  • lsolation.SERIALIZABLE:串行化,可以解决所有并发问题,但性能太低

2.Spring 事务传播机制

Spring 事务传播机制:规定多个事务在相互调用时,事务的执行行为

换句话说:事务隔离级别是保证多个并发事务执行的可控性的(稳定性的),而事务传播机制是保证⼀个事务在多个调用间的可控性的(稳定性的)

对于事务的隔离界别解决的是多个事务同时调用数据库的问题

事务传播机制解决的是⼀个事务在多个节点(方法)中传递的问题

2.1 传播机制

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(嵌套事务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

奋斗小温

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值