目录
一、事务回顾
1、什么是事务?
2、为什么需要事务?
3、事务的操作
二、Spring 中事务的实现
1、代码准备:
(1)创建项目 spring-trans,引入 Spring Web,MyBatis,MySQL等依赖
(2)配置文件
(3)实体类
(4)Mapper
(5)Service
(6)Controller
2、Spring编程式事务(了解)
(1)观察事务提交
(2)观察事务回滚
3、Spring 声明式事务 @Transactional
(1)添加依赖
(2)在需要事务的方法上添加 @Transactional 注解
(3)Transactional 作用
1、重新抛出异常
2、手动回滚事务
一、事务回顾
事务是在数据库阶段学习的内容,也是数据库里的机制。
1、什么是事务?
事务是一组操作的集合,是一个不可分割的操作。
事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。
2、为什么需要事务?
我们在进行程序开发时,也会有事务的需求。
比如转账操作:
第一步:A 账户 - 100元。 第二步:B 账户 + 100元。
如果没有事务,第一步执行成功了,第二步执行失败了,那么 A账户的100元就平白无故消失了。如果使用事务就可以解决这个问题,让这个组操作要么一起成功,要么一起失败。
比如秒杀系统:
第一步:下单成功。 第二步:扣减库存。
下单成功后,库存也需要同步减少。如果下单成功,库存扣减失败,那么就会造成下单超出的情况。所以就需要把这两部操作放在同一个事务中。要么一起成功,要么一起失败。
这里理解事务概念为主,实际企业开发时,并不是简单的通过事务来处理。
3、事务的操作
事务的操作主要有三步:
1、开启事务 start transaction / begin(一组操作前开启事务)
2、提交事务:commit(这组操作全部成功,提交事务)
3、回滚事务:rollback(这组操作中间任何一个操作出现异常,回滚事务)
-- 开启事务
start transaction;-- 提交事务
commit;-- 回滚事务
rollback;
二、Spring 中事务的实现
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';
1、代码准备:
(1)创建项目 spring-trans,引入 Spring Web,MyBatis,MySQL等依赖
(2)配置文件
spring:datasource:url: jdbc:mysql://127.0.0.1:3306/trans_test?characterEncoding=utf8&useSSL=falseusername: rootpassword: rootdriver-class-name: com.mysql.cj.jdbc.Driver
mybatis:configuration: # 配置打印 MyBatis⽇志log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case: true #配置驼峰⾃动转换
(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 {@Autowiredprivate UserInfoMapper userInfoMapper;public Integer registry(String name, String password) {return userInfoMapper.insert(name, password);}
}
LogService:
@Service
public class LogService {@Autowiredprivate LogInfoMapper logInfoMapper;public void insertLog(String name, String op) {//记录用户操作logInfoMapper.insertLog(name, "用户注册");}
}
(6)Controller
@RequestMapping("/user")
@RestController
public class UserController {@Autowiredprivate UserService userService;@RequestMapping("/registry")public Boolean registry(String name, String password) {//用户注册Integer ret = userService.registry(name, password);return true;}
}
2、Spring编程式事务(了解)
Spring 手动操作事务和上面 MYSQL操作事务类似,有3个重要操作步骤:
1、开启事务(获取事务)
2、提交事务
3、回滚事务
SpringBoot 内置了两个对象:
1、DataSourceTransactionManager 事务管理器。用来获取事务(开启事务),提交或回滚事务。
2、TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去,从而获得一个事务 TransactionStatus。
下面根据代码的实现来学习:
@RequestMapping("/user")
@RestController
public class UserController {//JDBC 事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;//定义事务属性@Autowiredprivate TransactionDefinition transactionDefinition;@Autowiredprivate UserService userService;@RequestMapping("/registry")public Boolean registry(String name, String password) {//开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);//用户注册Integer ret = userService.registry(name, password);//提交事务dataSourceTransactionManager.commit(transactionStatus);//回滚事务//dataSourceTransactionManager.rollback(transactionStatus);return true;}
}
(1)观察事务提交
//提交事务dataSourceTransactionManager.commit(transactionStatus);
运行程序:127.0.0.1:8080/user/registry?name="lisi"&&password="123"
(2)观察事务回滚
//回滚事务dataSourceTransactionManager.rollback(transactionStatus);
运行程序:127.0.0.1:8080/user/registry?name="zhangsan"&&password="1234"
数据库没多出数据:
和事务提交相比,日志少了一行。
以上代码虽然可以实现事务,但操作也很繁琐,有没有更简单的实现方法呢?接下来我们学习声明式事务(注解)。
3、Spring 声明式事务 @Transactional
声明式事务的实现很简单,两步操作:
(1)添加依赖
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId>
</dependency>
(2)在需要事务的方法上添加 @Transactional 注解
无需手动开启事务和提交事务,进入方法时自动开启事务。方法执行完会自动提交事务,如果中途发生了没有处理的异常会自动回滚事务。
我们来看代码的实现:
@RequestMapping("/trans")
@RestController
public class TransactionalController {@Autowiredprivate 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:8080/trans/registry?name="wangwu"&&password="12345",发现数据插入成功。
现在修改程序,使之出现异常:
@RequestMapping("/trans")
@RestController
public class TransactionalController {@Autowiredprivate UserService userService;@Transactional@RequestMapping("/registry")public String registry(String name, String password) {//用户注册Integer ret = userService.registry(name, password);//强制程序抛出异常int a = 10/0;if(ret > 0) return "注册成功";return "注册失败";}
}
运行程序:127.0.0.1:8080/trans/registry?name="wangwu"&&password="12345",发现数据插入失败。事务发生回滚了。
上面因为有异常,事务回滚了。
我们一般会在业务逻辑层当中来控制事务,因为在业务逻辑层当中,一个业务功能可能会包含多个数据访问的操作。在业务逻辑层来控制事务,我们就可以将多个数据访问操作控制在一个事务范围内。上述代码在Controller中书写,只是为了方便学习。
(3)Transactional 作用
@Transactional 可以用来修饰方法或类:
1、修饰方法时:只有修饰 public 方法时才生效(修饰其他方法时不会报错,也不生效)。[推荐]
2、修饰类时:对 @Transactional 修饰的类中所有的 public 方法都生效。
方法 / 类被 @Transactional 注解修饰时,在目标方法执行开始前,会自动开启事务,方法执行结束之后,自动提价事务。
如果在方法执行过程中,出现异常,且异常未被捕获,就进行事务回滚操作。
如果异常被程序捕获,方法就被认为是成功执行,依然会提交事务。
修改上述代码,对异常进行捕获:
@RequestMapping("/trans")
@RestController
public class TransactionalController {@Autowiredprivate 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();}if(ret > 0) return "注册成功";return "注册失败";}
}
运行程序:127.0.0.1:8080/trans/registry?name="zhaoliu"&&password="12345",数据库新增了一行数据
说明事务没有回滚,而是提交事务了。
上面运行程序,发现虽然程序出错了,但是由于异常被捕获了,所以事务依然得到了提交。如果需要事务进行回滚,有以下两种方式:
1、重新抛出异常
2、手动回滚事务
1、重新抛出异常
@RequestMapping("/trans")
@RestController
public class TransactionalController {@Autowiredprivate 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 "注册失败";}
}
运行程序:127.0.0.1:8080/trans/registry?name="zhaoliu"&&password="12345",发现
手动抛出异常后,说明事务回滚了。(异常并没有被捕获)
2、手动回滚事务
使用 TransactionAspectSupport.currentTransactionStatus() 得到当前事务,并使用 SetRollbackOnly 设置 setRollbackOnly。
@RequestMapping("/trans")
@RestController
public class TransactionalController {@Autowiredprivate 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;//手动回滚事务TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}if(ret > 0) return "注册成功";return "注册失败";}
}
运行程序:127.0.0.1:8080/trans/registry?name="zhaoliu"&&password="12345",发现事务回滚了。