Spring 事务

1.事务的概念

1.1什么是事务

事务是一组操作的集合,是一个不可分割的操作

1.2 为什么需要事务?

我们在进⾏程序开发时,也会有事务的需求.⽐如转账操作:

第⼀步:A账⼾-100元.第⼆步:B账⼾+100元.

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

⽐如秒杀系统,第⼀步:下单成功第⼆步:扣减库存

下单成功后,库存也需要同步减少.如果下单成功,库存扣减失败,那么就会造成下单超出的情况.所以就需要把这两步操作放在同⼀个事务中.要么⼀起成功,要么⼀起失败.

理解事务概念为主,实际企业开发时,并不是简单的通过事务来处理.

1.3 事务的操作

事务的操作主要有三步:

  1. 开启事starttransaction/begin(⼀组操作前开启事务)

  2. 提交事务:commit(这组操作全部成功,提交事务)

3.回滚事务:rollback(这组操作中间任何⼀个操作出现异常,回滚事务)

2.Spring中事务的实现

以上是数据库中事务的操作,那我们放到Spring中如何实现事务呢

Spring中事务分成两类

1.编程式事务

手动开启事务,手动提交事务

2.声明式事务 (更好实现)

通过注解来实现

我们先进行数据的准备

需要准备以下数据

//实体类
import lombok.Data;
import java.util.Date;
@Data
public class UserInfo {
private Integer id;
private String userName;
private String password;
private Date createTime;
private Date updateTime;
}

import lombok.Data;
import java.util.Date;
@Data
public class LogInfo {
private Integer id;
private String userName;
private String op;
private Date createTime;
private Date updateTime;
}


//. Mapper
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Mapper;
    @Mapper
    public interface UserInfoMapper {
    @Insert("insert into user_info(`user_name`,`password`)values(#{name},#{pa
    Integer insert(String name,String password);
    }


    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Mapper;
@ Mapper
public interface LogInfoMapper {
@Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
Integer insertLog(String name,String op);
}


//5. Service
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public void registryUser(String name,String password){
//插⼊⽤⼾信息
userInfoMapper.insert(name,password);
}
}


@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
public void insertLog(String name,String op){
//记录⽤⼾操作
logInfoMapper.insertLog(name,"⽤⼾注册");
}
}



//6. Contrller
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
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("/registry")
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
return "注册成功";
}
}

1.编程式事务

1.Spring⼿动操作事务和上⾯MySQL操作事务类似,有3个重要操作步骤:

• 开启事务(获取事务)

• 提交事务

• 回滚事务

2.SpringBoot内置了两个对象:

  1. DataSourceTransactionManager 事务管理器.⽤来获取事务(开启事务),提交或回滚事务

  1. TransactionDefinition 是事务的属性,在获取事务的时候需要将

TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus

我们先根据代码来学习事务,代码如下

package com.example.trans1.controller;

import com.example.trans1.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;
    @Autowired
    private UserService userService;
    @RequestMapping("/registry")
    public  String registry(String userName,String passWord){
        //开起事务
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(transactionDefinition);
        Integer result=userService.insertUser(userName,passWord);
        //回滚事务
        log.info("用户插入成功,result:"+result);
        dataSourceTransactionManager.rollback(transaction);
        return  "注册成功";
    }
}

我们运行程序,运行postman观察结果

我们执行成功了数据,但是点开数据库发现并没有数据,这种情况就是数据回滚了

我们再来试一下提交事务

运行程序,观察数据库

2.声明式事务

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

我们先来看代码实现

package com.example.trans1.controller;

import com.example.trans1.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/trans")
@RestController
@Slf4j
public class TransController {
    @Autowired
    private UserService userService;
    @Transactional
    @RequestMapping("/registry")
    public  String registry(String userName,String password){
        Integer result=userService.insertUser(userName,password);
        log.info("用户插入成功,result:"+result);
        return  "注册成功";
    }
}

这里我们可以看到,只需要加上一个注解 @Transactional,就可以实现,我们观察运行结果,

1.发现成功的实现了提交,那我们怎么进行回滚呢,

这里情况就是没有抛出异常时,事务就进行提交,抛出了异常并且没有捕获或者捕获了没有处理,事务就进行回滚,如下

2.假如我们处理了异常,就会发现事物提交成功

3,假如我们捕获了异常,又重新抛出

我们发现程序执行出错,事务进行回滚

4.我们怎么在声明中进行手动回滚呢

我们需要加入以下代码

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

图如下

复制代码

5.假如我们抛出了异常,但是抛的其他异常,

我们发现有些异常能回滚,有些异常会正常提交

因为@Transactional 默认只在遇到运⾏时异常和Error时才会回滚,⾮运⾏时异常不回 滚,即Exception的⼦类中,除了RuntimeException及其⼦类.

3.Transactional详解

1.rollbackFor

那我们该怎么对所有的异常都进行捕获呢,我们就需要这个rollbackfor

如果我们需要所有异常都回滚,需要来配置 @Transactional 注解当中的 rollbackFor 属性,通过 rollbackFor 这个属性指定出现何种异常类型时事务进⾏回滚

如下图

我们就加上rollbackfor注解,并且指定什么异常进行回滚

2.事务的隔离级别

1. sql标准定义了四种隔离级别

  1. 读未提交(READUNCOMMITTED):读未提交,也叫未提交读.该隔离级别的事务可以看到其他事务中未提交的数据.因为其他事务未提交的数据可能会发⽣回滚,但是该隔离级别却可以读到,我们把该级别读到的数据称之为脏数据,这个问题称之为脏读.

  2. 读提交(READCOMMITTED):读已提交,也叫提交读.该隔离级别的事务能读取到已经提交事务的数据,**该隔离级别不会有脏读的问题.但由于在事务的执⾏中可以读取到其他事务提交的结果,所以在不同时间的相同SQL查询可能会得到不同的结果,**这种现象叫做不可重复读

  3. 可重复读(REPEATABLEREAD):事务不会读到其他事务对已有数据的修改,即使其他事务已提交.也就可以确保同⼀事务多次查询的结果⼀致,但是其他事务新插⼊的数据,是可以感知到的.这也就引发了幻读问题.可重复读,是MySQL的默认事务隔离级别.

⽐如此级别的事务正在执⾏时,另⼀个事务成功的插⼊了某条数据,但因为它每次查询的结果都是⼀样的,所以会导致查询不到这条数据,⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因).明明在事务中查询不到这条信息,但⾃⼰就是插⼊不进去,这个现象叫幻读.

  1. 串⾏化(SERIALIZABLE):序列化,事务最⾼隔离级别.它会强制事务排序,使之不会发⽣冲突,从⽽解决了脏读,不可重复读和幻读问题,但因为执⾏效率低,所以真正使⽤的场景并不多.

2.Spring的隔离级别有五种

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

  2. Isolation.READ_UNCOMMITTED :读未提交,对应SQL标准中 READ UNCOMMITTED

  3. Isolation.READ_COMMITTED :读已提交,对应SQL标准中 READ COMMITTED

  4. Isolation.REPEATABLE_READ :可重复读,对应SQL标准中 REPEATABLE READ

  5. Isolation.SERIALIZABLE :串⾏化,对应SQL标准中 SERIALIZABLE

我们可以通过设置来设置级别

3.事务传播机制

事务传播机制就是:多个事务⽅法存在调⽤关系时,事务是如何在这些⽅法间进⾏传播的

⽐如有两个⽅法A,B都被 @Transactional 修饰,A⽅法调⽤B⽅法

A⽅法运⾏时,会开启⼀个事务.当A调⽤B时,B⽅法本⾝也有事务,此时B⽅法运⾏时,是加⼊A的事务,还是创建⼀个新的事务呢?这个就涉及到了事务的传播机制

1.事务的传播机制有哪些

特别注意

7.NESTED,当前存在多个事务,想当与它的主事务和附属事务,当一个事务出错时,只需要及时处理当前的事务,就不会影响其他事务的执行

4.总结

  1. Spring中使⽤事务,有两种⽅式:编程式事务(⼿动操作)和声明式事务.其中声明式事务使⽤较多,在⽅法上添加 @Transactional 就可以实现了

  2. 通过 @Transactional(isolation = Isolation.SERIALIZABLE) 设置事务的隔离别.Spring中的事务隔离级别有5种

  3. 通过@Transactional(propagation=Propagation.REQUIRED)设置事务的传播机制,Spring中的事务传播级别有7种,重点关注 REQUIRED (默认值)和 REQUIRES_NEW

相关推荐
零炻大礼包28 分钟前
【SQL server】数据库远程连接配置
数据库
duration~33 分钟前
Maven随笔
java·maven
zmgst37 分钟前
canal1.1.7使用canal-adapter进行mysql同步数据
java·数据库·mysql
随心............38 分钟前
python操作MySQL以及SQL综合案例
数据库·mysql
€☞扫地僧☜€39 分钟前
docker 拉取MySQL8.0镜像以及安装
运维·数据库·docker·容器
CopyDragon44 分钟前
设置域名跨越访问
数据库·sqlite
xjjeffery1 小时前
MySQL 基础
数据库·mysql
跃ZHD1 小时前
前后端分离,Jackson,Long精度丢失
java
写bug的小屁孩1 小时前
前后端交互接口(三)
运维·服务器·数据库·windows·用户界面·qt6.3
恒辉信达1 小时前
hhdb数据库介绍(8-4)
服务器·数据库·mysql