【JavaEE】(22) Spring 事务
一、Spring 中事务的实现
事务是一系列操作的集合,是原子的。要么都成功,要么都失败。比如转账:A账户-100,B账户+100,只有部分操作成功会导致业务逻辑错误。
1、准备工作
创建数据库:
-- 创建数据库
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';
创建 Spring Boot 项目。引入依赖 Spring Web、MyBatis、MySQL驱动、lombok 工具。配置数据库连接、MyBatis 日志和驼峰自动转换。
实体类:
package com.edu.spring.trans.demo.entity;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;
}package com.edu.spring.trans.demo.entity;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 插入一个用户:
package com.edu.spring.trans.demo.mapper;import com.edu.spring.trans.demo.entity.UserInfo;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;@Mapper
public interface UserMapper {@Insert("insert into user_info(user_name, password) values(#{userName}, #{password})")Integer insert(UserInfo userInfo);
}
service:
package com.edu.spring.trans.demo.service;import com.edu.spring.trans.demo.entity.UserInfo;
import com.edu.spring.trans.demo.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Service
public class UserService {@Autowiredprivate UserMapper userMapper;public Integer register(UserInfo userInfo) {return userMapper.insert(userInfo);}
}
2、Spring 编程方式实现
package com.edu.spring.trans.demo.controller;import com.edu.spring.trans.demo.entity.UserInfo;
import com.edu.spring.trans.demo.service.UserService;
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.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@Autowired// 事务管理器private DataSourceTransactionManager dataSourceTransactionManager;@Autowired// 事物的配置private TransactionDefinition transactionDefinition;@GetMapping("/register")public String register(UserInfo userInfo) {// 开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);// 注册用户Integer result = userService.register(userInfo);// 回滚事务
// dataSourceTransactionManager.rollback(transactionStatus);// 提交事务dataSourceTransactionManager.commit(transactionStatus);return "注册成功 result=" + result;}
}
3、Spring 注解声明方式实现
配置事务依赖:
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId>
</dependency>
在事务类或方法上加注解 @Transactional,只对 public 修饰的方法生效。
(1)程序正常执行,事务提交
(2)异常捕获,提交
(3)异常未捕获(Error、RuntimeException),回滚
抛出了异常,才能识别到异常,才回滚。
(4)异常未捕获(受查异常),提交
(5)异常捕获又抛出,回滚
(6)希望异常捕获仍回滚,手动回滚
希望触发异常时,就算捕获了,也不要提交。
二、@Transactional 详解
1、rollbackFor、noRollbackFor
2、isolation 事务的隔离级别
3、propagation 事务的传播机制
当事务方法 A 调用事务方法 B 时,B 是加入 A 的事务还是需要开启新的事务,以及方法 B 抛出异常时对 A 事务是否存在影响,是由事务的传播机制决定。
准备:事务方法 A 调用了事务方法 B 注册用户和事务方法 C 插入操作日志。
以下级别是控制被调用事务(B、C 事务)。
(1)REQUIRED(*)
默认事务传播级别。若存在事务 A,B 则加入;若不存在事务 A,B 则创建新事务。
① 事务 B、C 共用方法 A 的事务,事务 C 抛出异常,导致整个事务回滚。
② 事务 A不存在,B 创建新事务。 B 抛出异常,会回滚。
(2)SUPPORTS
若 A 存在事务,B 则加入;A 不存在事务,B 以非事务方法运行。
① 事务 A 存在,与 REQUIRED 相同。
② 事务 A 不存在,直接调用事务 B,B 以非事务方式运行。B 抛出异常,不会回滚。
(3)MANDATORY
若 A 存在事务,B 则加入事务;若 A 不存在事务,B 则抛出异常。
(4)REQUIRES_NEW(*)
不管 A 是否有事务,B 都创建新事务。
A 存在事务,B 创建独立于 A 的新事务,B 抛出异常回滚,但 A 会提交。
(5)NOT_SUPPORTED
不管 A 是否有事务,B 都以非事务方式运行。
A 有事务,B 触发异常,B 会提交。
(6)NEVER
若 A 存在事务,则抛出异常。
(7)NESTED
若 A 存在事务,B 创建 A 的嵌套事务;若 A 不存在事务,B 创建新事务。
(8)NESTED 和 REQUIRED 的区别
在例子中,它们的部分事务发生异常时,所有事务都回滚了。
但 NESTED 可以实现部分回滚(事务 C 发生异常后手动回滚,事务 B 就提交了),而 REQUIRED 只能全部回滚(事务 C 发生异常后手动回滚,会导致整个 A 事务回滚)。