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:本文到这里就结束了,谢谢观看;