【Spring】事务

Spring 事务

一、分类

声明式事务编程式事务 是两种事务管理的方式,它们在管理事务的实现和使用上有一些不同。

  • 声明式事务
    • 通过配置的方式来管理事务,通常通过 注解 或 XML配置 来声明事务的属性。
    • 开发人员只需在方法或类上添加事务相关的注解,而无需手动编写事务管理的代码。
    • Spring 提供了 @Transactional 注解来实现声明式事务,可以在需要添加事务管理的方法上添加该注解。
  • 编程式事务(了解)
    • 通过编程的方式来管理事务的,需要在代码中显式地编写事务管理的逻辑。
    • 开发人员需要手动调用事务管理器的 API 来开启、提交、回滚事务,并在需要的地方捕获并处理事务异常。
    • Spring 提供了 TransactionTemplate 类来简化编程式事务的使用,可以在代码中使用它来执行事务操作。

二、隔离级别

1、基本概念

级别名称隔离级别脏读不可重复读幻读数据库默认隔离级别
1读未提交Read Uncommitted-
2读已提交Read Committed×Oracle、SQL Server
3可重复读Repeatable Read××MySQL
4串行化Serializable×××-

级别1最低,级别4最高。隔离级别越高,安全性越高,性能越低。(Spring默认采用数据库默认的隔离级别)

2、相关配置

// Spring事务隔离级别设置
@Transactional(isolation = Isolation.REPEATABLE_READ)
public enum Isolation {
    DEFAULT(-1),			// 数据库默认隔离级别(Spring默认)
    READ_UNCOMMITTED(1),	// 读未提交
    READ_COMMITTED(2),		// 读已提交
    REPEATABLE_READ(4),		// 可重复度
    SERIALIZABLE(8);		// 串行化
}

三、传播行为

1、基本概念

事务传播行为:指的就是当一个事务方法调用另一个方法时,事务应该如何传播。

传播行为(方法A)说明(以方法A调用方法B为例)
REQUIRED(默认)如果方法 A 有事务,则方法 B 加入该事务;否则方法 B 开始一个新事务(增、删、改)
SUPPORTS如果方法 A 有事务,则方法 B 加入该事务;否则方法 B 以非事务的方式执行(查询)
MANDATORY如果方法 A 有事务,则方法 B 加入该事务;否则会抛出异常
REQUIRES_NEW方法 B 会创建一个新的事务,不论方法 A 是否已经处于一个事务中。
NOT_SUPPORTED方法 B 会以非事务的方式执行,不论方法 A 是否已经处于一个事务中。
NEVER如果方法 A 有事务,则抛出异常;如果方法 A 没有事务,方法 B 以非事务的方式执行
NESTED如果方法 A 有事务,则在嵌套事务中执行;否则方法 B 开始一个新事务

嵌套事务:指在一个事务内部可以开启子事务,子事务是嵌套在父事务内部的,它们共享事务的回滚或提交。

  • 子事务在父事务内部运行,但它有自己的保存点(Savepoint)。

    如果子事务内部发生异常并触发回滚操作,只有子事务会回滚到自己的保存点,而父事务不受影响。

总之,嵌套事务可以提供更细粒度的事务控制,允许在父事务内部开启子事务,并且子事务可以独立于父事务进行回滚。

Spring事务的传播机制是基于数据库连接来做的,一个数据库连接一个事务,如果传播机制配置为需要新开一个事务,那么实际上就是再建立一个数据库连接,在此新数据库连接上执行sql。

2、相关配置

// Spring事务传播行为设置
@Transactional(propagation = Propagation.REQUIRED)
public enum Propagation {
    REQUIRED(0),	// 默认
    SUPPORTS(1),
    MANDATORY(2),
    REQUIRES_NEW(3),
    NOT_SUPPORTED(4),
    NEVER(5),
    NESTED(6);
}

四、超时时间

// Spring超时时间设置,单位s(默认值-1,表示没有超时限制)
@Transactional(timeout = -1)

五、只读

// Spring只读设置(默认为false)
@Transactional(readOnly=true)

一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务

  • 如果一次执行单条查询语句

    则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性。

  • 如果一次执行多条查询语句(例如统计查询,报表查询)

    这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。

六、回滚规则

public @interface Transactional {
    // 定义了哪些异常会导致事务回滚而哪些不会
    Class<? extends Throwable>[] rollbackFor() default {};
    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};
    String[] noRollbackForClassName() default {};
}

以下是 rollbackFor()方法的注释

By default, a transaction will be rolling back on {@link RuntimeException}
and {@link Error} but not on checked exceptions (business exceptions)

可以看到,默认情况下,事务只有遇到 运行时异常 RuntimeExceptionError 才会回滚事务。

七、实现原理

Spring事务底层是基于 数据库事务 和 AOP机制 实现的

  1. 首先,对于使用了@Transactional注解的Bean,Spring会创建一个代理对象作为Bean,并放入IOC容器。
  2. 当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解。
  3. 如果没加,直接执行原方法;如果加了,执行以下逻辑:
  4. 利用事务管理器创建—个数据库连接。
  5. 修改数据库连接的autocommit属性为false,禁止此连接的自动提交。
  6. 执行方法,方法中会执行sql…
  7. 方法出现异常,判断这个异常是否需要回滚:需要回滚,则回滚事务;不需要回滚,则提交事务。

八、失效分析

  1. 数据库引擎不支持事务。(如:MyISAM)
  2. @Transactional加到了非public的方法上(Spring AOP 默认只会处理 public 方法)
  3. @Transactional的回滚规则不满足,默认只有遇到 RuntimeExceptionError 才会回滚事务。
  4. @Transactional的传播规则不满足,例如:NOT_SUPPORTEDNEVER
  5. 若同一类中,没有 @Transactional 注解的方法内部 调用有 @Transactional 注解的方法,事务失效。
/**
 * 内部调用导致Spring事务失效的解决方案:
 * 1、自己注入自己
 * 2、获取代理对象 AopContext.currentProxy()
 * 3、带有事务的方法写到另一个service
 */
@EnableAspectJAutoProxy(exposeProxy = true)
@Service
public class TransactionServiceImpl implements TransactionService {

    @Resource
    private UserMapper userMapper;

    @Lazy
    @Resource
    private TransactionService transactionService;

    /**
     * 事务失效
     */
    @Override
    public void test() {
        // 调用的是普通对象的update() -> 事务失效
        update();
    }

    /**
     * 自己注入自己 解决事务失效
     */
    @Override
    public void test1() {
        // 自己注入自己,注入的是代理对象 -> 调用代理对象的update() -> 事务生效
        transactionService.update();
    }

    /**
     * AopContext 解决事务失效
     *
     * 注意,必须要加 @EnableAspectJAutoProxy(exposeProxy = true),否则会抛异常
     * java.lang.IllegalStateException: Cannot find current proxy: 
     * Set 'exposeProxy' property on Advised to 'true' to make it available, and ensure that 
     * AopContext.currentProxy() is invoked in the same thread as the AOP invocation context.
     */
    @Override
    public void test2() {
        // 获取代理对象 -> 调用代理对象的update() -> 事务生效
        TransactionService transactionService = (TransactionService) AopContext.currentProxy();
        transactionService.update();
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void update() {
        UserEntity userEntity = userMapper.selectById(41);
        userEntity.setUsername("嘿嘿嘿嘿");
        userMapper.updateById(userEntity);
        throw new NullPointerException();
    }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

scj1022

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值