声明式事务概念
声明式事务是指使用注解或 XML 配置的方式来控制事务的提交和回滚。
开发者只需要添加配置即可, 具体事务的实现由第三方框架实现,避免我们直接进行事务操作!
使用声明式事务可以将事务的控制和业务逻辑分离开来,提高代码的可读性和可维护性。
区别:
-
编程式事务需要手动编写代码来管理事务
-
而声明式事务可以通过配置文件或注解来控制事务。
spring声明式事务实现原理
一、事务的定义
事务是数据库操作的最小工作单元,作为单个逻辑工作单元执行的一系列操作。这些操作必须作为一个整体来执行,即要么全部成功,要么全部失败。事务的这一特性保证了数据的一致性和可靠性,特别是在多用户共享资源的环境下。
在没有spring框架的情况下,需要我们进行编程式事务,即自己手动控制事务的回滚和提交
但是操作繁杂,所以我们使用了spring框架的声明式事务自动控制事务的各种流程,那么如何添加声明式事务呢?
二.添加声明式事务
对于需要添加事务的方法,添加一个这样的注解,就可以添加声明式事务了
不过要注意,我们在配置类中,需要添加一个如下图红箭头所示的注解,去开启事务注解的支持
并且我们需要在ioc容器中给对应的数据库连接池添加这样的管理器
事务属性:
只读模式
代码如下,当一个数据库操作只有查询的时候,可以设置事务为只读模式,这样会加快查询的效率
java
package org.atguigu.Service;
import org.atguigu.dao.StudentDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional
public class StudentService {
@Autowired
private StudentDao studentDao;
@Transactional(readOnly = true)
public void changeInfo(){
studentDao.updateAgeById(100,1);
int i=1/0;
System.out.println("-----------");
studentDao.updateNameById("test1",1);
}
}
超时时间
我们假设有如下代码,我们设置超时时限为3,然后我们使得这个线程休眠4000,于是当我们执行后就会发生以下错误
java
@Transactional(readOnly = true,timeout = 3)
public void changeInfo(){
studentDao.updateAgeById(100,1);
int i=1/0;
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("-----------");
studentDao.updateNameById("test1",1);
}
那么我们思考一个问题,假设我们在类上施加一个超时时限的注解,其中的方法会如何呢
观察如下代码,我们在类上设置了一个timeout = 3类上有一个注解用于设置只读模式
那么执行结果会是什么样呢
java
package org.atguigu.Service;
import org.atguigu.dao.StudentDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
@Transactional(timeout = 3)
public class StudentService {
@Autowired
private StudentDao studentDao;
@Transactional(readOnly = false)
public void changeInfo(){
studentDao.updateAgeById(100,1);
// int i=1/0;
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("-----------");
studentDao.updateNameById("test1",1);
}
}
执行结果如下,我们可以发现,没有任何报错,这是因为方法上的注解将类上的注解覆盖掉了
事务异常
正常情况下,假设你不做任何设置,其只会在运行时异常发生时才会发生事务回滚
那么假设我们想要规定一些默认情况下不会回滚的异常也要进行回滚,那么我们该如何做呢
首先我们观察一下异常类的各种关系,当默认情况下发生RentimeException(运行时异常)
就会回滚,而IOException就不会发生回滚
如图,我们设置了一个rollbackfor = Exception.class 这个注解的属性,这个属性规定了事务发生什么类型的错误应该回滚
而我们还设置了一个noRollbackFor = RuntimeException.class 这个注解属性,这个属性的意思是,在回滚范围内,什么异常类型不回滚
事务隔离
- 事务隔离级别
数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括:
-
读未提交(Read Uncommitted):事务可以读取未被提交的数据,容易产生脏读、不可重复读和幻读 等问题。实现简单但不太安全,一般不用。
-
读已提交(Read Committed):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
-
可重复读(Repeatable Read):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
-
串行化(Serializable):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
不同的隔离级别适用于不同的场景,需要根据实际业务需求进行选择和调整。
脏读(Dirty Read):一个事务可以读取另一个事务尚未提交的数据。如果该数据后来被回滚,那么第一个事务读取到的数据就是"脏"的。脏读通常发生在读未提交隔离级别下。
不可重复读(Non-repeatable Read):在一个事务中,两次读取同一数据时,数据内容不同。这是因为在两次读取之间,另一个事务修改并提交了该数据。不可重复读通常发生在读已提交隔离级别下。
幻读(Phantom Read):一个事务在读取某范围的数据时,另一个事务在该范围内插入了新数据,导致前一个事务再次读取时,发现多了"幻影"行。幻读通常发生在可重复读隔离级别下。
在代码中,我们可以这样设置事务的隔离性
事务传播
事务传播的定义
指的是事务之间的调用,叫做事务传播,比如下列图片中的业务方法1调用业务方法2,这叫做事务的传播,我们一般将事务的传播属性设置到子事务上
事务的传播属性有
1.假如父事务
2.不加入父事务
注解方式规定事务传播
通过在@Transactional注解里面,添加一个propagation注解设置事务的传播行为,
那么设置事务的传播行为有什么用呢?举个例子,在topService()事务里面,调用别的事务,然后我们在changeName()里面故意放了一个报错,
那么当topService调用两个事务时,是加入式的,他们合并为一个事务
当其中某个事务出现了报错的时候,整个事务就会回滚,看作一个事务,其他的事务的行为也不会修改
当事务加入的方式是独立的时候,那么当某个事务报错的时候,就不会影响别的事务的操作