出處:@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()方法也沒有運行。
瞭解了失效的緣由,解決的方法就簡單了(兩種):