第五节: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;
}
}