在计算机科学中,事务是一组相关的操作,被视为一个单独的不可分割的逻辑单元,或者说是一个原子操作。事务通常用于确保数据库操作的一致性和完整性,以及保证数据的可靠性。
事务的四个基本属性(ACID)包括:
通过使用事务,我们可以确保在一组操作中的任何操作失败时,所有的更改都能够回滚,从而保持数据的一致性和完整性。在数据库中,事务由一组SQL语句组成,这些SQL语句可以是插入、更新或删除操作。事务可以通过编程接口或SQL语句来控制和管理,以确保数据的完整性和一致性。
Spring框架提供了对事务管理的支持,以便于开发人员可以轻松地实现数据库事务控制。Spring事务管理通过与底层事务管理器交互来管理事务,支持多种事务管理机制,包括JDBC事务、Hibernate事务和JTA(Java Transaction API)事务。
Spring事务管理的核心接口是PlatformTransactionManager
,它定义了事务管理器的通用行为。Spring框架提供了多种实现PlatformTransactionManager
接口的类,如DataSourceTransactionManager
、HibernateTransactionManager
和JtaTransactionManager
等,可以根据需要选择适合的实现。
继承关系如下图1.2-1所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jRP0ekss-1681004468456)(/Users/gaogzhen/Documents/20230408-spring6-tx-DataSourceTransactionManager.png)]
在Spring中,我们可以使用@Transactional
注解来将一个方法标记为事务性方法。在使用@Transactional
注解时,可以指定事务的传播行为、隔离级别、超时时间和只读属性等。
例如,下面是一个使用@Transactional
注解实现事务控制的示例:
javaCopy code
@Transactional
public void transferMoney(Account fromAccount, Account toAccount, double amount) {
// 转账逻辑
}
在上面的示例中,transferMoney()
方法被标记为事务性方法,即使在方法执行期间发生异常,也会回滚整个事务,确保转账操作的原子性。
在Transactioanl属性中,我们主要了解下事务传播行为和事务隔离级别。我们不做测试程序,如果以后有应用场景,我们在讲解。
spring中传播行为为Propagation枚举类型,源代码2-1如下所示:
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
/**
* 传播行为
*/
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;
}
}
在Spring事务管理中,事务的传播行为是指当一个事务方法调用另一个事务方法时,当前事务如何传播到被调用的方法。Spring框架定义了七种事务传播行为:
以上的每种传播行为都适用于不同的场景和需求,例如:
REQUIRED
:应用于正常的业务逻辑,需要在一个事务中完成,而且需要保证事务的原子性。SUPPORTS
:适用于只读操作,即不需要进行修改,只需要读取数据的情况。MANDATORY
:适用于需要事务支持的操作,例如修改或删除操作。REQUIRES_NEW
:适用于需要独立的事务支持的操作,例如一些与业务逻辑无关的后台任务。NOT_SUPPORTED
:适用于只读操作或与事务无关的操作,例如查询操作。NEVER
:适用于不能在事务中执行的操作,例如一些文件操作或网络操作。NESTED
:适用于需要嵌套事务的场景,例如一些需要在某个业务逻辑完成后,再进行一些额外的操作的情况。事务传播行为是Spring事务管理的一个重要概念,了解和使用不同的传播行为可以帮助开发人员更好地控制和管理事务。
在并发环境下,多个事务可能同时访问数据库中的同一数据,这时需要保证事务的隔离性,以防止脏读、不可重复读、幻读等问题。事务的隔离级别定义了一个事务对另一个事务的影响程度,Spring框架支持四种事务隔离级别:
隔离级别越高,对性能的影响越大,因为需要对数据库进行更严格的锁定。因此,在选择隔离级别时,需要根据实际情况进行权衡,以获得最合适的性能和数据一致性。
在Spring事务管理中,可以通过@Transactional
注解的isolation
属性来指定事务隔离级别,默认为DEFAULT
,表示使用数据库的默认隔离级别。例如,可以使用以下注解来指定事务的隔离级别为可重复读:
@Transactional(isolation = Isolation.REPEATABLE_READ)
在Spring事务管理中,可以通过@Transactional
注解的timeout
属性来设置事务的超时时间,单位为秒,默认值为-1,表示不设置超时时间。
事务超级时间计算规则:
示例:
@Override
@Transactional(rollbackFor = RuntimeException.class, timeout = 3)
public void transfer(Integer fromId, Integer toId, BigDecimal mount) {
// 查询账户
Account from = accountDao.getAccount(fromId);
if (from == null) {
throw new IllegalArgumentException("账户不存在:" + fromId);
}
Account to = accountDao.getAccount(toId);
if (to == null) {
throw new IllegalArgumentException("账户不存在:" + toId);
}
// 模拟用时
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException("异常处理");
}
// 校验
if (from.getBalance().compareTo(mount) < 0) {
throw new RuntimeException("余额不足");
}
// 转账
from.setBalance(from.getBalance().subtract(mount));
to.setBalance(to.getBalance().add(mount));
int count = accountDao.updateAccount(from);
if (count != 1) {
throw new RuntimeException("转出失败");
}
count = accountDao.updateAccount(to);
if (count != 1) {
throw new RuntimeException("转转入失败");
}
// try {
// TimeUnit.SECONDS.sleep(5);
// } catch (InterruptedException e) {
// throw new RuntimeException("异常处理");
// }
}
在最后一条D ML语句count = accountDao.updateAccount(to);
之前会报错;之后模拟用时则不会报错。报错如下:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out:
@Ttransactional(readonly=true),设置只读事务的目的:
Spring会对只读事务进行了优化,这种优化称为“只读优化”,主要是针对只读事务的特点,采用了以下几种优化措施:
通过以上优化措施,Spring对只读事务的执行效率得到了很大的提升,使得只读事务在查询方面的性能表现更加优秀。但需要注意的是,只读事务的优化可能会对数据的一致性产生一定的影响,因此在使用只读事务时需要慎重考虑,并结合业务场景进行选择。
rollbackFor
是Spring中用于设置事务回滚的异常类的属性,表示当抛出指定的异常时,事务将回滚,即撤销已经提交的数据库操作,将数据恢复到之前的状态。
rollbackFor
属性接受一个异常类或多个异常类的数组作为参数,例如:
javaCopy code
@Transactional(rollbackFor = {SQLException.class, IOException.class})
public void updateUser(User user) throws SQLException, IOException {
// ...
}
在这个例子中,当在updateUser
方法中抛出SQLException
或IOException
异常时,Spring将会触发事务回滚,撤销之前的数据库操作。
如果不设置rollbackFor
属性,Spring默认只有在抛出RuntimeException
及其子类异常时,才会触发事务回滚。如果要回滚非运行时异常,可以在rollbackFor
属性中设置需要回滚的异常类。
需要注意的是,设置rollbackFor
属性时,指定的异常类应当是受检查异常(checked exception),即需要显式地在方法声明中指定抛出该异常,否则在方法中抛出该异常时,编译器会提示“未捕获异常”的错误。
在Spring中,当一个方法使用了@Transactional
注解进行事务管理,如果这个方法中发生了受检查异常(checked exception),那么Spring会将该异常转换成RuntimeException
类型的异常,并抛出该异常,从而触发事务回滚操作。
如果同时在应用中设置了全局异常处理器(例如@ControllerAdvice
注解的异常处理类),并在该处理器中处理了该异常类型,那么在异常被处理后,异常已经被成功捕获并处理,不会再被抛出到上层调用链中,从而不会触发事务回滚操作。
为了解决这个问题,可以通过设置rollbackFor
属性或rollbackForClassName
属性,在异常被处理时,仍然将其转换为RuntimeException
类型的异常并抛出,从而触发事务回滚。例如:
@Transactional(rollbackFor = Exception.class)
public void someMethod() throws Exception {
// ...
}
这个例子中,@Transactional
注解中设置了rollbackFor
属性,将所有异常都转换为RuntimeException
类型的异常,并触发事务回滚操作。当全局异常处理器捕获到异常并处理后,仍然会将异常转换为RuntimeException
类型并抛出,从而触发事务回滚。
另外,需要注意的是,如果在全局异常处理器中使用了@Transactional
注解,那么在处理异常时,Spring会自动启动一个新的事务进行处理。在这种情况下,如果全局异常处理器中的操作失败,那么该事务将会回滚,但不会影响原来的事务。因此,在使用全局异常处理器时需要慎重考虑是否需要进行事务管理。
如果小伙伴什么问题或者指教,欢迎交流。
❓QQ:806797785
⭐️源代码仓库地址:https://gitee.com/gaogzhen/spring6-study
参考:
[1]Spring框架视频教程[CP/OL].P118-135.
[2]ChatGPT