源码专题(三)Spring事务@Transactional的使用及原理

在Spring开发中,经常会用到这个注解,但它是怎么起到开启事务的作用的,还一知半解,今天就来分析一波它的用法和原理。

编程式事务和声明式事务

    Spring 支持两种事务管理方式:编程式和声明式,编程式是在程序中显式地使用TransactionTemplate来控制事务的开启和提交。

​    声明式事务是使用@Transactional注解,在类或方法上使用这个注解,就可以起到开启事务的作用,声明式事务是基于AOP的方式,在方法前开启一个事务,在方法执行后进行commit,中间进行一些异常的判断和处理。

​    相比来说,声明式事务使用起来更加优雅,AOP的方式对代码没有侵入性,比较推荐在日常开发中使用。

声明式事务的用法

@Transactional注解可以加在类或方法上,加在类上时是对该类的所有public方法开启事务。加在方法上时也是只对public方法起作用。另外@Transactional注解也可以加在接口上,但只有在设置了基于接口的代理时才会生效,因为注解不能继承。所以该注解最好是加在类的实现上。

下面看一下@Transactional注解的各项参数。

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

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

    String[] noRollbackForClassName() default {};
}

timtout

timtout是用来设置事务的超时时间,可以看到默认为-1,不会超时。

isolation

isolation属性是用来设置事务的隔离级别,数据库有四种隔离级别:读未提交、读已提交、可重复读、可串行化。MySQL的默认隔离级别是可重复读。

public enum Isolation {
    DEFAULT(-1),
    READ_UNCOMMITTED(1),  // 读未提交
    READ_COMMITTED(2),         // 读已提交
    REPEATABLE_READ(4),     // 可重复读
    SERIALIZABLE(8);            // 可串行化
}

readOnly

    readOnly属性用来设置该属性是否是只读事务,只读事务要从两方面来理解:它的功能是设置了只读事务后在整个事务的过程中,其他事务提交的内容对当前事务是不可见的。

​    那为什么要设置只读事务呢?它的好处是什么?可以看出它的作用是保持整个事务的数据一致性,如果事务中有多次查询,不会出现数据不一致的情况。所以在一个事务中如果有多次查询,可以启用只读事务,如果只有一次查询就无需只读事务了。

​    另外,使用了只读事务,数据库会提供一些优化。

​    但要注意的是,只读事务中只能有读操作,不能含有写操作,否则会报错。

propagation

    propagation属性用来设置事务的传播行为,对传播行为的理解,可以参考如下场景,一个开启了事务的方法A,调用了另一个开启了事务的方法B,此时会出现什么情况?这就要看传播行为的设置了。

public enum Propagation {
    REQUIRED(0),                 // 如果有事务则加入,没有则新建
    SUPPORTS(1),                // 如果已有事务就用,如果没有就不开启(继承关系)
    MANDATORY(2),                // 必须在已有事务中
    REQUIRES_NEW(3),        // 不管是否已有事务,都要开启新事务,老事务挂起
    NOT_SUPPORTED(4),   // 不开启事务
    NEVER(5),                        // 必须在没有事务的方法中调用,否则抛出异常
    NESTED(6);                    // 如果已有事务,则嵌套执行,如果没有,就新建(和REQUIRED类似,和REQUIRES_NEW容易混淆)
}

REQUIRES_NEW 和 NESTED非常容易混淆,因为它们都是开启了一个新的事务。我去查询了一下它们之间的区别,大概是这样:

REQUIRES_NEW是开启一个完全的全新事务,和当前事务没有任何关系,可以单独地失败、回滚、提交。并不依赖外部事务。在新事务执行过程中,老事务是挂起的。

NESTED也是开启新事务,但它开启的是基于当前事务的子事务,如果失败的话单独回滚,但如果成功的话,并不会立即commit,而是等待外部事务的执行结果,外部事务commit时,子事务才会commit。

rollbackFor

在@Transactional注解中,有一个重要属性是roolbackFor,这是用来判断在什么异常下会进行回滚的,当方法内抛出指定的异常时,进行事务回滚。rollbackForClassName也是类似的。

​    rollbackFor有个问题是默认情况会做什么,以前认为默认会对所有异常进行回滚,但其实默认情况下只对RuntimeException回滚。

noRollbackFor

这个和上面正好相反,用来设置出现指定的异常时,不进行回滚。

Spring事务实现机制原理

我们来分析一下Spring事务管理机制的实现原理。由于Spring内置AOP默认使用动态代理模式实现,我们就先来分析一下动态代理模式的实现方法。动态代理模式的核心就在于代码中不出现与具体应用层相关联的接口或者类引用,如上所说,这个代理类适用于任何接口的实现。下面我们来看一个例子。

public class TxHandler implements InvocationHandler {
private Object originalObject;
public Object bind(Object obj) {
 this.originalObject = obj;
 return Proxy.newProxyInstance(obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);
}

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
 Object result = null;
 if (!method.getName().startsWith("save")) {
  UserTransaction tx = null;
  try {
   tx = (UserTransaction) (new InitialContext().lookup("java/tx"));
   result = method.invoke(originalObject, args);
   tx.commit();
  } catch (Exception ex) {
   if (null != tx) {
    try {
     tx.rollback();
    } catch (Exception e) {
   }
  }
 }
} else {
 result = method.invoke(originalObject, args);
}
return result;
}
}

下面我们来分析一下上述代码的关键所在。

  首先来看一下这段代码:

return Proxy.newProxyInstance(
 obj.getClass().getClassLoader(),obj.getClass().getInterfaces(),this);

   java.lang.reflect.Proxy.newProxyInstance方法根据传入的接口类型 (obj.getClass.getInterfaces())动态构造一个代理类实例返回,这也说明了为什么动态代理实现要求其所代理的对象一定要实现一个接口。这个代理类实例在内存中是动态构造的,它实现了传入的接口列表中所包含的所有接口。

  再来分析以下代码:

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
 ……
 result = method.invoke(originalObject, args);
 ……
 return result;
}


   InvocationHandler.invoke方法将在被代理类的方法被调用之前触发。通过这个方法,我们可以在被代理类方法调用的前后进行一些处理,如代码中所示,InvocationHandler.invoke方法的参数中传递了当前被调用的方法(Method),以及被调用方法的参数。同时,可以通过method.invoke方法调用被代理类的原始方法实现。这样就可以在被代理类的方法调用前后写入任何想要进行的操作。

   Spring的事务管理机制实现的原理,就是通过这样一个动态代理对所有需要事务管理的Bean进行加载,并根据配置在invoke方法中对当前调用的方法名进行判定,并在method.invoke方法前后为其加上合适的事务管理代码,这样就实现了Spring式的事务管理。Spring中的AOP实现更为复杂和灵活,不过基本原理是一致的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值