天天看点

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的隔离级别,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的