目录
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 手动回滚事务
1. 创建项目、数据库及MyBatis配置
1.1 创建数据库及java实体类
创建数据库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;
}
1.2 使用yml配置MyBatis
# 端口配置
server:port: 8080
# 数据库连接配置
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: xxxxxxdriver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #配置打印MyBatis日志map-underscore-to-camel-case: true #配置转换驼峰
1.3 对应三层架构开发
创建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 {@Autowiredprivate UserInfoMapper userInfoMapper;public Integer insertUser(String userName, String password){return userInfoMapper.insert(userName,password);}
}
编程式事务与声明式事务的不同体现在controller中,不同的事务类型将创建不同的Controller,稍后完成controller对service的调用。
2. Spring编程式事务
2.1 编写UserController类
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 {@Autowiredprivate 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 "注册成功";}
}
启动项目。
2.2 接口测试
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的用户信息;
2.23关于事务回滚与事务提交的日志
上例中第二次执行(事务提交)的日志如下:
使用userName=wangwu&password=123456作为参数根据路由映射再次测试接口,并进行事务提交,其日志如下:
可见相较于事务回滚,事务提交多了一条committing的日志。
3. Spring声明式事务
3.1 编写TransController类
对于@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 {@Autowiredprivate 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 "注册成功";}
}
启动项目。
3.2 接口测试
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表,可查看到新增的数据信息。
3.3 关于@Transactional实现事务回滚的情况
现已验证,使用@Transactional进行编程式事务管理时,当接口内部抛出运行时异常,或接口内部抛出异常并未捕获、抛出异常捕获但未正确处理时,都会导致事务回滚。
除了以上方式令事务回滚外,有两种方式可以进行操作。
3.3.1 重新抛出异常
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 {@Autowiredprivate 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数据表,也没有新增数据。即捕获异常又重新抛出时进行事务回滚。
3.3.2 手动回滚事务
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 {@Autowiredprivate 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对于事物的提交或回滚处理是按照完整的方法进行判断的。若方法中存在异常并未捕获,或异常捕获了并未处理时,则会认为方法出错,则进行回滚。