第五节:springboot整合Mybatis(声明式事务@Transactional)

前言

ps:一般会在springBoot的启动类加上@EnableTransactionManagement启动事务,在类上使用@Transactional就可以启动事务了。不过不加@EnableTransactionManagement,springBoot也会自动给你装载了@EnableTransactionManagement,启动事务了。

一、准备工作

1、mysql新增t_car表

在这里插入图片描述

/*
 Navicat MySQL Data Transfer

 Source Server         : 本地数据库3306
 Source Server Type    : MySQL
 Source Server Version : 50735
 Source Host           : localhost:3306
 Source Schema         : study-demo

 Target Server Type    : MySQL
 Target Server Version : 50735
 File Encoding         : 65001

 Date: 05/07/2023 10:05:11
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_car
-- ----------------------------
DROP TABLE IF EXISTS `t_car`;
CREATE TABLE `t_car`  (
  `car_id` int(20) NOT NULL,
  `car_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `gmt_create` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `gmt_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`car_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

2、新增Car类

package com.gzgs.studydemo02.web.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.Date;

@Data
@NoArgsConstructor //无参构造函数
@AllArgsConstructor//有参构造(全字段)
@Accessors(chain = true)//链式; 存取器。通过该注解可以控制getter和setter方法的形式。
@TableName("t_car")
public class Car {

    @TableId(value = "car_id", type = IdType.ID_WORKER)
    private Long carId;//主键

    private String carName;

    private Date gmtCreate;//创建时间

    private Date gmtModified;//修改时间
}


3、新增CarMapper类

package com.gzgs.studydemo02.web.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.gzgs.studydemo02.web.entity.Car;

public interface CarMapper extends BaseMapper<Car> {

}

4、新增CarServicer类

package com.gzgs.studydemo02.web.service;

public interface CarService {
}

5、新增CarServicerImpl类

package com.gzgs.studydemo02.web.service.impl;

import com.gzgs.studydemo02.web.service.CarService;

@Service
public class CarServiceImpl implements CarService {

}

二、事务测试

1、@Transactional-readOnly只读

描述:当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false

首先在UserServiceImpl新增transactionalTest1()方法,模拟查询,因为加了在方法上@Transactional(readOnly=true),所以两次的结果会一样,不会因为在查询的过程中查询数据导致结果不一致。
代码如下:

    @Override
    @Transactional(readOnly = true)
    public void transactionalTest1() throws InterruptedException {

        System.out.println("第一次查询的用户总数:"+userMapper.selectCount(null));

        System.out.println("=================睡眠20s==============");
        TimeUnit.SECONDS.sleep(20);

        System.out.println("第二次查询的用户总数:"+userMapper.selectCount(null));

    }

在test目录上写测试用例模拟,先执行TransactionalTests1()测试用例,因为会睡眠20秒,所以在这期间再执行saveUser()测试用例,新增一条数据。

package com.gzgs.studydemo02;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.gzgs.studydemo02.web.entity.User;
import com.gzgs.studydemo02.web.mapper.UserMapper;
import com.gzgs.studydemo02.web.service.CarService;
import com.gzgs.studydemo02.web.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class TransactionalTests {

    @Autowired
    private UserService userService;

    @Autowired
    private CarService carService;

    @Autowired
    private UserMapper userMapper;

    @Test
    void TransactionalTests1() throws InterruptedException {

        userService.transactionalTest1();
    }


    @Test
    void saveUser() {
        User user = new User();
        user.setUserId(10L)
                .setAge(12)
                .setUserName("用户1");

        userMapper.insert(user);
    }


}

结果如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
实际上已经有一条数据了,但是第二次查询用户的总数还是0,那是因为mybatis一级缓存的原因,两条查询只执行一条。如果不加事务,则会查询两次,每次查询都会重新生成一个sqlSession对象。

如果去掉@Transactional(readOnly = true),结果会如下:
在这里插入图片描述
ps:不过正常情况下,不会在一个方法里面查两次一样的记录,一般都会查询一次后,进行逻辑处理获取你想要的数据。

2、@Transactional-rollbackFor需要进行回滚的异常类(正确演示)

描述:当方法中抛出指定异常数组中的异常时,则进行事务回滚
多个异常可以这么写@Transactional(rollbackForClassName={“RuntimeException”,“Exception”})

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transactionalTest1() throws Exception {

        Car car = new Car();
        car.setCarId(10l)
           .setCarName("消防车");
        carMapper.insert(car);
        //模拟抛出异常java.lang.ArithmeticException: / by zero
        int i = 1/0;

    }

执行前或后t_car表数据没有插入消防车的数据,如下:
在这里插入图片描述
如果删除@Transactional(rollbackFor = Exception.class),异常报错后,会插入消防车的数据,如下:
在这里插入图片描述

3、@Transactional-rollbackFor需要进行回滚的异常类(错误演示)

描述:加上try捕获异常后,事务不会生效

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transactionalTest1() throws Exception {

        //加上捕获异常后,事务会失效
        try {
            Car car = new Car();
            car.setCarId(10l)
                    .setCarName("消防车");

            carMapper.insert(car);

            int i = 1/0;
        }catch (Exception e){
            e.printStackTrace();
        }


    }

需要自己手动回滚异常【TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();】

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transactionalTest1() throws Exception {

        //加上捕获异常后,事务会失效
        try {
            Car car = new Car();
            car.setCarId(10l)
                    .setCarName("消防车");

            carMapper.insert(car);

            int i = 1/0;
        }catch (Exception e){
            e.printStackTrace();
            //手工回滚异常
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }


    }

三、事务不生效原因

1、方法创建错误

非public方法、final修饰、static

2、异常吃掉了

这个种情况上面有说过,需要手动设置回滚事务,或者: catch 处理完成后,再把异常抛出去:throw e;

@Transactional(rollbackFor = Exception.class)
    public void transactionalTest1() throws Exception {

        //加上捕获异常后,事务会失效
        try {
            Car car = new Car();
            car.setCarId(10l)
                    .setCarName("消防车");
            carMapper.insert(car);
            int i = 1/0;
        }catch (Exception e){
            //事务生效1:再把异常抛出去
            throw e;
            //事务生效2:手工回滚异常
            //TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }

3、同一个类中(或不同类中),A方法调B方法

A方法调用B方法,A方法没有配事务,B方法配事务。 -> 事务失效

package com.gzgs.studydemo02.web.service.impl;

import com.gzgs.studydemo02.web.entity.Car;
import com.gzgs.studydemo02.web.mapper.CarMapper;
import com.gzgs.studydemo02.web.service.CarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;


@Service
public class CarServiceImpl implements CarService {

    @Autowired
    private CarMapper carMapper;

    //这是A方法
    @Override
    public void transactionalTestA() throws Exception {
        Car car = new Car();
        car.setCarId(20L).setCarName("推土机");
        carMapper.insert(car);
        transactionalTestB();
    }

    //这是B方法
    @Transactional(rollbackFor = Exception.class)
    public void transactionalTestB() throws Exception {
        Car car = new Car();
        car.setCarId(30L).setCarName("警车");
        carMapper.insert(car);
        //模拟异常
        int i =1/0;
    }
}

A方法调用B方法,A方法配事务,B方法不配事务或者配事务。 -> 事务生效

package com.gzgs.studydemo02.web.service.impl;

import com.gzgs.studydemo02.web.entity.Car;
import com.gzgs.studydemo02.web.mapper.CarMapper;
import com.gzgs.studydemo02.web.service.CarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;


@Service
public class CarServiceImpl implements CarService {

    @Autowired
    private CarMapper carMapper;

    //这是A方法
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void transactionalTestA() throws Exception {
        Car car = new Car();
        car.setCarId(20L).setCarName("推土机");
        carMapper.insert(car);
        transactionalTestB();
    }

    //这是B方法
    public void transactionalTestB() throws Exception {
        Car car = new Car();
        car.setCarId(30L).setCarName("警车");
        carMapper.insert(car);
        //模拟异常
        int i =1/0;
    }
}

4、其他

1、存储引擎不支持;

2、spirng配置没有开启事务;

3、类没有被spring管理;

在这里插入图片描述

4、多线程情况下;