1. 事务
事务(Transaction)是数据库管理系统中的一个重要概念,它是一个原子性的数据库操作序列。原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)是事务必须具备的四个核心特性,通常简称为ACID特性。
-
原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部失败并回滚到操作之前的状态。这意味着,事务内的操作是不可分割的。
-
一致性(Consistency):事务必须确保数据库从一个一致性状态转换到另一个一致性状态。一致性状态意味着数据库中的数据应满足完整性约束和业务规则。
-
隔离性(Isolation):当多个事务并发执行时,它们之间的操作应该相互隔离。事务的隔离级别决定了事务操作的可见性和其他事务的干扰程度。
-
持久性(Durability):一旦事务被提交(Commit),它对数据库所做的修改应该是永久性的。即使系统崩溃,事务的结果也应该被保存。
在Spring框架中,事务管理是一个核心功能,它允许开发者以声明式或编程式的方式来处理事务,从而确保数据库操作的ACID特性。Spring的事务管理机制是基于代理模式和AOP(面向切面编程)的,这使得事务管理更加灵活和透明。
1.1 Spring事务管理的关键概念
-
PlatformTransactionManager:这是Spring事务管理的核心接口,负责抽象出不同事务管理器的共同操作。根据底层数据访问技术的不同,Spring提供了不同的实现,如
DataSourceTransactionManager
、HibernateTransactionManager
和JpaTransactionManager
。 -
TransactionDefinition:这个接口定义了事务的属性,包括传播行为、隔离级别、超时时间等。
-
TransactionStatus:这是一个对象,代表了当前事务的状态,它可以被用来查询事务的当前状态,或者控制事务的执行(如设置回滚标志)。
-
TransactionCallback 和 TransactionTemplate:这是编程式事务管理的关键类。
TransactionTemplate
是一个模板类,它提供了执行事务的方法,而TransactionCallback
是一个回调接口,用于执行实际的业务逻辑。 -
@Transactional 注解:这是声明式事务管理的核心,可以应用于类或方法上。它定义了事务的边界和属性,Spring容器在运行时会根据这些注解来自动管理事务。
1.2 Spring事务管理的工作流
-
获取事务:Spring通过
PlatformTransactionManager
获取当前线程的事务。 -
事务传播:根据
TransactionDefinition
的传播行为,Spring决定是创建一个新的事务,还是加入现有的事务。 -
执行业务逻辑:在事务的保护下执行业务逻辑。
-
事务提交或回滚:如果业务逻辑执行成功,则提交事务;如果发生异常,则根据配置决定是否回滚事务。
-
释放资源:无论事务是否成功,Spring都会释放事务资源。
2. 实例
JDK | 8 |
Maven | 3.6 |
SpringBoot | 2.7.12 |
2. 1 环境搭建
2.1.1 添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
</dependency>
</dependencies>
注:更换依赖版本需要注意依赖冲突问题
2.1.2 MySQL库表创建
user表
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`iphone` varchar(128) DEFAULT NULL,
`money` decimal(6,2) DEFAULT NULL,
`address` varchar(128) DEFAULT NULL,
`password` varchar(32) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
order表
CREATE TABLE `order` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int DEFAULT NULL,
`name` varchar(128) DEFAULT NULL,
`number` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
注:库表结构不一定非要按照这样,符合逻辑即可。
2.1.3 application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://116.198.242.56:13306/transaction?useSSL=false&serverTimezone=UTC
username: root
password: 123456
hikari:
minimum-idle: 5
maximum-pool-size: 20
auto-commit: true
idle-timeout: 30000
pool-name: DatebaseHikariPool
max-lifetime: 1800000
connection-timeout: 30000
type: com.zaxxer.hikari.HikariDataSource
server:
port: 8081
mybatis:
mapper-locations: classpath:/mapper/*.xml
2.2 工程结构
project-root/
|-- main/
| |-- java/
| | |-- com/
| | | | -- xiaokai/
| | | |-- TransactionApplication.java
| | | |-- service/
| | | | |-- impl/
| | | | | |-- IOrderServiceImpl.java
| | | | | |-- IUserServiceImpl.java
| | | | | `-- (其他 service impl 类)
| | | | `-- IOrderService.java
| | | | `-- IUserService.java
| | | `-- tran/
| | | |-- AnnotationService.java
| | | |-- config/
| | | |-- TransactionConfig.java
| | | |-- entity
| | |-- OrderEntity.java
| | `-- UserEntity.java
| | | |-- repository
| | |-- OrderMapper.java
| | `-- UserMapper.java
| |-- resources/
| | |-- mapper/
| | | |-- order_mapper.xml
| | | `-- user_mapper.xml
| | `-- application.yml
|-- test/
| |-- java/
| | |-- com/
| | `-- TransactionTest.java
注:工程结构只是提供一个版本,可以根据所需更换类名或者所在包。
2.3 编程式事务
2.3.1 config
package com.xiaokai.tran.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.support.TransactionTemplate;
import javax.sql.DataSource;
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
@Bean
public TransactionTemplate transactionTemplate(PlatformTransactionManager transactionManager) {
return new TransactionTemplate(transactionManager);
}
}
注:有的版本不需要这个配置bean类。
2.3.2 实体类
/**
* Author:yang
* Date:2024-09-26 10:52
* Description:订单实体类
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OrderEntity {
private Integer userId;
private String name;
private Integer number;
}
/**
* Author:yang
* Date:2024-09-26 10:55
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserEntity {
private int id;
private String name;
private String password;
private String iphone;
private String address;
private int money;
}
2.3.3 ORM交互接口
/**
* Author:yang
* Date:2024-09-26 11:00
*/
@Mapper
public interface OrderMapper {
int insertOrder(OrderEntity order);
}
/**
* Author:yang
* Date:2024-09-26 10:46
*/
@Mapper
public interface UserMapper {
Integer insertUser(UserEntity userEntity);
}
2.3.4 mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xiaokai.tran.repository.UserMapper">
<insert id="insertUser" parameterType="com.xiaokai.tran.entity.UserEntity" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, iphone, money, address, password)
VALUES (#{name}, #{iphone}, #{money}, #{address}, #{password})
</insert>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.xiaokai.tran.repository.OrderMapper">
<insert id="insertOrder" parameterType="com.xiaokai.tran.entity.OrderEntity" useGeneratedKeys="true">
insert into `order` (`user_id`, `name`, `number`)
values (#{userId},
#{name},
#{number})
</insert>
</mapper>
2.3.5 启动类
package com.xiaokai;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* Author:yang
* Date:2024-09-25 9:55
*/
@SpringBootApplication
@EnableTransactionManagement
public class TransactionApplication {
public static void main(String[] args) {
SpringApplication.run(TransactionApplication.class, args);
}
}
2.3.6 测试
package com;
import com.xiaokai.TransactionApplication;
import com.xiaokai.service.IOrderService;
import com.xiaokai.service.IUserService;
import com.xiaokai.service.impl.IOrderServiceImpl;
import com.xiaokai.tran.entity.OrderEntity;
import com.xiaokai.tran.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.support.TransactionTemplate;
/**
* Author:yang
* Date:2024-09-26 11:09
*/
@SpringBootTest(classes = {TransactionApplication.class})
@Slf4j
@RunWith(SpringRunner.class)
public class TransactionTest {
@Autowired
private IOrderService orderService;
@Autowired
private IUserService userService;
// 注入编程式事务管理bean
@Autowired
private TransactionTemplate transactionTemplate;
@Test
public void test_program() {
log.info("test_program_transaction");
transactionTemplate.execute(status -> {
try {
// 添加用户
UserEntity user = new UserEntity().builder()
.name("xiaokai")
.iphone("13812345678")
.money(1000)
.address("beijing")
.build();
Integer countUser = userService.createUser(user);
if (countUser != 1) {
log.error("createUser error count: {}", countUser);
}else {
log.info("createUser count: {},user: {}", countUser, user.toString());
}
// 创建异常,事务回滚
int i = 1 / 0;
OrderEntity order = new OrderEntity().builder()
.userId(user.getId())
.name("xiaokai")
.number(10)
.build();
Integer countOrder = orderService.createOrder(order);
if (countOrder != 1) {
log.error("createOrder count: {}", countOrder);
}else {
log.info("createOrder count: {},order: {}", countOrder, order.toString());
}
} catch (Exception e) {
status.setRollbackOnly();
log.error("transaction rollback", e);
}
return 1;
});
log.info("test_program_transaction end");
}
}
结果1:
2024-09-26 15:18:20.122 INFO 26420 --- [ main] com.TransactionTest : test_program_transaction
2024-09-26 15:18:20.124 INFO 26420 --- [ main] com.zaxxer.hikari.HikariDataSource : DatebaseHikariPool - Starting...
2024-09-26 15:18:20.493 INFO 26420 --- [ main] com.zaxxer.hikari.HikariDataSource : DatebaseHikariPool - Start completed.
2024-09-26 15:18:20.635 INFO 26420 --- [ main] com.TransactionTest : createUser count: 1,user: UserEntity(id=6, name=xiaokai, password=null, iphone=13812345678, address=beijing, money=1000)
2024-09-26 15:18:20.637 ERROR 26420 --- [ main] com.TransactionTest : transaction rollback
java.lang.ArithmeticException: / by zero
由于有异常终止了终止了后部分代码,在捕获异常后回滚事务。根据日志,user表中已经插入了一条数据id=6,查看表数据:
没有找到该数据是由于事务回滚了。
结果2:注释 int i = 1 / 0;
test_program_transaction
DatebaseHikariPool - Starting...
DatebaseHikariPool - Start completed.
createUser count: 1,user: UserEntity(id=7, name=xiaokai, password=null, iphone=13812345678, address=beijing, money=1000)
createOrder count: 1,order: OrderEntity(userId=7, name=xiaokai, number=10)
test_program_transaction end
没有异常抛出,两条数据均插入成功,事务commit。
2.4 声明式事务
2.4.1 添加注解
在启动类上添加 @EnableTransactionManagement
注解以及在所需要执行事务的方法或者类上添加 @Transactional
注解
2.4.2 添加事务服务
package com.xiaokai.tran;
import com.xiaokai.service.IOrderService;
import com.xiaokai.service.IUserService;
import com.xiaokai.tran.entity.OrderEntity;
import com.xiaokai.tran.entity.UserEntity;
import com.xiaokai.tran.repository.UserMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* Author:yang
* Date:2024-09-25 14:40
*/
@Service
@Transactional
@Slf4j
public class AnnotationService {
@Autowired
private IUserService userService;
@Autowired
private IOrderService orderService;
public void statementTransaction() {
// 添加用户
UserEntity user = new UserEntity().builder()
.name("xiaokai")
.iphone("13812345678")
.money(1000)
.address("beijing")
.build();
Integer countUser = userService.createUser(user);
if (countUser != 1) {
log.error("createUser error count: {}", countUser);
} else {
log.info("createUser count: {},user: {}", countUser, user.toString());
}
// 创建异常
//int i = 1 / 0;
OrderEntity order = new OrderEntity().builder()
.userId(user.getId())
.name("xiaokai")
.number(10)
.build();
Integer countOrder = orderService.createOrder(order);
if (countOrder != 1) {
log.error("createOrder count: {}", countOrder);
} else {
log.info("createOrder count: {},order: {}", countOrder, order.toString());
}
}
}
2.4.3 测试
@Test
public void test_statement() {
log.info("test_statement_transaction");
annotationService.statementTransaction();
log.info("test_statement_transaction end");
}
测试结果可以参考编程式事务结果。
注:在声明式事务管理中,@Transactional
注解可以配置多种事务属性,如propagation
(传播行为)、isolation
(隔离级别)、timeout
(超时时间)、readOnly
(只读标志)等,以满足不同的业务需求。
3. 传播机制
参考另一篇文章:Spring事务传播机制
4. 事务失效
参考另一篇文章:Spring事务常见失效场景