Spring事务隔离级别和传播级别测试分析

438 阅读2分钟

Spring事务隔离级别和传播级别测试分析

在某些情况下, 我们需要修改某事务事务隔离级别从Spring默认级别**(此处使用MySql, 默认为ISOLATION_REPEATABLE_READ)**为脏读, 从而读取其他事务没有提交的数据. 于是对事务隔离级别和传播属性作了个简单测试.

测试组合如下:

名称ISOLATIONPROPOGATION结果(是否可读)
NONE~~~~~~~~true
BOTH_DEFAULTISOLATION_REPEATABLE_READ**(DEFAULT)**REQUIRED**(DEFAULT)**true
REQUIRED_NEW(DEFAULT)REQUIRES_NEWfalse
READ_UNCOMMITTEDREAD_UNCOMMITTEDREQUIRED**(DEFAULT)**true
REQUIRES_NEW_READ_UNCOMMITTEDREAD_UNCOMMITTEDREQUIRES_NEWtrue
表1

1. 测试步骤:

1.1 测试调用(原始事务)

	@Transactional(rollbackFor = {Exception.class})
    @Override
    public String create(ApplicationDTO applicationDTO, BaseParam baseParam) {
        final ApplicationPO applicationPO = createApplication(applicationDTO);
        String applicationId = applicationPO.getApplicationId();

        // 此处注意事项参见要点2.1
        final ApplicationServiceImpl proxy = (ApplicationServiceImpl) AopContext.currentProxy();
        
        proxy.testTransactionNONE(applicationId);
        proxy.testTransactionBothDefault(applicationId);
        proxy.testTransactionRequiredNew(applicationId);
        proxy.testTransactionReadUncommitted(applicationId);
        proxy.testTransactionRequiresNewAndReadUncommitted(applicationId);
    }

1.2 事务测试方法具体行为(所有测试方法行为一致)

通过原始事务方法传入的applicationId参数, 尝试在各事务中方法中查询获取此应用记录.

public void testTransactionNONE(String id) {
    final ApplicationPO applicationPO = getApplicationById(id);
    
    System.out.println("No Transaction = " + (applicationPO != null));
}

1.3 各测试方法事务属性配置(参见[表1])

  • NONE

    public void testTransactionNONE(String id){
    
  • BOTH_DEFAULT

    @Transactional
    public void testTransactionBothDefault(String id) {
    
  • REQUIRED_NEW

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void testTransactionRequiredNew(String id) {
    
  • READ_UNCOMMITTED

    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    public void testTransactionReadUncommitted(String id) {
    
  • REQUIRES_NEW_READ_UNCOMMITTED

    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_UNCOMMITTED)
    public void testTransactionRequiresNewAndReadUncommitted(String id) {
    

2. 要点

2.1 嵌套调用事务方法

  • 事务方法内部嵌套调用另外一个事务方法必须通过代理对象调用, 否则后续方法事务配置不生效, 沿用原始事务.
  • 通过AopContext.currentProxy()获取当前类的代理对象, 所以必须设置expose-proxytrue, 否则抛出异常IllegalStateException.
  • 由于这几个测试方法不是接口方法, 而是在测试类中直接添加的, 所以该测试类必须通过cglib代理, 所以必须设置proxy-target-class为true.
  • 配置标签: <aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/>

3. 结果分析:

1. NONE : true

没有配置事务, 则沿用上一个事务, 处于同一个事务中所以可读.

2. BOTH_DEFAULT : true

传播级别propogation=required, 没有事务则创建一个新事务, 若上一个事务存在则沿用上一个事务, 处于同一个事务中所以可读.

3. REQUIRED_NEW : false

传播级别propogation=required_new, 创建一个新事务, 若上一个事务存在则将上一个事务挂起, 处于不同事务中所以不可读.

4. READ_UNCOMMITTED : true

传播级别propogation=required, 没有事务则创建一个新事务, 若上一个事务存在则沿用上一个事务, 处于同一个事务中所以可读.

此处虽然设置了READ_UNCOMMITTED, 但由于沿用了上一个事务, 所以不会生效.

5. REQUIRES_NEW_READ_UNCOMMITTED : true

传播级别propogation=required_new, 创建一个新事务, 若上一个事务存在则将上一个事务挂起. 虽然处于不同事务中默认不可读取消未提交数据, 但此处隔离级别设置为READ_UNCOMMITTED , 允许读取不同事务中的未提交数据.