spring事物(2)-----手寫spring註解事務&&事務傳播行為
阿新 • • 發佈:2018-11-20
一,spring事務的註解
1.1,spring自帶的@Transactional例子
package com.qingruihappy1.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class UserDao { @Autowiredprivate JdbcTemplate jdbcTemplate; public void add(String name, Integer age) { String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);"; int updateResult = jdbcTemplate.update(sql, name, age); System.out.println("updateResult:" + updateResult); } }
package com.qingruihappy1.service; //user 服務層 public interface UserService { public void add(); }
package com.qingruihappy1.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.qingruihappy1.dao.UserDao; import com.qingruihappy1.service.UserService;//user 服務層 @Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; //宣告:@Transactional 或者XML方式 //方法執行開始執行前,開啟提交事務 @Transactional public void add() { userDao.add("test001", 20); int i = 1 / 0; System.out.println("################" + i); userDao.add("test002", 21); //注意下面的程式碼是不會回滾的,因為異常給吃掉了,所以是不會回滾的。 //因為回滾的機制在異常中的,現在吃掉異常之後就不會進入異常的方法中,所以自然而然的不會回滾的。 /* try { userDao.add("test001", 20); int i = 1 / 0; System.out.println("################" + i); userDao.add("test002", 21); } catch (Exception e) { e.printStackTrace(); }*/ } // 方法執行完畢之後,才會提交事務 }
<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"></context:component-scan> <aop:aspectj-autoproxy></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> <!-- 開啟註解事務 注意自定義註解的時候把它註釋掉,用spring自帶的話則加上 --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager" /> </beans>
測試:
package com.qingruihappy1.test; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.qingruihappy1.service.UserService; public class Test001 { public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = (UserService) applicationContext.getBean("userServiceImpl"); userService.add(); } }
1.2,自定義註解案例
package com.qingruihappy2.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * @Target(value = ElementType.METHOD) 設定註解許可權<br> * @author qingruihappy * */ @Target(value = ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) // @interface 定義註解 public @interface AddAnnotation { // 手寫Spring事務註解 int userId() default 0; String userName() default "預設名稱"; String[]arrays(); }
package com.qingruihappy2.annotation; import java.lang.reflect.Method; public class User { @AddAnnotation(userName = "張三", userId = 18, arrays = { "1" }) public void add() { } public void del() { } public static void main(String[] args) throws ClassNotFoundException { // 怎麼樣獲取到方法上註解資訊 反射機制 Class<?> forName = Class.forName("com.qingruihappy2.annotation.User"); // 獲取到當前類(不包含繼承)所有的方法 Method[] declaredMethods = forName.getDeclaredMethods(); for (Method method : declaredMethods) { // 獲取該方法上是否存在註解 System.out.println("####方法名稱" + method.getName()); AddAnnotation addAnnotation = method.getAnnotation(AddAnnotation.class); if (addAnnotation == null) { // 該方法上沒有註解 System.out.println("該方法上沒有加註解.."); continue; } // 在該方法上查詢到該註解 System.out.println("userId:" + addAnnotation.userId()); System.out.println("userName:" + addAnnotation.userName()); System.out.println("arrays:" + addAnnotation.arrays()); System.out.println(); } } }
####方法名稱add userId:18 userName:張三 arrays:[Ljava.lang.String;@18760838 ####方法名稱main 該方法上沒有加註解.. ####方法名稱del 該方法上沒有加註解..
1.3,手寫spring註解事務
步驟
package com.qingruihappy3.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class UserDaoa { @Autowired private JdbcTemplate jdbcTemplate; public void add(String name, Integer age) { String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);"; int updateResult = jdbcTemplate.update(sql, name, age); System.out.println("updateResult:" + updateResult); } }
package com.qingruihappy3.service; //user 服務層 public interface UserService { public void add(); public void del(); }
package com.qingruihappy3.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.qingruihappy3.annotation.ExtTransaction; import com.qingruihappy3.dao.UserDaoa; import com.qingruihappy3.service.UserService; //user 服務層 @Service public class UserServiceImpla implements UserService { @Autowired private UserDaoa userDao; /* * 宣告:@Transactional 或者XML方式 方法執行開始執行前,開啟提交事務 */ @ExtTransaction public void add() { userDao.add("test001", 20); int i = 1 / 0; System.out.println("################" + i); userDao.add("test002", 21); // 獲取當前事務,手動進行回滾 } // 方法執行完畢之後,才會提交事務 public void del() { System.out.println("del"); } }
註解:
package com.qingruihappy3.annotation; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 事務註解 設定傳播行為 @Target({ ElementType.METHOD })//只在方法上起作用 @Retention(RetentionPolicy.RUNTIME)//執行時的註解 public @interface ExtTransaction { }
工具類
package com.qingruihappy3.transaction; 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; // 程式設計事務(需要手動begin 手動回滾 手都提交) @Component @Scope("prototype") // 每個事務都是新的例項 目的解決執行緒安全問題 多例子 prototype就是多例的。 public class TransactionUtils { // 全域性接受事務狀態 TransactionStatus宣告為全域性的原因就是讓每個事務都建立一個例項,防止出現執行緒安全的問題 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("提交事務"); dataSourceTransactionManager.commit(transaction); } // 回滾事務 public void rollback() { System.out.println("回滾事務..."); dataSourceTransactionManager.rollback(transactionStatus); } }
切面類:
package com.qingruihappy3.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.qingruihappy3.annotation.ExtTransaction; import com.qingruihappy3.transaction.TransactionUtils; // 自定義事務註解具體實現 @Aspect @Component public class AopExtTransaction { // 一個事務例項子 針對一個事務 @Autowired private TransactionUtils transactionUtils; // 使用異常通知進行 回滾事務 @AfterThrowing("execution(* com.qingruihappy3.service.*.*.*(..))") public void afterThrowing() { // 獲取當前事務進行回滾 // TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); transactionUtils.rollback(); } // 環繞通知 在方法之前和之後處理事情 @Around("execution(* com.qingruihappy3.service.*.*.*(..))") public void around(ProceedingJoinPoint pjp) throws Throwable { // 1.獲取該方法上是否加上註解 ExtTransaction extTransaction = getMethodExtTransaction(pjp); TransactionStatus transactionStatus = begin(extTransaction); // 2.呼叫目標代理物件方法 pjp.proceed(); // 3.判斷該方法上是否就上註解 commit(transactionStatus); } private TransactionStatus begin(ExtTransaction extTransaction) { if (extTransaction == null) { return null; } // 2.如果存在事務註解,開啟事務 return transactionUtils.begin(); } private void commit(TransactionStatus transactionStatus) { if (transactionStatus != null) { // 5.如果存在註解,提交事務 transactionUtils.commit(transactionStatus); } } // 獲取方法上是否存在事務註解 private ExtTransaction getMethodExtTransaction(ProceedingJoinPoint pjp) throws NoSuchMethodException, SecurityException { String methodName = pjp.getSignature().getName(); // 獲取目標物件 Class<?> classTarget = pjp.getTarget().getClass(); // 獲取目標物件型別 Class<?>[] par = ((MethodSignature) pjp.getSignature()).getParameterTypes(); // 獲取目標物件方法 Method objMethod = classTarget.getMethod(methodName, par); ExtTransaction extTransaction = objMethod.getAnnotation(ExtTransaction.class); return extTransaction; } }
package com.qingruihappy3; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.qingruihappy3.service.UserService; public class Test001 { public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = (UserService) applicationContext.getBean("userServiceImpla"); userService.add(); } }
二:事務的傳播行為
我們通過一張圖引入
這個怎麼實現呢?
我們來看這7中傳播行為
Propagation(key屬性確定代理應該給哪個方法增加事務行為。這樣的屬性最重要的部份是傳播行為。)有以下選項可供使用:
PROPAGATION_REQUIRED--支援當前事務,如果當前沒有事務,就新建一個事務。這是最常見的選擇。
PROPAGATION_SUPPORTS--支援當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY--支援當前事務,如果當前沒有事務,就丟擲異常。
PROPAGATION_REQUIRES_NEW--新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED--以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER--以非事務方式執行,如果當前存在事務,則丟擲異常。
我們通過程式碼來看:
package com.qingruihappy4.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class LogDao { @Autowired private JdbcTemplate jdbcTemplate; public void add(String name) { String sql = "INSERT INTO t_log(log_name) VALUES(?);"; int updateResult = jdbcTemplate.update(sql, name); System.out.println("##LogDao##updateResult:" + updateResult); } }
package com.qingruihappy4.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; @Repository public class UserDaoc { @Autowired private JdbcTemplate jdbcTemplate; public void add(String name, Integer age) { String sql = "INSERT INTO t_users(NAME, age) VALUES(?,?);"; int updateResult = jdbcTemplate.update(sql, name, age); System.out.println("updateResult:" + updateResult); } }
package com.qingruihappy4.service; public interface LogService { public void addLog(); }
package com.qingruihappy4.service; //user 服務層 public interface UserService { public void add(); public void del(); }
package com.qingruihappy4.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import com.qingruihappy4.dao.LogDao; import com.qingruihappy4.service.LogService; @Service public class LogServiceImpl implements LogService { @Autowired private LogDao logDao; @Transactional(propagation = Propagation.REQUIRES_NEW)關鍵就是這一行程式碼 public void addLog() { logDao.add("addLog" + System.currentTimeMillis()); } }
package com.qingruihappy4.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.qingruihappy4.dao.UserDaoc; import com.qingruihappy4.service.LogService; import com.qingruihappy4.service.UserService; //user 服務層 @Service public class UserServiceImplc implements UserService { @Autowired private UserDaoc userDao; @Autowired private LogService logService; @Transactional public void add() { // 呼叫介面的時候 介面失敗 需要回滾,但是日誌記錄不需要回滾。 logService.addLog(); // 後面程式發生錯誤,不能影響到我的回滾### 正常當addLog方法執行完畢,就應該提交事務 userDao.add("test001", 20); int i = 1 / 0; System.out.println("################"); userDao.add("test002", 21); } // 方法執行完畢之後,才會提交事務 public void del() { System.out.println("del"); } }
package com.qingruihappy4; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.qingruihappy4.service.UserService; public class Test001 { public static void main(String[] args) { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml"); UserService userService = (UserService) applicationContext.getBean("userServiceImplc"); userService.add(); } }
關鍵就是上面標紅的程式碼。定義一個兩套事務互不影響的。