sprin 事务注解@Transactional的实现原理(转)

 出处:@Transactional实现原理html

 

Transactional是spring中定义的事务注解,在方法或类上加该注解开启事务。主要是经过反射获取bean的注解信息,利用AOP对编程式事务进行封装实现。AOP对事务的封装能够看个人这篇文章的介绍java

咱们先写个demo,感觉它的加载过程。mysql

spring事务注解:spring

              

1. 自定义一个注解sql

/** * @Target 做用域(做用在方法上,类上,或是属性上) * @Retention 运行周期 * @interface 定义注解 */ @Target(value = ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyAnnotation { //自定义注解的属性
    int id() default 0; String name() default "默认名称"; String[]arrays(); String title() default "默认标题"; }

2. 测试编程

import java.lang.reflect.Method; public class User { @MyAnnotation(name="吴磊",arrays = {"2","3"}) public  void aMethod () {} public  void bMethod () {} public static void main(String[] args) throws ClassNotFoundException { //1. 反射获取到类信息
        Class<?> forName = Class.forName("com.demo.User"); //2. 获取到当前类(不包含继承)全部的方法
        Method[] declaredMethods = forName.getDeclaredMethods(); //3. 遍历每一个方法的信息
        for (Method method : declaredMethods) { System.out.println("方法名称:" + method.getName()); //4. 获取方法上面的注解
            MyAnnotation annotation = method.getDeclaredAnnotation(MyAnnotation.class); if(annotation == null) { System.out.println("该方法上没有加注解...."); }else { System.out.println("Id:" + annotation.id()); System.out.println("Name:" + annotation.name()); System.out.println("Arrays:" + annotation.arrays()); System.out.println("Title:" + annotation.title()); } System.out.println("--------------------------"); } } }
=============================
【控制台输出】
方法名称:main
该方法上没有加注解....
--------------------------
方法名称:aMethod
Id:0
Name:吴磊
Arrays:[Ljava.lang.String;@74a14482
Title:默认标题
--------------------------
方法名称:bMethod
该方法上没有加注解....
--------------------------

总结:经过上面这么一个小demo咱们就能发现,反射获取到每个方法的注解信息而后进行判断,若是这是@Transactional注解,spring就会开启事务。接下来咱们能够按照这种思路基于上一篇博客的编程式事务本身实现一个事务注解。安全

手写注解事务:app

1. 导包ide

<dependencies>
        <!-- 引入Spring-AOP等相关Jar -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>3.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1_2</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
        </dependency>
    </dependencies>

2. 配置spring.xml文件工具

<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
    <!-- 扫描指定路劲 -->
    <context:component-scan base-package="com.wulei"/>
    <!-- 开启切面代理 -->
    <aop:aspectj-autoproxy /> 
    <!-- 1. 数据源对象: C3P0链接池 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!-- 2. JdbcTemplate工具类实例 -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

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

</beans>

3.1 自定义事务注解 (经过反射解析方法上的注解,若是有这个注解就执行事务逻辑)

import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; //@Transaction能够做用在类和方法上, 咱们这里只做用在方法上。
@Target(value = ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) /** * 自定义事务注解 */
public @interface MyAnnotation { }

3.2 封装编程式事务

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.DefaultTransactionAttribute; @Component @Scope("prototype") // 申明为多例,解决线程安全问题。 /** * 手写编程式事务 */
public class TransactionUtil { // 全局接受事务状态
    private TransactionStatus transactionStatus; // 获取事务源
 @Autowired private DataSourceTransactionManager dataSourceTransactionManager; // 开启事务
    public TransactionStatus begin() { System.out.println("开启事务"); transactionStatus = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute()); return transactionStatus; } // 提交事务
    public void commit(TransactionStatus transaction) { System.out.println("提交事务"); if(dataSourceTransactionManager != null) dataSourceTransactionManager.commit(transaction); } // 回滚事务
    public void rollback(TransactionStatus transaction) { System.out.println("回滚事务..."); if(dataSourceTransactionManager != null) dataSourceTransactionManager.rollback(transaction); } }

3.3 经过AOP封装事务工具类, 基于环绕通知和异常通知来触发事务。

import java.lang.reflect.Method; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.interceptor.TransactionAspectSupport; import com.wulei.transaction.TransactionUtil; @Aspect// 申明为切面
@Component /** * 切面类封装事务逻辑 */
public class AopTransaction { @Autowired private TransactionUtil transactionUtil; private TransactionStatus transactionStatus; /** * 环绕通知 在方法以前和以后处理事情 * @param pjp 切入点 */ @Around("execution(* com.wulei.service.*.*(..))") public void around(ProceedingJoinPoint pjp) throws Throwable { // 1.获取方法的注解
        MyAnnotation annotation = this.getMethodMyAnnotation(pjp); // 2.判断是否须要开启事务
        transactionStatus = begin(annotation); // 3.调用目标代理对象方法
 pjp.proceed(); // 4.判断关闭事务
 commit(transactionStatus); } /** * 获取代理方法上的事务注解 * @param pjp 切入点 */
    private MyAnnotation getMethodMyAnnotation(ProceedingJoinPoint pjp) throws Exception { //1. 获取代理对对象的方法
        String methodName = pjp.getSignature().getName(); //2. 获取目标对象
        Class<?> classTarget = pjp.getTarget().getClass(); //3. 获取目标对象类型
        Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes(); //4. 获取目标对象方法
        Method objMethod = classTarget.getMethod(methodName, par); //5. 获取该方法上的事务注解
        MyAnnotation annotation = objMethod.getDeclaredAnnotation(MyAnnotation.class); return annotation; } /** 开启事务 */
    private TransactionStatus begin(MyAnnotation annotation) { if(annotation == null) return null; return transactionUtil.begin(); } /** 关闭事务 */
    private void commit(TransactionStatus transactionStatus) { if(transactionStatus != null) transactionUtil.commit(transactionStatus); } /** * 异常通知进行 回滚事务 */ @AfterThrowing("execution(* com.wulei.service.*.*(..))") public void afterThrowing() { // 获取当前事务 直接回滚 //TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        if(transactionStatus != null) transactionUtil.rollback(transactionStatus); } }

4. 编写dao层

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; /* CREATE TABLE `t_users` ( `name` varchar(20) NOT NULL, `age` int(5) DEFAULT NULL, PRIMARY KEY (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; */ @Repository public class UserDao { @Autowired private JdbcTemplate jdbcTemplate; public int add(String name, Integer age) { String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);"; int result = jdbcTemplate.update(sql, name, age); System.out.println("插入成功"); return result; } }

5. 编写service

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.wulei.dao.UserDao; import com.wulei.transaction.MyAnnotation; @Service public class UserService { @Autowired private UserDao userDao; // 加上事务注解
 @MyAnnotation public void add() { userDao.add("test001", 20); int i = 1 / 0; userDao.add("test002", 21); //        // 若是非要try,那么出现异常不会被aop的异常通知监测到,必须手动在catch里面回滚事务。 // try { // userDao.add("test001", 20); // int i = 1 / 0; // userDao.add("test002", 21); // } catch (Exception e) { //            // 回滚当前事务 // System.out.println("回滚"); // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // }
 } public void del() { System.out.println("del..."); } }

6. 测试

import org.springframework.context.support.ClassPathXmlApplicationContext; import com.wulei.service.UserService; public class Main { public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = (UserService) applicationContext.getBean("userService"); // aop()对userService类进行了拦截,添加自定义事务注解的方法会触发事务逻辑
 userService.add(); // del()方法没有加注解,则什么也不会触发。 //userService.del();
 } }

 


@Transactional  自调用事务失效分析

 

出处:在同一个类中,一个方法调用另一个有注解(好比@Async,@Transational)的方法,注解失效的缘由和解决方法

 

  在同一个类中,一个方法调用另一个有注解(好比@Async,@Transational)的方法,注解是不会生效的。

  好比,下面代码例子中,有两方法,一个有@Transational注解,一个没有。若是调用了有注解的addPerson()方法,会启动一个Transaction;若是调用updatePersonByPhoneNo(),由于它内部调用了有注解的addPerson(),若是你觉得系统也会为它启动一个Transaction,那就错了,其实是没有的。

@Service public class PersonServiceImpl implements PersonService { @Autowired PersonDao personDao; @Override @Transactional public boolean addPerson(Person person) { boolean result = personDao.insertPerson(person)>0 ? true : false; return result; } @Override //@Transactional
 public boolean updatePersonByPhoneNo(Person person) { boolean result = personDao.updatePersonByPhoneNo(person)>0 ? true : false; addPerson(person); //测试同一个类中@Transactional是否起做用
  return result; } }

如何查看是否启动了Transaction?
  设置log leve为debug,能够查看是否有下面这个log,判断是否启动了Transaction:
  DEBUG org.springframework.jdbc.datasource.DataSourceTransactionManager - Creating new transaction with name...

一样地,@Async等其余注解也有这样的问题。
(关于@Async的用法,请参考:http://blog.csdn.net/clementad/article/details/47403185

缘由:
  spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,若是包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,其实是由代理类来调用的,代理类在调用以前就会启动transaction。然而,若是这个有注解的方法是被同一个类中的其余方法调用的,那么该方法的调用并无经过代理类,而是直接经过原来的那个bean,因此就不会启动transaction,咱们看到的现象就是@Transactional注解无效。

为何一个方法a()调用同一个类中另一个方法b()的时候,b()不是经过代理类来调用的呢?能够看下面的例子(为了简化,用伪代码表示):

@Service class A{ @Transactinal method b(){...} method a(){ //标记1
 b(); } } //Spring扫描注解后,建立了另一个代理类,并为有注解的方法插入一个startTransaction()方法:
class proxy$A{ A objectA = new A(); method b(){ //标记2
 startTransaction(); objectA.b(); } method a(){ //标记3
        objectA.a();    //因为a()没有注解,因此不会启动transaction,而是直接调用A的实例的a()方法
 } }

  当咱们调用A的bean的a()方法的时候,也是被proxy$A拦截,执行proxy$A.a()(标记3),然而,由以上代码可知,这时候它调用的是objectA.a(),也就是由原来的bean来调用a()方法了,因此代码跑到了“标记1”。因而可知,“标记2”并无被执行到,因此startTransaction()方法也没有运行。

了解了失效的缘由,解决的方法就简单了(两种):

  •   把这两个方法分开到不一样的类中;
  •   把注解加到类名上面;
相关文章
相关标签/搜索