上文中说了在SpringBoot中如何开启事务,接下来就说下事务的一些属性
隔离性
事务的隔离性:指在一个事务执行期间,对数据库的其他事务进行访问的方式。
在实际开发与应用中存在多个事务同时运行的情况,多个事务同时执行期间对数据的读取可能会出现异常,数据的不一致性。为了解决这些异常因此需要对事务的隔离性进行设置。
事务的隔离性可以保证在一个事务执行期间,其他事务不会看到事务的未提交的更改,也不会影响其他事务的执行。
异常的种类
多个事务运行可能导致以下几种异常:
- 脏读(Dirty Read)
- 不可重复读(Non-repeatable Read)
- 幻读(Phantom Read)。
脏读
脏读是指在一个事务中读取了另一个事务的未提交的更改,然后在这个事务中对这些更改进行了修改,最后另一个事务提交了这些更改,导致数据的一致性被破坏。
不可重复读
不可重复读是指在一个事务中多次读取同一数据,但是读取到的数据不同,这是因为另一个事务在当前事务执行期间修改了这个数据。
假设有一个订单表,其中包含一个
customer_id
字段,用于存储订单的客户ID。在一个事务中,你读取了一个订单,并记录下了这个订单的customer_id
。然后,在另一个事务中,你修改了这个订单的customer_id
。最后,在第一个事务中,你再次读取这个订单,那么你可能会读取到修改后的customer_id
,这就是不可重复读。
幻读
幻读是指在一个事务中读取了多个数据,但是读取到的数据的数目不同,这是因为另一个事务在当前事务执行期间插入或删除了数据。
例如,如果在一个事务中读取了一个订单表中的所有订单,然后在另一个事务中插入了一个新的订单,最后当前事务再次读取这个订单表中的所有订单,那么就可能会出现幻读。这就是因为在当前事务执行期间,另一个事务插入了一个新的订单,导致当前事务读取到的数据的数目不同。
隔离级别
MySql中一共有4种级别,隔离级别从低到高分别是:
- 读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。
- 读提交:一个事务提交之后,它做的变更才会被其他事务看到。
- 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。
- 串行化:顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
为什么要设计这么多种呢?因为你隔离得越严实,效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。所以才会有不同的隔离级别供我们选择。
其中读提交解决了脏读,可重复读解决了“不可重复读”,串行化解决了“幻读”。当然串行化也是效率最低的。
SpringBoot中的隔离级别
在SpringBoot中,可以通过
TransactionDefinition.ISOLATION_DEFAULT
TransactionDefinition.ISOLATION_READ_UNCOMMITTED
TransactionDefinition.ISOLATION_READ_COMMITTED
TransactionDefinition.ISOLATION_REPEATABLE_READ
TransactionDefinition.ISOLATION_SERIALIZE
来设置事务的隔离性。
其中TransactionDefinition.ISOLATION_DEFAULT
是事务的默认隔离级别,它会根据数据库的设置来决定事务的隔离级别。在大多数情况下,这个级别就是你想要的级别。
第二种到第五种分别对应数据库中的四种隔离级别:读未提交、读已提交、可重复读和串行化。
MySQL 默认的隔离级别 REPEATABLE READ ,可重复读
- 设置事务隔离级别
@Transactional(isolation = TransactionDefinition.ISOLATION_DEFAULT)
public class UserService {
//.......
}
以上就是通过注解来实现事务,并设置事务隔离级别——默认。若是编程式事务,则
public void updateName(){
// 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
// ...
}
事务的传播性
在Spring中,事务的传播性是指当一个事务方法被另一个事务方法调用时,事务该以何种状态存在?是否需要在当前事务中嵌套另一个事务,还是会新开启自己的事务。
举个例子:
@Service
public class DemoService{
@Autowired
private UserService userSerice;
@Transactional
public void test(){
userSerice.updateName("test");
int i = 1/0;
}
}
// userSerice.updateName() 该方法也开启了事务
当发生异常时,userSerice.updateName()它是否也会回滚。这个就要看事务的传播性设置的何种状态,如果userSerice.updateName它的事务传播属性为是REQUIRED
,则它会回滚。若是REQUIRES_NEW
,则不会回滚。
如果是REQUIRED,事务的属性都是继承于大事务的
7种传播种类
Spring中一共定义了7种方式:
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) { this.value = value; }
public int value() { return this.value; }
}
它们的意思如下:
名称 | 含义 |
---|---|
REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务 |
SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 |
MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起 |
NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起 |
NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常 |
NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED |
开启事务后它的传播行为的默认值为:Propagation.REQUIRED
传播属性设置
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
@Transactional( propagation= Propagation.REQUIRES_NEW)
public void testAddEx(){
// .....
}
回滚规则
Spring中事务的回滚规则默认只有在抛出RuntimeException或Error时,才会发生事务回滚,在遇到检查型(Checked Exception)异常时不会回滚。
而我们可以通过设置回滚规则来自定义抛出哪些异常时进行回滚事务,包括checked异常。
@Transactional(rollbackFor = IOException.class)
public void handle() {
// .....
}
超时时间
超时时间是说一个事务允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。
@Transactional(timeout = 10)
public void handle() {
// .....
}
其中时间单位为秒。
只读属性
事务的是否只读(read-only)是指事务执行时是否可以修改数据库中的数据。如果一个事务被设置为只读,则事务执行时不能修改数据库中的数据,如果试图修改数据库中的数据,则事务会回滚并抛出异常。
@Transactional(readOnly = true)
public void handle() {
// .....
}