事务回滚@ Transactional 详谈

4,549 阅读5分钟

前言: 事务回滚我们总是在用到,但是有可能不太了解具体的细节,接下来我会通过源码解读以及真实的案例测试,来说明。 接下来我会从三方面来讲述事务的运用:

  1. 源码解读
  2. 事务使用
  3. 事务的失效场景

1.源码解读


/**
 描述事务的属性在一个方法或者类上(个人觉得应该是使用事务的属性在方法或者类上)
 *<p>
  这种注释类型通常可以直接与Spring的注释类型进行比较,
 实际上将直接将数据转换为后者,因此Spring的事务支持代码不必知道注释。 如果没有规则与异常相关,则将其视为回滚运行时异常
 */

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

    /**
     * 主要是spring不提供事务管理器,可以通过这个去定义自己想要的事务管理器,例如hibernate的、JTA的、JDBC的
     * 大致上有以下几种
     * org.springframework.jdbc.datasource.DataSourceTransactionManager  DBC及iBATIS、MyBatis框架事务管理器
     * org.springframework.orm.jdo.JdoTransactionManager Jdo事务管理器
     * org.springframework.orm.jpa.JpaTransactionManager Jpa事务管理器
     * org.springframework.orm.hibernate3.HibernateTransactionManager  hibernate事务管理器
     * org.springframework.transaction.jta.JtaTransactionManager Jta事务管理器
     */
    String value() default "";


    /**
     * * 事务传播类型 默认是required 具体解释如下:
     * @Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
     * @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
     * @Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
     * @Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
     * @Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
     * @Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
     * <p>
     */
    Propagation propagation() default Propagation.REQUIRED;
    
    
    /**
     * 事务的隔离机制 默认是数据库的隔离机制 需要是innodb引擎数据库才可以
     * READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),  	读取未提交数据(会出现脏读, 不可重复读) 基本不使用
     * READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED), 读取已提交数据(会出现不可重复读和幻读)
     * REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ), 可重复读(会出现幻读)
     * SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE); 串行化
     */
    Isolation isolation() default Isolation.DEFAULT;


    /*   
     * 事务超时时间 默认-1 永远不超时 如果设置之后  达到时间自动回滚
     */
    int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;


    /**
     * 设置是否只读 默认是false  如果设置之后会锁住库 如果有插入的话 会报异常 java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
     */
    boolean readOnly() default false;

    /**
     * 回滚的异常 默认只能是回滚runtimeExceptional
     如果想全部回滚需要加上 rollback=exceptional.class
     * CheckedException不回滚:
     * Java认为Checked异常都是可以被处理的异常,所以Java程序必须显式的处理Checked异常,如果程序没有处理checked异常,程序在编译时候将发生错误。
     * 我们比较熟悉的Checked异常有
     * Java.lang.ClassNotFoundException
     * Java.lang.NoSuchMetodException
     * java.io.IOException
     * <p>
     * RunTimeException 回滚:
     * Runtime如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序。当然如果你有处理要求也可以显示捕获它们。
     * 我们比较熟悉的RumtimeException类的子类有
     * Java.lang.ArithmeticException
     * Java.lang.ArrayStoreExcetpion
     * Java.lang.ClassCastException
     * Java.lang.IndexOutOfBoundsException
     * Java.lang.NullPointerException
     *
     * @Transactional(rollbackFor=RuntimeException.class)
     * 指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
     */
    Class<? extends Throwable>[] rollbackFor() default {};



    /**
     * 这个是支持输入的类名称 详细功能同上
     * @Transactional(rollbackForClassName="RuntimeException")
     * 指定多个异常类名称:@Transactional(rollbackForClassName={"RuntimeException","Exception"})
     */
    String[] rollbackForClassName() default {};


    /**
     
     * 不会滚的异常可以在这设置 ,也可以自己实现异常 但是必须要继承runtimeExceptional
     */
    Class<? extends Throwable>[] noRollbackFor() default {};


    /**
     *  同上 这个是支持输入的类名称  不会滚的异常可以在这设置 ,也可以自己实现异常 但是必须要继承runtimeExceptional
     */
    String[] noRollbackForClassName() default {};

}


2.事务使用

(1). 一个方法中涉及两次及以上重要插入,用事务来保证完整性 原子性 (2). 事务只能用在public 方法上 ,其他方法不起作用 (3). 可以放在ServiceImpl类上。全局实现事务 下面举一个简单的例子:

@Override
@Transactional(rollbackFor = Exception.class)
public int insert(DneWechat record) {

    record.setId(1);
    record.setName("hj12334");
    updateByPrimaryKey(record);
    dneWechatMapper.insert(record);

    insertMemberStudent();
    return 1;
}

 //该方法是被第二次调用的方法 如果抛出异常 则全部回滚
 //该方法可以不用写@transectional 因为事务是可以继承的 
@Transactional(rollbackFor = Exception.class)
public void insertMemberStudent() {
    DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
            .receivedTime(new Date())
            .expireTime(new Date())
            .userId(123)
            .build();
    dneMemberStudentService.insert(dneMemberStudent);
    throw new RuntimeException();
}

3.事务失效的几种场景

一、try catch之后没有抛出对应的异常,这样是不会回滚的,错误例子如下:

@Override
    @Transactional(rollbackFor = Exception.class)
    public int insert(DneWechat record) {

        record.setId(1);
        record.setName("hj12334");
        updateByPrimaryKey(record);
        dneWechatMapper.insert(record);

        insertMemberStudent();
        return 1;
    }

    @Transactional(rollbackFor = Exception.class)
    public void insertMemberStudent() {
        DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
                .receivedTime(new Date())
                .expireTime(new Date())
                .userId(123)
                .build();
        dneMemberStudentService.insert(dneMemberStudent);
        try {

            int a = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

二、 private 方法 不会回滚,错误例子如下:

 @Override
    @Transactional
    public int insert(DneWechat record) throws IOException {

        record.setId(1);
        record.setName("hj12334");
        updateByPrimaryKey(record);
        dneWechatMapper.insert(record);

        insertMemberStudent();

        return 1;
    }

    @Transactional(rollbackFor = Exception.class)
    private void insertMemberStudent() {
        DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
                .receivedTime(new Date())
                .expireTime(new Date())
                .userId(123)
                .build();
        dneMemberStudentService.insert(dneMemberStudent);
    }

三、 检查异常 例如ioexception 不会回滚,例子如下:

 @Override
    @Transactional
    public int insert(DneWechat record) throws IOException {

        record.setId(1);
        record.setName("hj12334");
        updateByPrimaryKey(record);
        dneWechatMapper.insert(record);

        insertMemberStudent();
            throw new IOException();

    }

    @Transactional(rollbackFor = Exception.class)
    public void insertMemberStudent() {
        DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
                .receivedTime(new Date())
                .expireTime(new Date())
                .userId(123)
                .build();
        dneMemberStudentService.insert(dneMemberStudent);
    }

四、 被调用的方法有注解,Override 方法没有注解,不会回滚,因为注解注入是通过aop实现的,例子如下:

@Override
    public int insert(DneWechat record) {

        record.setId(1);
        record.setName("hj12334");
        updateByPrimaryKey(record);
        dneWechatMapper.insert(record);

        insertMemberStudent();
        return 1;
    }

    @Transactional(rollbackFor = Exception.class)
    public void insertMemberStudent() {
        DneMemberStudent dneMemberStudent = DneMemberStudent.builder()
                .receivedTime(new Date())
                .expireTime(new Date())
                .userId(123)
                .build();
        dneMemberStudentService.insert(dneMemberStudent);
        throw new RuntimeException();
    }

我等采石之人,当心怀大教堂之愿景! 欢迎关注我的公众号!!