目录
1. 创建项目、数据库及MyBatis配置
1.1 创建数据库及java实体类
1.2 使用yml配置MyBatis
1.3 对应三层架构开发
2. Spring编程式事务
2.1 编写UserController类
2.2 接口测试
2.23关于事务回滚与事务提交的日志
3. Spring声明式事务
3.1 编写TransController类
3.2 接口测试
3.3 关于@Transactional实现事务回滚的情况
3.3.1 重新抛出异常
3.3.2 手动回滚事务
创建数据库trans_test和两张数据表:user_info和log_Info:
DROP DATABASE IF EXISTS trans_test;
CREATE DATABASE trans_test DEFAULT CHARACTER SET utf8mb4;
-- 用户表
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` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
`update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE = INNODB DEFAULT charset= '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';
创建model包,并在其下分别创建UserInfo和LogInfo的java类对应数据表字段,其中user_name和create_time、update_time均需采用java实体类的小驼峰命名规范:
package com.bite.transaction.model;
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.bite.transaction.model;
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;
}
# 端口配置
server:
port: 8080
# 数据库连接配置
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=false
username: root
password: xxxxxx
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #配置打印MyBatis日志
map-underscore-to-camel-case: true #配置转换驼峰
创建service包、mapper包,并在其下分别创建UserService、LogService类,以 及UserInfoMapper和LogInfoMapper接口。
编写思路相同,即service调用mapper。
以UserXXX相关信息为例,UserInfoMapper接口内容如下:
package com.bite.transaction.mapper;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info(user_name, password) values(#{userName},#{password})")
Integer insert(@Param("userName") String userName,@Param("password") String password);
}
UserService类内容如下:
package com.bite.transaction.service;
import com.bite.transaction.mapper.UserInfoMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public Integer insertUser(String userName, String password){
return userInfoMapper.insert(userName,password);
}
}
编程式事务与声明式事务的不同体现在controller中,不同的事务类型将创建不同的Controller,稍后完成controller对service的调用。
Springboot有两个内置对象:
(1)DataSourceTransactionManager:
表示一个事务管理器,用于开启事务、提交事务、回滚事务等;
(2)TransactionDefinition:
表示事务的属性,获取事务时需传递该对象从而获得一个事务TransactionStatus;
创建controller包,对于编程式事务,创建UserControlle类,编写UserController类内容:
package com.bite.transaction.controller;
import com.bite.transaction.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
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Autowired
// 定义事务管理器
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
// 定义事务属性
private TransactionDefinition transactionDefinition;
@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.commit(transaction);
// 回滚事务
dataSourceTransactionManager.rollback(transaction);
return "注册成功";
}
}
启动项目。
1、仅保留执行事务回滚的代码,根据路由映射测试接口,以userName=zhangsan&password=123456作为参数:
postman响应处显示注册成功,查看数据库的user_info表,并未有数据更新:
2、仅保留执行事务提交的代码,根据路由映射测试接口,以userName=lisi&password=123456作为参数:
postman响应处显示注册成功,查看数据库的user_info表:
由于id作为主键采取了自增方式,而此时第二次执行insert操作后id为2,说明第一次执行insert也执行成功,但事务回滚,故数据表user_info中又删除了id=1的用户信息;
上例中第二次执行(事务提交)的日志如下:
使用userName=wangwu&password=123456作为参数根据路由映射再次测试接口,并进行事务提交,其日志如下:
可见相较于事务回滚,事务提交多了一条committing的日志。
对于@Transactional注解进行声明式事务,其进行事务提交和回滚的原则如下:
(1)所在方法没有抛出异常时,事务就提交;
(2)所在方法抛出异常了(报错却没有捕获,或报错捕获却未处理)且异常为运行时异常,或Error时,事务就回滚;
(其他异常,包括NullPointerException等都不会回滚)
在controller包下创建TransController类用于实现声明式事务,编写TransController类内容如下:
package com.bite.transaction.controller;
import com.bite.transaction.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/trans")
public class TransController {
@Autowired
private UserService userService;
/*
* 未抛出异常(正常执行)时,进行事务提交*/
@Transactional
@RequestMapping("/registry")
public String registry(String userName, String password){
Integer result = userService.insertUser(userName, password);
log.info("用户插入成功,result: "+result);
return "注册成功";
}
/*
* 抛出异常时且未处理,进行事务回滚*/
@Transactional
@RequestMapping("/registry2")
public String testRegistryException(String userName, String password){
Integer result = userService.insertUser(userName, password);
int tmp = 10/0;
log.info("用户插入成功,result: "+result);
return "注册成功";
}
/*
* 抛出异常但对异常进行捕获,进行事务提交*/
@Transactional
@RequestMapping("/registry3")
public String testRegistryException2(String userName, String password){
Integer result = userService.insertUser(userName, password);
try{
int tmp = 10/0;
}catch(Exception e){
log.info("程序出错");
}
log.info("用户插入成功,result: "+result);
return "注册成功";
}
}
启动项目。
1、根据路由映射测试接口1,以userName=zhaoliu&password=123456作为参数。postman显示注册成功,查看控制台日志:
由于存在committing日志,说明当前无异常时进行事务执行。
查看数据库的user_info表,可查看到新增的数据信息。
2、根据路由映射测试接口2,以userName=tiqnqi&password=123456作为参数。postman显示服务器内部错误,查看控制台日志:
对应数据库的user_info数据表,也没有新增数据。即抛出异常时进行事务回滚。
3、根据路由映射测试接口3,以userName=aaa&password=123456作为参数。postman显示注册成功,查看控制台日志:
由于存在committing日志,说明当前无异常时进行事务执行。
查看数据库的user_info表,可查看到新增的数据信息。
现已验证,使用@Transactional进行编程式事务管理时,当接口内部抛出运行时异常,或接口内部抛出异常并未捕获、抛出异常捕获但未正确处理时,都会导致事务回滚。
除了以上方式令事务回滚外,有两种方式可以进行操作。
package com.bite.transaction.controller;
import com.bite.transaction.service.UserService;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
@RestController
@RequestMapping("/trans")
public class TransController {
@Autowired
private UserService userService;
/*
* 异常捕获后重新抛出,进行事务回滚*/
@Transactional
@RequestMapping("/registry4")
public String testRegistryException3(String userName, String password){
Integer result = userService.insertUser(userName, password);
try{
int tmp = 10/0;
}catch(Exception e){
log.info("程序出错");
throw e;
}
log.info("用户插入成功,result: "+result);
return "注册成功";
}
}
根据路由映射测试接口4,以userName=bbb&password=123456作为参数。postman显示服务器内部错误,查看控制台日志:
对应数据库的user_info数据表,也没有新增数据。即捕获异常又重新抛出时进行事务回滚。
package com.bite.transaction.controller;
import com.bite.transaction.service.UserService;
import lombok.extern.slf4j.Slf4j;
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;
@Slf4j
@RestController
@RequestMapping("/trans")
public class TransController {
@Autowired
private UserService userService;
/*
* 手动回滚事务*/
@Transactional
@RequestMapping("/registry5")
public String testRegistryException4(String userName, String password){
Integer result = userService.insertUser(userName, password);
try{
int tmp = 10/0;
}catch(Exception e){
log.info("程序出错");
// 获取当前事务状态,令其回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
log.info("用户插入成功,result: "+result);
return "注册成功";
}
}
根据路由映射测试接口5,以userName=ccc&password=123456作为参数。postman显示注册成功,查看控制台日志:
可见实现了手动事务回滚。
@Transactional对于事物的提交或回滚处理是按照完整的方法进行判断的。若方法中存在异常并未捕获,或异常捕获了并未处理时,则会认为方法出错,则进行回滚。