文章目錄
- 1、AOP
-
- 1.1、開啟自動代理功能
- 1.2、定義切面類
- 1.3、定義切點
- 1.4、定義通知
- 2、事務
-
- 2.1、導入相關依賴
- 2.2、配置資料源,JdbcTemplate
- 2.3、給方法标注 @Transactional 注解
- 2.4、配置類上開啟事務管理 @EnableTransactionManagement
- 2.5、配置事務管理器來控制事務
- 2.6、事務失效
- 2.7、事務失效的原因
-
- 2.7.1、方法不是 public的:
- 2.7.2、資料源沒有配置事務管理器
- 2.7.3、異常被捕獲
- 2.7.4、異常類型錯誤或格式配置錯誤
- 2.7.5、自身調用問題
- 3、事務特性
-
- 3.1、propagation 屬性
- 3.2、Isolation 屬性
1、AOP
1.1、開啟自動代理功能
- 在java配置類上添加注解@EnableAspectJAutoProxy
@Configuration
@EnableAspectJAutoProxy
@ComponentScan(value = "com.xyulu"
,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})})
public class AopConfig {
}
1.2、定義切面類
@Component
@Aspect
public class LogAspects {
}
1.3、定義切點
@Pointcut(value = "execution(* com.xyulu..*(..))")
public void pointCut() {
}
1.4、定義通知
- @Before:在目标方法執行前執行通知;
- @After:在目标方法執行後執行通知;
- @AfterReturning:在目标方法執行完成後執行通知;
- @AfterThrowing:在目标方法抛出異常後執行通知;
- @Around:可在目标方法執行前後自定義通知行為;
切點表達式
圖檔摘自https://blog.csdn.net/longyanchen/article/details/94486678
代碼示例
public class LogAspect {
@Pointcut(value = "execution(* com.xyulu.service..*(..))")
public void pointcut() {
}
@Before(value = "pointcut()")
public void before(JoinPoint joinPoint) {
System.out.println("LogAspect.before,方法名:" + joinPoint.getSignature().getName() +
",參數清單:" + Arrays.toString(joinPoint.getArgs()));
}
@After(value = "pointcut()")
public void after(JoinPoint joinPoint) {
System.out.println("LogAspect.after,方法名:" + joinPoint.getSignature().getName());
}
@AfterReturning(value = "pointcut()", returning = "result")
public void afterReturn(JoinPoint joinPoint, Object result) {
System.out.println("LogAspect.afterReturn,方法名:" + joinPoint.getSignature().getName()
+ ",運作成功,傳回值:" + result);
}
@AfterThrowing(value = "pointcut()", throwing = "exception")
public void exception(JoinPoint joinPoint, Exception exception) {
System.out.println("LogAspect.exception,方法名:" + joinPoint.getSignature().getName()
+ "出錯,error is :" + exception);
}
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("LogAspect.around,環繞通知開始執行");
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
}
環繞通知和前置通知,後置通知有着很大的差別,主要有兩個重要的差別:
- 目标方法的調用由環繞通知決定,即你可以決定是否調用目标方法,而前置和後置通知 是不能決定的,他們隻是在方法的調用前後執行通知而已,即目标方法肯定是要執行的。
- 環繞通知可以控制傳回對象,即你可以傳回一個與目标對象完全不同的傳回值,雖然這很危險,但是你卻可以辦到。而後置方法是無法辦到的,因為他是在目标方法傳回值後調用。
使用面向切面來處理一些問公共的問題,比如,權限管理,事務的委托,當程式發生異常時,重複送出請求,重複的次數是可以設定的
2、事務
示例參考
2.1、導入相關依賴
2.2、配置資料源,JdbcTemplate
2.3、給方法标注 @Transactional 注解
@Transactional
public int saveUser(User user) {
int row = userDao.insertUser(user);
int i = 1/0;
return row;
}
2.4、配置類上開啟事務管理 @EnableTransactionManagement
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
@ComponentScan(value = "com.xyulu")
public class TxConfig {
@Bean
public DataSource dataSource() {
return DatasourceUtil.getDataSource();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
}
2.5、配置事務管理器來控制事務
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Configuration
@EnableAspectJAutoProxy
@EnableTransactionManagement
@ComponentScan(value = "com.xyulu")
public class TxConfig {
@Bean
public DataSource dataSource() {
return DatasourceUtil.getDataSource();
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
return jdbcTemplate;
}
@Bean
public PlatformTransactionManager platformTransactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
2.6、事務失效
實踐例子
1、資料庫沒有按照期望復原,同時出現非自定義的異常
org.springframework.aop.AopInvocationException: Null return value from advice does not match primitive return type for: public int com.xyulu.service.UserService.saveUser(com.xyulu.entity.User)
at org.springframework.aop.framework.CglibAopProxy.processReturnType(CglibAopProxy.java:395)
at org.springframework.aop.framework.CglibAopProxy.access$000(CglibAopProxy.java:85)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:694)
at com.xyulu.service.UserService$$EnhancerBySpringCGLIB$$78d36019.saveUser(<generated>)
at com.xyulu.TxTest.test(TxTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
、、、、、
- 此處是 int insertUser(User user);的傳回值 int 導緻的 無法将null類型轉換成int類型
2、将傳回值全部用Integer類型接收,重新執行
java.lang.NullPointerException
at com.xyulu.TxTest.test(TxTest.java:24)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:483)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at 、、、、tRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:221)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
- 任然報錯
3、調試發現是AOP的環繞通知報錯。
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
System.out.println("LogAspect.around,環繞通知開始執行");
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
System.out.println("LogAspect.around,環繞通知執行結束");
return result;
}
- 發現執行 joinPoint.proceed(); 出現異常,但是被代碼捕獲了,導緻事務無法復原
4、修改異常捕獲為抛出
@Around(value = "pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("LogAspect.around,環繞通知開始執行");
Object result = null;
result = joinPoint.proceed();
System.out.println("LogAspect.around,環繞通知執行結束");
return result;
}
- 執行程式,正常復原
2.7、事務失效的原因
查找了幾種事務失效的情況
2.7.1、方法不是 public的:
- spring的事務注解@Transactional隻能放在public修飾的方法上才起作用,如果放在其他非public方法上,雖然不報錯,但是事務不起作用
2.7.2、資料源沒有配置事務管理器
2.7.3、異常被捕獲
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order;
}catch (Exception e){
//do something;
}
}
}
- 如果在加有事務的方法内,使用了try…catch…語句塊對異常進行了捕獲,而catch語句塊沒有throw new RuntimeExecption異常,事務也不會復原
2.7.4、異常類型錯誤或格式配置錯誤
- 預設復原的是:RuntimeException,如果你想觸發其他異常的復原,需要在注解上配置一下
2.7.5、自身調用問題
方法a 和方法b, 然後方法b上面用 @Transactional加了方法級别的事務,在方法a裡面 調用了方法b, 方法b裡面的事務不會生效。
- 原因是在同一個類之中,方法互相調用,切面無效 ,而不僅僅是事務。這裡事務之是以無效,是因為spring的事務是通過aop實作的。
3、事務特性
屬性 | 類型 | 描述 |
---|---|---|
value | String | 指定使用的事務管理器 |
propagation | Propagation【enum】 | 事務傳播行為設定 |
isolation | Isolation【enum】 | 事務隔離級别設定 |
timeout | int | 事務逾時時間設定 |
readOnly | boolean | 讀寫或者隻讀,預設隻讀 |
rollbackFor | Class<? extends Throwable>[] | 導緻事務復原的異常類數組 |
rollbackForClassName | String[] | 導緻事務復原的異常類名數組 |
noRollbackFor | Class<? extends Throwable>[] | 不會導緻事務復原的異常類數組 |
noRollbackForClassName | String[] | 不會導緻事務復原的異常類名數組 |
3.1、propagation 屬性
事務的傳播行為,預設值為 Propagation.REQUIRED。
可選的值有:
- Propagation.REQUIRED:如果目前存在事務,則加入該事務,如果目前不存在事務,則建立一個新的事務。
- Propagation.SUPPORTS:如果目前存在事務,則加入該事務;如果目前不存在事務,則以非事務的方式繼續運作。
- Propagation.MANDATORY:如果目前存在事務,則加入該事務;如果目前不存在事務,則抛出異常。
- Propagation.REQUIRES_NEW:重新建立一個新的事務,如果目前存在事務,暫停目前的事務。
- Propagation.NOT_SUPPORTED:以非事務的方式運作,如果目前存在事務,暫停目前的事務。
- Propagation.NEVER:以非事務的方式運作,如果目前存在事務,則抛出異常。
- Propagation.NESTED:和 Propagation.REQUIRED 效果一樣。
3.2、Isolation 屬性
隔離級别 | 含義 | 髒讀 | 不可重複讀 | 幻讀 |
---|---|---|---|---|
DEFAULT | 使用後端資料庫預設的隔離級别 | |||
READ_UNCOMMITTED | 允許讀取尚未送出的資料變更(最低的隔離級别) | 是 | 是 | 是 |
READ_COMMITTED | 允許讀取并發事務已經送出的資料 | 否 | 是 | 是 |
REPEATABLE_READ | 對同一字段的多次讀取結果都是一緻的,除非資料是被本身事務自己所修改 | 否 | 否 | 是 |
SERIALIZABLE | 最高的隔離級别,完全服從ACID的隔離級别,也是最慢的事務隔離級别,因為它通常是通過完全鎖定事務相關的資料庫表來實作的 | 否 | 否 | 否 |