Spring事务和事务传播机制(上)

1. 事务回顾

        事务是在数据库阶段学习的内容,也是数据库里的机制

1.1 什么是事务

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

        事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败

1.2 为什么需要事务

        我们在进行程序开发时,也会有事务的需求。

        比如转账操作:

第一步:A 账户 - 100元;

第二步:B 账户 + 100元;

        如果没有事务,就会出现第一步执行成功了,第二步执行失败了这种情况,那么 A账户的100元就平白无故消失了。如果使用事务就可以解决这个问题,让这个组操作要么一起成功,要么一起失败。

        比如秒杀系统:

第一步:下单成功;

第二步:扣减库存。

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

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

1.3 事务的操作

        事务的操作主要有三步:

1、开启事务 start transaction / begin(一组操作前开启事务)

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

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

2. Spring 中事务的实现

        我们之前学的是mysql中的事务操作,当然Spring 对事务也进行了实现,Spring 中的事务操作分为下面两类:

1、编程式事务(手动写代码操作事务)。

2、声明式事务(利用注解自动开启和提交事务)。

        在学习事务之前,我们先准备数据和数据的访问代码

        需求:用户注册,注册时在日志表中插入一条操作记录。

-- 创建数据库
DROP DATABASE IF EXISTS trans_test;
 
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;
 
use trans_test;
 
-- 用户表
DROP TABLE IF EXISTS user_info;
CREATE TABLE user_info (
        `id` INT NOT NULL AUTO_INCREMENT,
        `user_name` VARCHAR (128) NOT NULL,
        `password` VARCHAR (128) NOT NULL,
        `create_time` DATETIME DEFAULT now(),
        `update_time` DATETIME DEFAULT now() ON UPDATE now(),
        PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT CHARACTER
SET = utf8mb4 COMMENT = '用户表';
 
-- 操作日志表
DROP TABLE IF EXISTS log_info;
CREATE TABLE log_info (
        `id` INT PRIMARY KEY auto_increment,
        `user_name` VARCHAR ( 128 ) NOT NULL,
        `op` VARCHAR ( 256 ) NOT NULL,
        `create_time` DATETIME DEFAULT now(),
        `update_time` DATETIME DEFAULT now() ON UPDATE now()
) DEFAULT charset 'utf8mb4';

2.1 代码准备

1、创建项目 spring-trans,引入 Spring Web,MyBatis,MySQL等依赖

2、配置文件

server:
  port: 8085
# 数据库连接配置
spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/book_manage?allowPublicKeyRetrieval=true&characterEncoding=utf8&useSSL=false
    username: root
    password: ******
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
  configuration:
    map-underscore-to-camel-case: true #配置驼峰自动转换
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句
  mapper-locations: classpath:mapper/**Mapper.xml

3、实体类

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;
}

4、Mapper

          UserInfoMapper

@Mapper
public interface UserInfoMapper {
    @Insert("insert into user_info(user_name, password) values (#{name}, #{password})")
    Integer insert(String name, String password);
}

         LogInfoMapper

@Mapper
public interface LogInfoMapper {
    @Insert("insert into log_info(user_name, op) values (#{name}, #{op})")
    Integer insertLog(String name, String op);
}

5、Service

        UserService:

@Service
public class UserService {
    @Autowired
    private UserInfoMapper userInfoMapper;
    public Integer registry(String name, String password) {
        return userInfoMapper.insert(name, password);
    }
}

         LogService:

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

6. Controller

@RequestMapping("/user")
@RestController
public class UserController {
 
    @Autowired
    private UserService userService;
 
    @RequestMapping("/registry")
    public Boolean registry(String name, String password) {
        //用户注册
        Integer ret = userService.registry(name, password);
        return true;
    }
}

2.2 Spring编程式事务(了解)

        Spring 手动操作事务和上面 MYSQL操作事务类似,有3个重要操作步骤:

1、开启事务(获取事务)

2、提交事务

3、回滚事务

        SpringBoot 内置了两个对象:

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

2、TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去,从而获得一个事务 TransactionStatus

        下面根据代码的实现来学习:

package com.example.spring_trans.controller;


import com.example.spring_trans.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);
        dataSourceTransactionManager.commit(transaction);
        return "注册成功";
    }
}

1、观察事务提交

        浏览器访问http://127.0.0.1:8085/user/registry?userName=%22shenmengyao%22&&password=%22111111%22

2、观察事务回滚

浏览器访问http://127.0.0.1:8085/user/registry?userName=yuanyiqi&&password=111111 ,

         数据库没多出数据 ,和事务提交相比,日志少了一行。

        以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?接下来我们学习声明式事务(注解)

3. Spring 声明式事务 @Transactional

        声明式事务的实现很简单,两哥操作步骤:

3.1 添加依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
</dependency>

3.2 在需要事务的方法上添加 @Transactional 注解

        无需手动开启事务和提交事务,进入方法时自动开启事务。方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务

        我们来看代码的实现:

@RequestMapping("/trans")
@RestController
public class TransactionalController1 {
    @Autowired
    private UserService userService;
 
    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) {
        //用户注册
        Integer ret = userService.registry(name, password);
        if(ret > 0) return  "注册成功";
        return "注册失败";
    }
}

        运行程序:127.0.0.1:8085/trans/registry?name=baixinyu&&password=111111,发现数据插入成功。

        现在修改程序,使之出现异常:

@RequestMapping("/trans")
@RestController
public class TransactionalController1 {
    @Autowired
    private UserService userService;
 
    @Transactional
    @RequestMapping("/registry")
    public String registry(String userName, String password) {
        //用户注册
        Integer ret = userService.insertUser(userName, password);
        int a = 10/0;
        if(ret > 0) return  "注册成功";
        return "注册失败";
    }
}

        浏览器访问http://127.0.0.1:8085/trans/registry?userName=zhouxiang&&password=111111,发现数据插入失败。事务发生回滚了。

        查看数据库我们发现没有新的数据插入;

        日志显示,上面因为有异常,事务回滚了。

        我们一般会在业务逻辑层当中来控制事务,因为在业务逻辑层当中,一个业务功能可能会包含多个数据访问的操作。在业务逻辑层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。上述代码在Controller中书写,只是为了方便学习。

3.3 Transactional 作用

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

1、修饰方法时:只有修饰 public 方法时才生效(修饰其他方法时不会报错,也不生效)。[推荐]

2、修饰类时:对 @Transactional 修饰的类中所有的 public 方法都生效。

 方法 / 类被 @Transactional 注解修饰时,在目标方法执行开始前,会自动开启事务,方法执行结束之后,自动提交事务

        如果在方法执行过程中,出现异常,且异常未被捕获,就进行事务回滚操作。

        如果异常被程序捕获,方法就被认为是成功执行,依然会提交事务

 修改上述代码,对异常进行捕获:

public class TransactionalController1 {
    @Autowired
    private UserService userService;
 
    @Transactional
    @RequestMapping("/registry")
    public String registry(String userName, String password) {
        //用户注册
        Integer ret = userService.insertUser(userName, password);
        try{int a = 10/0;}
        catch (Exception e){
            e.printStackTrace();
        };

        if(ret > 0) return  "注册成功";
        return "注册失败";
    }
}

        浏览器访问之前的url,发现数据成功被注册;

        周湘的数据成功被插入到数据库;

        由下面的日志信息可知。事务并没有回滚而是被提交;

        上面运行程序,发现虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交。如果需要事务进行回滚,有以下两种方式:

1、重新抛出异常

2、手动回滚事务

3.3.1 重新抛出异常 

@RequestMapping("/trans")
@RestController
public class TransactionalController {
    @Autowired
    private UserService userService;
 
    @Transactional
    @RequestMapping("/registry")
    public String registry(String name, String password) {
        //用户注册
        Integer ret = userService.registry(name, password);
        //强制程序抛出异常
        try {
            int a = 10/0;
        } catch (Exception e) {
//            e.printStackTrace();
            //将异常抛出去
            throw e;
        }
        if(ret > 0) return  "注册成功";
        return "注册失败";
    }
}

浏览器访问:http://127.0.0.1:8085/trans/registry?userName=wangyi&&password=111111

        我们发现王奕的数据信息没有被插入到数据库,查看日志信息如下:

         手动抛出异常后,说明事务回滚了。(异常并没有被捕获) 

3.3.2手动回滚事务

        使用 TransactionAspectSupport.currentTransactionStatus() 得到当前事务,并使用 SetRollbackOnly 设置 setRollbackOnly。

package com.example.spring_trans.controller;

import com.example.spring_trans.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/trans")
@RestController
public class TransactionalController1 {
    @Autowired
    private UserService userService;
 
    @Transactional
    @RequestMapping("/registry")
    public String registry(String userName, String password) {
        //用户注册
        Integer ret = userService.insertUser(userName, password);
        try{int a = 10/0;}
        catch (Exception e){
            //手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        };

        if(ret > 0) return  "注册成功";
        return "注册失败";
    }
}

继续访问之前的url,发现数据没有添加,且事务进行回滚了;

ps:本文到这里就结束了,谢谢观看;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值