Spring事务使用总结

Spring对事务的支持

首先,程序是否支持事务,取决于数据库,比如Mysql的Innodb引擎,那么就支持事务;如果是Myisam引擎,那么就根本不支持事务。

Spring支持两种方式的事务管理,一种是编程式,另一种是注解式。编程式对代码入侵太大,一般使用情况较少;基本上都是通过注解方式来进行事务管理。

事务传播行为

事务传播行为是为了解决业务层方法之间互相调用的事务问题。

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

Propagation.REQUIRED

使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:

  1. 如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
  2. 如果外部方法开启事务并且被Propagation.REQUIRED的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。

举个例子:

@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @Autowired
    B b;
    @Transactional(propagation=Propagation.REQUIRED)
    public void requiredA(){
        b.requiredB(); //1
        jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
        //除于0 ,制造一个程序异常
        int s = 3/0; //2
    }

}
@Component
@EnableTransactionManagement
public class B {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Transactional(propagation=Propagation.REQUIRED)
    public void requiredB(){
        jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");

    }
}

在方法A里面,制造一个异常//2处,此时方法A会回滚,那么方法B也就会回滚。

或者将异常放在方法B中,就算方法A对异常进行了try catch处理,方法B回滚了,那么方法A也一样会回滚。

@Component
@EnableTransactionManagement 
public class A {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    B b;

    @Transactional(propagation=Propagation.REQUIRED)
    public void requiredA(){
       jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
        try{
          //将异常放在B中,就算异常捕获了,A还是会回滚
            b.requiredB();
        }catch (Throwable e){
            System.out.println(e.getMessage());
        }
    }

}
@Component
@EnableTransactionManagement
public class B {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Transactional(propagation=Propagation.REQUIRED)
    public void requiredB(){
        jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
        //除于0 ,制造一个程序异常
        int s = 3/0; //2
    }
}

Propagation.REQUIRES_NEW

创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

比如上面,我们将方法B的注解改为:@Transactional(propagation=Propagation.REQUIRES_NEW)

@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    B b;

    @Transactional(propagation=Propagation.REQUIRED)
    public void requiredNewA(){
        b.requiredNewB(); //1
        jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
        //除于0 ,制造一个程序异常
        int s = 3/0; //2
    }

}
@Component
@EnableTransactionManagement
public class B {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public void requiredNewB(){
        jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
    }
}

此时,方法A抛出了异常,方法A会回滚,但是方法B不会回滚,因为方法B是独立的事务。
同理,如果方法B回滚,同样也不会对方法A造成影响,比如:

@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    B b;

    @Transactional(propagation=Propagation.REQUIRED)
    public void nestedA(){
        b.nestedB();
        jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
        //除于0 ,制造一个程序异常
        int s = 3/0;
    }

}
@Component
@EnableTransactionManagement
public class B {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Transactional(propagation=Propagation.NESTED)
    public void nestedB(){
        jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
    }
}

此时,方法A回滚,那么方法B也就会回滚。

如果将异常放在方法B里面:

@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    B b;

    @Transactional(propagation=Propagation.REQUIRED)
    public void nestedA(){
        b.nestedB();
        jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
        
    }

}
@Component
@EnableTransactionManagement
public class B {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Transactional(propagation=Propagation.NESTED)
    public void nestedB(){
        jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
        //除于0 ,制造一个程序异常
        int s = 3/0;
    }
}

Propagation.NESTED

如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。也就是说:

它大部分情况都等价于Propagation.REQUIRS_NEW,但是:
如果主事务回滚了,那么NESTED也会回滚。比如:

@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    B b;

    @Transactional(propagation=Propagation.REQUIRED)
    public void nestedA(){
        b.nestedB(); //1
        jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
        //除于0 ,制造一个程序异常
        int s = 3/0; //2
    }

}
@Component
@EnableTransactionManagement
public class B {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Transactional(propagation=Propagation.NESTED)
    public void nestedB(){
        jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
    }
}

此时方法A产生了异常,出现了回滚,那么方法B也会回滚。

Propagation.MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
这个一般用得的比较少。
比如方法A有事务:

@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    B b;

   @Transactional(propagation=Propagation.REQUIRED) //1
    public void mandatoryA(){
        b.mandatoryB();
        jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");

    }

}
@Component
@EnableTransactionManagement
public class B {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Transactional(propagation=Propagation.MANDATORY)
    public void mandatoryB(){
        jdbcTemplate.update("insert into user(id,name) value (3,'wangwu')");
    }
}

此时程序正常,未见异常。
如果将//1处,方法A的@Transactional注解去掉,再次运行执行方法A,就会出现如下异常:

org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'

Propagation.NOT_SUPPORTED

表示不支持事务,方法不会以事务的方式进行。如果有事务存在,将它挂起,以无事务状态运行 。
无事务,就是指底层的Connection对象的autoCommit、isolation等属性与数据库有关,与dataSource设置的属性有关,不会被Spring改变。

事务隔离级别

Spring有一个枚举类Isolation,专门定义事务隔离级别。

public enum Isolation {
    DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
    READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
    READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
    REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
    SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);

    private final int value;
    Isolation(int value) {
        this.value = value;
    }
    public int value() {
        return this.value;
    }

}
  • DEFAULT:使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别;
  • READ_UNCOMMITTED:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读;
  • READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生;
  • REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生;
  • SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别;

Spring 事务失效的几种情况

@Transactional只有在ublic方法才起作用

@Transactional注解作用的方法,private方法直接就编译出错,protected方法注解不会生效。比如:

    @Transactional
    protected void pA(){
        jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
        int s =3/0;
    }

该方法的事务不会生效

自调用

在一个非事务方法里调用一个事务方法,此时事务方法的事务就会失效。比如:

@Component
@EnableTransactionManagement
public class TxInvalidMaster {

    @Autowired
    JdbcTemplate jdbcTemplate;

    public void a(){
        jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
        b();
    }

    @Transactional
    public void b(){
        jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
        int s =3/0;
    }
}

执行方法a,会发现方法b的事务并没有生效。

属性rollbackFor指定异常不正确

比如,下面方法抛出的是IoException,但是rollbackFor却是SQLException,那么此时事务也不会回滚

    @Transactional(rollbackFor = SQLException.class)
    public void rollbackForA() throws IOException {
        jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
        throw new IOException("123");
    }

参考文章

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值