当构建复杂的企业级应用程序时,数据一致性和可靠性是至关重要的。Spring 框架提供了强大而灵活的事务管理机制,成为开发者处理事务的首选工具。本文将深入探讨 Spring 事务的使用和原理,为大家提供全面的了解和实际应用的指导。
本文概览
- 首先,我们将从事务的基础出发,介绍其概念、生命周期、隔离级别、传播行为。
- 其次,我们再介绍在 Spring 中,如何应用声明式和编程式两种事务管理方式。
- 最后,我们将深入研究 Spring 事务的原理,了解其核心组件和关键类,解析其工作原理,探索它是如何做到将事务的控制与业务逻辑进行解耦的。
事务基础
事务简介
在数据库和软件开发领域,事务是一组相关的操作
,被视为不可分割的执行单位。事务具有四个关键数据,简称 ACID
属性:
- 原子性(Atomicity):事务是原子的,它要么全部执行成功,要么完全不执行。如果事务的任何部分失败,整个事务将回滚到初始状态,不会留下部分完成的结果。
- 一致性(Consistency):事务在执行前后,数据库的状态应保持一致。这意味着事务的执行不会破坏数据库的完整性约束,如唯一性约束、外键约束等。
- 隔离性(Isolation):多个事务并发执行时,每个事务都应该被隔离,以防止彼此之间的干扰。数据库系统通过事务隔离级别来定义事务之间的隔离程度。
- 持久性(Durability):一旦事务成功完成,其结果应该是持久的,即使在系统故障或重启后也应该保持。数据库系统通常通过将事务的结果写入日志文件来实现持久性。
事务的生命周期通常包括一下阶段:
- 开始:事务开始时,系统记录数据库的初始状态。
- 执行:事务执行相关的数据库操作,可能包括插入、更新、删除等。
- 提交:如果事务成功执行,将对数据库的更改提交,使其成为永久性的。
- 回滚:如果在事务执行期间发生错误或者事务被显示混滚,系统将撤销事务中的所有更改,回复数据库到事务开始时的状态。
下面我们通过一些例子来深入理解下事务的生命周期过程:
案例一:开启事务并插入一条数据,执行成功并提交事务
-- 开始事务
BEGIN;
-- 执行数据库操作,向 `user` 表中插入一条数据
INSERT INTO `user` (name,age,address) VALUE ("帅气的小张",25,"山东菏泽");
-- 提交事务
COMMIT;
案例二:开启事务插入两条数据,其中第二条数据执行异常,事务发生回滚,那么第一条数据并没有生效
-- 开始事务
BEGIN;
-- 执行操作,向 `user` 表中插入一条数据
INSERT INTO `user` (name,age,address) VALUE ("帅气的小张",25,"山东菏泽");
-- 执行一条异常操作 address 字段拼错
INSERT INTO `user` (name,age,adress) VALUE ("帅气的小张",25,"山东菏泽");
-- 回滚事务
ROLLBACK;
可以看到,在 MySQL 里,执行事务的操作包括 BEGIN(开启)、COMMIT(提交)、ROLLBACK(回滚)
案例三:使用 Spring 框架时,进行声明式事务管理
package com.markus.spring.transaction.service;
import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.data.jdbc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
/**
* @Author: zhangchenglong06
* @Date: 2024/2/2
* @Description:
*/
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Transactional
public void processUser() {
User user = new User();
user.setName("markus zhang unique time :" + System.currentTimeMillis());
user.setAge(25);
user.setAddress("山东菏泽");
// 1. 先向数据库中插入一条数据
userDao.insertUser(user);
// 故意抛出一个异常,验证下 第一步 的操作是否会回滚
int i = 1 / 0;
// 2. 再查询该数据
User queryUserByName = userDao.queryUserByName(user.getName());
if (Objects.isNull(queryUserByName)) {
return;
}
// 3. 再更新该数据到数据库中
queryUserByName.setAddress("北京朝阳");
userDao.updateUser(queryUserByName);
}
}
案例四:使用 Spring 框架时,进行编程式事务管理
package com.markus.spring.transaction.service;
import com.markus.spring.data.jdbc.domain.entity.User;
import com.markus.spring.data.jdbc.repository.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import java.util.List;
import java.util.Objects;
/**
* @Author: zhangchenglong06
* @Date: 2024/2/2
* @Description:
*/
@Service
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private TransactionTemplate transactionTemplate;
public void processUserByProgram() {
User user = new User();
user.setName("markus zhang unique time :" + System.currentTimeMillis());
user.setAge(25);
user.setAddress("山东菏泽");
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
processUser();
} catch (Exception e) {
// 如果发生异常,回滚事务
status.setRollbackOnly();
throw e;
}
}
});
}
public void processUser() {
User user = new User();
user.setName("markus zhang unique time :" + System.currentTimeMillis());
user.setAge(25);
user.setAddress("山东菏泽");
// 1. 先向数据库中插入一条数据
userDao.insertUser(user);
// 故意抛出一个异常,验证下 第一步 的操作是否会回滚
int i = 1 / 0;
// 2. 再查询该数据
User queryUserByName = userDao.queryUserByName(user.getName());
if (Objects.isNull(queryUserByName)) {
return;
}
// 3. 再更新该数据到数据库中
queryUserByName.setAddress("北京朝阳");
userDao.updateUser(queryUserByName);
}
public List<User> queryAllUsers() {
List<User> users = userDao.queryUsers(-1);
return users;
}
}