天天看點

Spring注解開發---AOP與事務1、AOP2、事務3、事務特性

文章目錄

  • 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:可在目标方法執行前後自定義通知行為;

切點表達式

Spring注解開發---AOP與事務1、AOP2、事務3、事務特性
圖檔摘自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的隔離級别,也是最慢的事務隔離級别,因為它通常是通過完全鎖定事務相關的資料庫表來實作的