一.事务的概念及应用
1.1什么是事务
Spring Boot中的事务同样遵循ACID特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability):
- 原子性:事务中的所有操作作为一个整体执行,它们要么全部成功,要么全部失败回滚。
- 一致性:事务执行前后,数据库必须从一个一致性状态转变到另一个一致性状态。
- 隔离性:并发执行的事务之间应该相互隔离,避免数据冲突和不一致性。
- 持久性:一旦事务提交,其对数据库的修改就是永久性的,即使在系统故障后也能够恢复。
1.2为什么需要事务
假设你正在使用网上银行进行转账操作,你想从你的储蓄账户中转出1000元到你的信用卡账户以偿还欠款。这个转账操作实际上包含了两个步骤:1)从储蓄账户中扣除1000元;2)向信用卡账户中存入1000元。
如果银行系统没有使用事务来管理这两个步骤,那么可能会出现以下问题:
部分执行:如果第一个步骤(从储蓄账户中扣款)成功执行了,但是第二个步骤(向信用卡账户存款)因为某种原因(如系统故障)未能执行,那么你的储蓄账户就会减少1000元,而信用卡账户却没有增加相应的金额,导致数据不一致。
重复执行:如果两个步骤都成功执行了,但是因为系统错误,银行系统误以为转账没有成功,并尝试再次执行这两个步骤,那么你的储蓄账户就会被错误地扣除两次1000元,而信用卡账户也会增加两次1000元,这同样会导致数据不一致。
为了避免这些问题,银行系统会使用事务来管理这两个步骤。事务会确保这两个步骤要么都成功执行,要么都失败回滚,从而保持数据的一致性和完整性。如果第一个步骤成功执行了,但是第二个步骤失败了,那么事务会回滚第一个步骤,将储蓄账户中的1000元返还给你,确保你的账户余额不会受到错误操作的影响。
⽐如秒杀系统,第⼀步: 下单成功第⼆步: 扣减库存下单成功后, 库存也需要同步减少. 如果下单成功, 库存扣减失败, 那么就会造成下单超出的情况. 所以就需要把这两步操作放在同⼀个事务中. 要么⼀起成功, 要么⼀起失败
1.3 事务的操作
1.事务的开始
-
显式事务:在需要显式控制事务的情况下,可以通过特定的命令或API来开始一个事务。例如,在SQL中,可以使用
BEGIN TRANSACTION
或START TRANSACTION
命令来标记事务的开始。在Spring框架中,可以通过在方法上添加@Transactional
注解来声明一个事务的边界。 -
隐式事务和自动事务:在某些数据库或框架中,事务的开始可能是隐式的或自动的。例如,在某些数据库管理系统中,每条SQL语句都被视为一个自动提交的事务,除非显式地开始了一个更大的事务。在Spring框架中,也可以通过配置来启用声明式事务管理,使得事务的创建、提交和回滚都自动进行。
2.事务的执行
在事务开始之后,就可以执行一系列的数据库操作了。这些操作可以是查询、插入、更新或删除等。在事务的执行过程中,所有的操作都被视为一个整体,它们要么全部成功,要么全部失败。
3.事务的提交
如果事务中的所有操作都成功执行了,那么就需要提交事务。提交事务的操作会将事务中所有的更改永久地保存到数据库中。一旦事务被提交,就不能再对其进行回滚操作了。
在SQL中,可以使用COMMIT
命令来提交事务。在Spring框架中,当声明了@Transactional
注解的方法正常结束时(即没有抛出未检查异常),事务管理器会自动提交事务。
-- 开启事务
start transaction;
-- 提交事务
commit;
-- 回滚事务
rollback;
二.Spring 中事务的实现
-- 创建数据库
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` 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:
datasource:
url: jdbc:mysql://127.0.0.1:3306/trans_test?
characterEncoding=utf8&useSSL=false
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
configuration: # 配置打印 MyBatis⽇志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true #配置驼峰⾃动转换
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 org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserInfoMapper {
@Insert("insert into user_info(`user_name`,`password`)values(#{name},#
{password})")
Integer insert(String name,String password);
}
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface LogInfoMapper {
@Insert("insert into log_info(`user_name`,`op`)values(#{name},#{op})")
Integer insertLog(String name,String op);
}
@Slf4j
@Service
public class UserService {
@Autowired
private UserInfoMapper userInfoMapper;
public void registryUser(String name,String password){
//插⼊⽤⼾信息
userInfoMapper.insert(name,password);
}
}
@Slf4j
@Service
public class LogService {
@Autowired
private LogInfoMapper logInfoMapper;
public void insertLog(String name,String op){
//记录⽤⼾操作
logInfoMapper.insertLog(name,"⽤⼾注册");
}
}
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
@Autowired
private UserService userService;
@RequestMapping("/registry")
public String registry(String name,String password){
//⽤⼾注册
userService.registryUser(name,password);
return "注册成功";
}
}