目录
数据库事务?
数据库事务是一组数据库操作的集合,这些操作要么全部成功(提交),要么全部失败(回滚)。数据库事务遵循 ACID 原则(原子性、一致性、隔离性和持久性),以确保数据的完整性和可靠性。
示例:
BEGIN TRANSACTION;
-- 更新客户的地址
UPDATE customers SET address = '新地址' WHERE customer_id = 1;
-- 其它一坨DML操作
-- 检查是否有错误
IF @@ERROR <> 0
BEGIN
ROLLBACK; -- 如果更新失败,回滚事务
PRINT '更新失败,事务已回滚';
END
ELSE
BEGIN
COMMIT; -- 如果更新成功,提交事务
PRINT '更新成功,事务已提交';
END
ACID?
特性 | 定义 | 关注点 | 示例 |
---|---|---|---|
原子性 | 事务中的所有操作要么全部成功,要么全部失败。 | 事务的 完整性 | 银行转账操作中的扣款和存款要么都成功,要么都失败。 |
一致性 | 事务执行前后,数据库必须保持一致的状态。 | 数据库的 完整性约束 | 账户的总金额在转账前后必须相等。 |
隔离性 | 事务之间应相互独立,避免干扰。 | 并发事务的独立性 | 多个用户同时转账,结果不会相互影响。 |
持久性 | 一旦事务提交,结果将永久保存。 | 数据的可靠性 | 转账成功后,账户余额的改变不会因系统崩溃而丢失 |
Java 事务(@Transactional)?
@Transactional就是事务的具体应用,在代码中打上该标签,表示开启一个事务,若事务中有异常(RuntimeException)发生,则回滚所有对数据库的操作;
数据库中的的事务和java中事务的区别?
- 相似之处:核心目的都是为了确保数据的一致性和完整性。隔离级别的概念在Java中的与数据库都是一样的;
- 不同之处:事务的范围是以方法为单位,而数据库中是以一个或多个数据库操作,可能会有多表;数据库中没有指定传播行为,可通过代码的编写来决定的传播行为;而java中就有具体的传播行为可以指定。
必须明确一点:抛出的异常被打上@Transactional的方法接收到才会进行回滚操作。
以下是@Transactional的具体使用方法:
@Transactional(rollbackFor = Exception.class)
//若这个异常抛出,取消该删除操作
public void delete(Integer id) throws Exception {
//根据部门id删除部门信息
//模拟:异常
if(true){
throw new Exception("出现异常了~~~");
}
//删除部门下的所有员工信息
}
rollBackFor属性
rollBackFor指定事务回滚的异常条件,比如指定IOException时,事务报该IOException异常时才回滚;
@Transactional不同的含义
方法
- 当前方法交给spring进行事务管理
类
- 当前类中所有的方法都交由spring进行事务管理
接口
- 接口下所有的实现类当中所有的方法都交给spring 进行事务管理
一个问题
A、B方法都用@Transactional标签表示开启一个事务,A方法调用B时,是开启两个事务还是共用一个事务呢?
默认下传播行为是REQUIRED;
A、B方法共用一个事务。若A调用B,B中抛出异常,则A与B的所有数据库操作全部回滚。
示例:
@Transactional
public void outerMethod()throws {
//增删改查
innerMethod();
//增删改查
}
@Transactional
public void innerMethod() {
//插插×
//ssss
//删删删
//抛异常
int a=1/0;
}
可以通过指定事务的传播行为的,决定开启一个事务还是开启两个事务,或者说直接挂起事务。
事务的传播行为定义了A方法在执行时如何与B方法的事务进行交互。
简单来说,它决定了一个方法在调用另一个方法时,该如何处理事务。
propagation属性
以下是@Transactional的属性propagation的属性值,指定属性值可以确定具体的传播行为:
属性值 | 含义 |
---|---|
REQUIRED | A、B都打上注解@Trans,A、B方法共用一个事务。 |
REQUIRES_NEW | A、B都打上注解@Trans,A、B方法各开一个事务。 需要新事务; |
SUPPORTS | 支持事务,有则加入,无则在无事务状态中运行 |
NOT_SUPPORTED | 不支持事务,在无事务状态下运行,如果当前存在已有事务,则 挂起 当前事务 下面有具体代码解释; |
MANDATORY | 必须有事务,否则抛异常 |
NEVER | 必须没事务,否则抛异常 |
传播行为NOT_SUPPORTED
若要简单的理解NOT_SUPPORTED,即在方法中指定该行为后,方法为无事务状态,该方法中的数据库操作永远不会回滚(写sql写错了还是会回滚的,数据库自带的回滚)。
但既然是为无事务状态,为什么不直接把@Transational直接给删掉呢?
提示:回滚的前提一定是开启事务的方法捕获到了异常;
例子如下:
若在innerMethod中不加@Transational注解:
/**
*innerMethod中的异常只要抛出并被outerMethod接收到,那么innerMethod中的数据库操作就会回滚
*就假如在innerMethod中直接使用try..catch..捕获异常,结束掉异常处理的话,那么innerMethod中的
*数据库操作就不会回滚了;
*/
@Transactional
public void outerMethod()throws Exception {
//删除
innerMethod();
//插入
}
public void innerMethod() throws Exception{
//插插×
throws new Exception();
//ssss
//删删删
}
未捕获的异常:如果 innerMethod
中抛出的异常没有被捕获,并且被 outerMethod
接收到(即 outerMethod
调用 innerMethod
),那么 innerMethod
中的数据库操作会回滚。这是因为 Spring 的事务管理器会检测到未处理的异常,并决定回滚事务。
捕获异常:如果在 innerMethod
中使用 try..catch
捕获了异常,并在 catch
块中处理了这个异常(例如,记录日志或打印消息),那么这个异常不会传播到 outerMethod
。因此,Spring 将不会回滚 innerMethod
中的数据库操作,因为它认为该方法已成功完成(尽管在逻辑上可能并不是这样)。
innerMethod中加入事务注解并指定传播行为属性值为NOT_SUPPORTED;
@Transactional
public void outerMethod() throws Exception {
//删除
innerMethod();
//在这抛异常了;outerMethod中数据库操作回滚,innerMethod中的数据库操作不受影响。
//插入
}
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void innerMethod() throws Exception{
//插插×
//又抛异常了;outerMethod事务挂起状态,innerMethod在无事务状态下执行,上面的“插插×”
//操作不会回滚;
throw new Exception();
//ssss
//删删删
}
事务的挂起
代码中提到了挂起,什么叫挂起?
挂起指的是将当前的状态保存下来,然后将其从执行上下文中移除。在事务管理中,这意味着将当前事务的所有操作和状态记录下来,但不再对其进行处理。
用人话打个比方就是说:你看电影的时候按下了暂停键。此时暂停看电影这一行为的操作就叫挂起;奖励完自己后,再按下播放键,继续看电影。
outerMethod在调用
innerMethod
时,由于innerMethod
指定了传播行为NOT_SUPPORTED,事务被挂起,相当于给事务按下了暂停键,但事务中还记录着outerMethod对数据库的操作,暂时保存。这意味着在outerMethod
中的所有数据库操作不会被提交,直到事务恢复(按下播放键),即innerMethod
在无事务的状态下运行完成。
挂起与代码的暂停有什么不同?
暂停指的是在程序执行过程中暂时停止执行某段代码,等待某个操作完成或条件满足。暂停并不意味着状态被保存,通常是在等待某个外部事件。
调用方法(outerMethod)的执行被暂停,直到被调用的方法执行(innerMethod)完毕并返回。(堆栈中的调用方法入栈,结束方法出栈)
public void outerMethod() {
//A代码行
//B代码行
//在调用这个方法时,整个代码像是被暂停了一样,只有执行完innerMethod中的所有代码才会继续运行;
innerMethod();
//C代码行
}
public void innerMethod() {
//A代码行
//B代码行
//C代码行
}
其实也能用一句话讲明白:
事务的挂起是对事务的暂停,就好像事务不存在了一样,而代码的暂停是指在程序执行过程中临时中断执行,等待某个条件满足或事件发生,之后继续执行。