Spring事务
spring事务
Spring 事务详解 | JavaGuide(Java面试+学习指南)
spring事务
事务:操作要么都成功,要么都失败。 具有以下四个特性:
- 原子性: 操作不可分割
- 一致性: 事务开始和结束对数据库完整性没有破坏(什么意思)
- 隔离性:多个事互不干扰
- 持久性:事务结束后,对数据的修改时永久的, 不会因为系统故障丢失
事务的应用很常见, 如银行卡转账, 你的账户少了1000块,别人的账户多了1000块, 这俩的处理应该要么都成功,要么都失败。 要是因为网络原因,你少了1000, 别人的账户没收到,会造成财产损失。
为了测试用法,采用springboot + mybatisplus + postgreSQL操作数据库, 并利用@Transactional注解测试。项目结构如下:其中sql脚本用于生成数据表

数据库准备
maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wang</groupId>
<artifactId>Study</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>Study</name>
<description>Study</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- mybatis plus 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.4.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
sql脚本
脚本中创建的表的主键id会自增,创建完毕利用navicat可以看到绑定的序列:
nextval('person_id_seq'::regclass)
CREATE TABLE IF NOT EXISTS person
(
id SERIAL PRIMARY KEY,
name character(100),
money integer
)
WITH (
OIDS=FALSE
);
ALTER TABLE person
OWNER TO postgres;
COMMENT ON TABLE person
IS '这是个人信息表';
spring配置
数据库连接+ sql脚本执行
spring:
datasource:
url: jdbc:postgresql://localhost:5432/whdb?stringtype=unspecified
username: postgres
password: 179530
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 200
minimum-idle: 20
max-lifetime: 20000
connection-test-query: select 1
leak-detection-threshold: 30000
allow-pool-suspension: true
sql:
init:
mode: always
schema-locations: classpath:schema-all.sql
事务测试
springboot集成mybatis-plus
POJO
@Data
@TableName("person")
public class Person {
@TableId(value = "id",type = IdType.AUTO)
private Integer id;
private String name;
private Integer money;
}
Mapper: @Mapper注解可以省略,在启动类用@MapperScan("wang.transaction.mapper")
@Mapper
public interface PersonMapper extends BaseMapper<Person> {
}
Service接口
public interface PersonService extends IService<Person> {
}
Service实现类
@Service
public class PersonServiceImpl extends ServiceImpl<PersonMapper,Person> implements PersonService{
}
(这里闹出一个低级错误,在PersonServiceImpl中利用PersonService接口的方法操作数据库,这样会有循环依赖问题)
测试
在App的启动类中执行一次init方法存入两条记录到表person中
@Service
public class DataBaseServiceTest {
@Autowired
private PersonService personService;
@PostConstruct
public void init() {
int money = 1000;
Person p1 = personService.getById(1);
p1.setMoney(money);
Person p2 = personService.getById(2);
p2.setMoney(money);
personService.updateBatchById(Arrays.asList(p1,p2));
System.out.println("save successfully");
}
@Transactional
public void run() {
int money = 200;
Person p1 = personService.getById(1);
p1.setMoney(p1.getMoney() + money);
personService.updateById(p1);
// int a = 1 / 0;
Person p2 = personService.getById(2);
p2.setMoney(p2.getMoney() - money);
personService.updateById(p2);
}
}
注释第28行发现执行符合预期,取消注释执行后发现二者都没有被修改
事务失效的几种场景
聊聊spring事务失效的12种场景,太坑了 - 腾讯云开发者社区-腾讯云 (tencent.com)
Transactional修改的方法不是public
将上面run方法的权限修改为private, 结果发现p1更新了,p2没更新,事务失效
Transactional修改的是final方法
@Transactional
public final void run() {
...
}
异常被catch捕获
(其实可以指定异常的类型)
@Transactional
public void run() {
int money = 200;
Person p1 = personService.getById(1);
p1.setMoney(p1.getMoney() + money);
personService.updateById(p1);
try {
int a = 1 / 0;
} catch (Exception e) {
throw new RuntimeException(e);
}
Person p2 = personService.getById(2);
p2.setMoney(p2.getMoney() - money);
personService.updateById(p2);
}
发现p1更新了,p2没更新 , 事务失效
内部调用
内部某个方法A调用一个被Transactional注解修饰的方法B,则B不会生成事务,其中A有没有被@Transactional修饰都是如此。
@Transactional
public void run() {
int money = 200;
Person p1 = personService.getById(1);
p1.setMoney(p1.getMoney() + money);
personService.updateById(p1);
int a = 1 / 0;
Person p2 = personService.getById(2);
p2.setMoney(p2.getMoney() - money);
personService.updateById(p2);
}
@Transactional
public void run2() {
run();
}
如果真的想在类中调用自己的事务方法怎么办?将这段事务方法剥离出去,再注入bean调用:
剥离的事务代码
@Component
public class Test {
@Autowired
private DataBaseServiceTest dataBaseServiceTest;
@Bean
public void start(){
dataBaseServiceTest.run();
}
}
未被spring管理
这个没啥好说的,但又容易被忽略
多线程调用
Bean1中事务A (run),开启线程调用Bean2中事务B (run2),分为两种情况:(1)调用A之前发生了异常 -- > 回滚 (2)事务B发生了异-->不回滚
@Transactional
public void run() {
int money = 200;
Person p1 = personService.getById(1);
p1.setMoney(p1.getMoney() + money);
personService.updateById(p1);
int a = 1 / 0;
new Thread(() -> {
int b = 1 / 0;
dataBaseServiceTest2.run2(money);
}).start();
}
@Service
public class DataBaseServiceTest2 {
@Autowired
private PersonService personService;
@Transactional
public void run2(int money) {
Person p2 = personService.getById(2);
p2.setMoney(p2.getMoney() - money);
personService.updateById(p2);
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构