浅谈Spring之事务剖析

浅谈Spring之事务剖析

Tans 1,187 2022-03-24

Spring事务

事务就是一组逻辑上的动作要全部执行,或者全不执行,是为了保证数据修改前后的数据定义的完整性。

Spring提供两种事务管理方式,分别是编程式和声明式。

  • 编程式:通过编码的方式来手动启用,提交或者回滚事务,力度更细,但更麻烦
  • 声明式:通过在方法类或者接口上添加注解进行包装,无侵入地实现事务,更方便但是力度更大。

需要注意:使用的数据库必须要支持事务,否则事务将不起作用。比如说MysqlMyISAM引擎就不支持事务。

事务的使用

· 编程式事务

  1. 编写配置文件
    <context:component-scan base-package="transaction"/>

    <!--从jdbc.properties中注入datasource-->
    <context:property-placeholder location="jdbc.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${prop.driverClass}"/>
        <property name="username" value="${prop.username}"/>
        <property name="url" value="${prop.url}"/>
        <property name="password" value="${prop.password}"/>
    </bean>


    <!--配置事务管理器-->
    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置Transaction Template-->
    <bean class="org.springframework.transaction.support.TransactionTemplate" id="transactionTemplate">
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <!--配置JDBCTemplate-->
    <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>
  1. 使用事务
@Component
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Autowired
    PlatformTransactionManager transactionManager;

    @Autowired
    TransactionTemplate transactionTemplate;


     //第一种使用方式,显式的去管理
    public void transfer(){
        DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(definition);
        try{
             //一组操作............
            jdbcTemplate.update("update account set balance = ? where age = ?", 90, 10);
            transactionManager.commit(status);
        }catch (Exception e){
            e.printStackTrace();
            transactionManager.rollback(status);
        }
    }
     //使用transactionTemplate
    public void transfer2(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult(){
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                try{
                    jdbcTemplate.update("update account set balance = ? where age = ?", 100, 10);
                }catch (DataAccessException e){
                    e.printStackTrace();
                    status.setRollbackOnly();
                }
            }
        });
    }
}

·声明式的使用

使用@Transactional注解加在特定的方法或者接口上。

@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.DEFAULT, rollbackFor = Exception.class)
public int save(Info info) throws Exception {
}

几个重要接口

  • PlatformTransactionManager:(平台)事务管理器,Spring 事务策略的核心。
  • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。
  • TransactionStatus:事务运行状态。

1. PlatformTransactionManager : 事务管理接口

public interface PlatformTransactionManager extends TransactionManager {
     //获得事务
     //@Param: definition: 事务定义信息 
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition);
     //提交事务
	void commit(TransactionStatus status) throws TransactionException;
     //回滚事务
	void rollback(TransactionStatus status) throws TransactionException;
}

2. TransactionDefinition : 事务定义信息

上述获取事务的时候,传入的事务定义信息definition包含了什么内容呢?

public class DefaultTransactionDefinition{
     //设置事务传播行为, 默认是REQUIRED
     public final void setPropagationBehavior(int propagationBehavior);
     //设置隔离级别, 默认是DEFAULT
     public final void setIsolationLevelName(String constantName) ;
     //设置超时时间, 如果事务超过该时间限制还未完成,那么就会回滚。 默认为 -1
     public final void setTimeout(int timeout) ;
     //设置是否只读,默认为false
     public final void setReadOnly(boolean readOnly);
     //.......................
}

3. TransactionStatus:事务状态接口

public interface TransactionStatus{
    boolean isNewTransaction(); // 是否是新的事务
    boolean hasSavepoint(); // 是否有恢复点
    void setRollbackOnly();  // 设置为只回滚
    boolean isRollbackOnly(); // 是否为只回滚
    boolean isCompleted; // 是否已完成
     //...........
}

事务传播行为

PROPAGATION_REQUIRED

定义:如果当前没有事务,那么就新建一个事务,如果当前存在事务,那么就加入这个事务。

@Transactional(propagation = Propagation.REQUIRED)
method1(){
     //.......
}

@Transactional(propagation = Propagation.REQUIRED)
method2(){
     //.......
}

@Transactional(propagation = Propagation.REQUIRED)
method2(){
     //.......
}
  1. test1-1{ 
         method1();
         method2();
    }
    

    image-20220324175412647

  2. test1-2{
         method1();
         method3();
    }
    

    image-20220324175545532

  3. @Transactional(propagation = Propagation.REQUIRED)
    test1-3{
         method1(); 
         method3();
         throw new Exception();
    }
    

    image-20220324175558710

  4. @Transactional(propagation = Propagation.REQUIRED)
    test1-4{ 
         method1(); 
         try{     
              method3(); 
         }catch(){
              
         }
    }
    

    image-20220324175604794

结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

PROPAGATION_REQUIRES_NEW

定义:新建事务,如果当前存在事务,就把当前事务挂起

@Propagation.REQUIRED
method1Re( ){
     //.......
}

@PROPAGATION_REQUIRES_NEW
method1Re_new(){
}

@PROPAGATION_REQUIRES_NEW
method2Re_new(){
     //.......
}

@PROPAGATION_REQUIRES_NEW
method2Re_new_throw(){
     throw Exeception();
}

image-20220324175911043

image-20220324175953107

image-20220324175959457

结论:在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。

PROPAGATION_NESTED

定义:新建事务,如果当前存在事务,就在嵌套事务内执行,否则执行和 PROPAGATION_REQUIRED一样的操作

image-20220324180120571

image-20220324180129085

image-20220324180136260

image-20220324180145434

结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务

三者区别

由“1.3 场景”和“3.2 场景”对比,我们可知:NESTED 和 REQUIRED 修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是 REQUIRED 是加入外围方法事务,所以和外围事务同属于一个事务,一旦 REQUIRED 事务抛出异常被回滚,外围方法事务也将被回滚。而 NESTED 是外围方法的子事务,有单独的保存点,所以 NESTED 方法抛出异常被回滚而且异常被处理,那么不会影响到外围方法的事务。

由“2.2 场景”和“3.3 场景”对比,我们可知:NESTED 和 REQUIRES_NEW 都可以做到内部方法事务回滚而不影响外围方法事务。但是因为 NESTED 是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而 REQUIRES_NEW 是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。

PROPAGATION_SUPPORTS

如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

PROPAGATION_NOT_SUPPORTED

以非事务方式运行,如果当前存在事务,则把当前事务挂起。

PROPAGATION_NEVER

以非事务方式运行,如果当前存在事务,则抛出异常。

Transaction注解使用

/**
 * Describes a transaction attribute on an individual method or on a class.
 * <p>When this annotation is declared at the class level, it applies as a default
 * to all methods of the declaring class and its subclasses.
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
     Propagation propagation() default Propagation.REQUIRED; //默认的事务传播
     Isolation isolation() default Isolation.DEFAULT; //默认的隔离机制
     int timeout() default TransactionDefinition.TIMEOUT_DEFAULT; //事务超时时间,默认-1
     boolean readOnly() default false; //事务是否只读
     Class<? extends Throwable>[] rollbackFor() default {};//事务的回滚异常类型, 默认都进行回滚
}	

作用范围:

  1. 方法 :推荐将注解使用于方法上,不过需要注意的是:该注解只能应用到 public 方法上,否则不生效。
  2. :如果这个注解使用在类上的话,表明该注解对该类及其子类中所有的 public 方法都生效。
  3. 接口 :不推荐在接口上使用。

参考资料

  1. 可能是最漂亮的Spring事务管理详解
  2. Spring事务的配置与使用