Spring(五)AOP、事务

文章目录

    • 一、AOP
      • 1.1 初识AOP
        • 1.1.1 AOP是什么
        • 1.1.2 怎么理解面向切面编程
      • 1.2 AOP术语
      • 1.3 两种代理
        • 1.3.1 JDK动态代理和CGLIB动态代理的使用选择
        • 1.3.2 JDK动态代理
        • 1.3.3 CGLIB动态代理
        • 1.3.4 JDK动态代理和CGLIB动态代理的区别
      • 1.4 Spring在运行时通知对象
      • 1.5 Spring通知类型
      • 1.6 多个切面类的执行顺序
      • 1.7 使用注解实现AOP示例
      • 1.8 AOP常用的几个注解
    • 二、Spring事务
      • 2.1 Spring事务的实现方式和实现原理
      • 2.2 Spring的事务传播行为
        • 2.2.1 事务传播行为分类
        • 2.2.2 使用注解的方式进行事务配置
      • 2.3 声明式事务和编程式事务
      • 2.4 Spring事务回滚
      • 2.5 @Transactional的使用
        • 2.5.1 常用参数
        • 2.5.2 使用@Transactional的注意事项
      • 2.6 事务的嵌套失效
    • 三、Spring常用注解
      • 3.1 注解分类
      • 3.2 一些常用注解

本系列文章:
  Spring(一)IOC、DI、@Autowired、@Resource、作用域
  Spring(二)IOC容器的初始化流程
  Spring(三)IOC容器的依赖注入流程
  Spring(四)IOC容器的高级特性
  Spring(五)AOP、事务
  Spring(六)Spring MVC
  Spring(七)SpringBoot
  Spring(八)Spring Cloud

一、AOP

  AOP本质:在不改变原有业务逻辑的情况下增强横切逻辑,横切逻辑代码往往是权限校验代码、日志代码、事务控制代码、性能监控代码。

1.1 初识AOP

1.1.1 AOP是什么

  AOP: Aspect oriented Programming,面向切面编程。
  AOP是OOP的延续(或完善)
  OOP的特征:封装、继承和多态。OOP是一种垂直继承体系:
Spring(五)AOP、事务_第1张图片
  OOP编程思想可以解决大多数的代码重复问题,但是有一些情况是处理不了的。比如权限校验、日志追踪等非业务性代码会多次重复出现。这种代码可以简单理解为横切逻辑代码
Spring(五)AOP、事务_第2张图片
  横切逻辑代码存在的问题:

  1. 横切代码重复问题。
  2. 横切逻辑代码和业务代码混杂在一起,代码臃肿,维护不方便。

  AOP的做法,是将横切逻辑代码和业务逻辑代码分离:
Spring(五)AOP、事务_第3张图片

  因此AOP解决的问题是:在不改变原有业务逻辑情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。

1.1.2 怎么理解面向切面编程

  :指的是横切逻辑,原有业务逻辑代码我们不能动,只能操作横切逻辑代码,所以面向横切逻辑。
  :横切逻辑代码往往要影响的是很多个方法,每个方法都如同一个点,多个点构成面。
  AOP,一般称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块。这个模块被命名为“切面”(Aspect),这样就减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
  AOP可用于日志管理、权限认证、安全检查、事务控制等。

1.2 AOP术语

  • 1、切面(Aspect)------>通知和切入点的结合
      通知和切入点共同定义了切面的全部内容【AOP核心1:可以简单理解为要实现切面功能的那个类】
  • 2、连接点(Join point)
      指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
  • 3、切入点(Pointcut)------>添加公共功能的地方/方法
      切点的定义会匹配通知所要织入的一个或多个连接点【AOP核心3:定义一个方法,和原有业务代码中需要实现AOP功能的相应方法关联起来】。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
  • 4、通知(Advice)------>要添加的公共功能
      在AOP术语中,切面的工作被称为通知【AOP核心2:需要在连接点实现的具体功能,有五种通知类型】
  • 5、目标对象(Target)------>被添加公共功能的原始对象
       被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
  • 6、引入(Introduction)
      在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段。
  • 7、织入(Weaving)------>在目标对象上使用切面
      织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:
  1. 编译期
      切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
  2. 类加载期
      切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
  3. 运行期
      切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。SpringAOP就是以这种方式织入切面。

1.3 两种代理

  AOP实现的关键在于代理模式,AOP代理主要分为静态代理和动态代理。
  AspectJ静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
  Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
  Spring提供了两种方式来生成代理对象: JDK动态代理和Cglib动态代理。

  • 1、Java动态代理
      利用反射机制生成一个实现代理接口的匿名类`,在调用具体方法前调用InvokeHandler来处理。
  • 2、Cglib动态代理
      是利用asm开源包,对代理对象类的class文件加载进来,通过修改其(代理对象类的class文件)字节码生成子类来处理。

1.3.1 JDK动态代理和CGLIB动态代理的使用选择

  1. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP;
  2. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP;
      如何强制使用CGLIB实现AOP?
  1. 添加CGLIB库;
  2. 在Spring配置文件中加入
  1. 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

1.3.2 JDK动态代理

  JDK动态代理主要涉及到java.lang.reflect包中的两个类:Proxy(Proxy.newProxyInstance()方法)和InvocationHandler。
  InvocationHandler是一个接口,通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编制在一起。
  Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。
  将被代理类作为构造函数的参数传入代理类proxy,调用Proxy.newProxyInsatnce(classloader,interfaces,handler)方法生成代理类,实现Invocation接口,重写invoke()方法,调用被代理类方法时默认调用此方法。
  总结: 代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。

1.3.3 CGLIB动态代理

  CGLib(Code Generation Library),是一个强大的高性能,高质量的代码生成类库,可以在运行期扩展Java类与实现Java接口,CGLib封装了asm,可以再运行期动态生成新的class。和JDK动态代理相比较:JDK 创建代理有一个限制,就是只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,则可以通过CGLib创建动态代理。
  生成对象类型为Enhancer。实现原理类似于JDK动态代理,只是他在运行期间生成的代理对象是针对目标类扩展的子类。

1.3.4 JDK动态代理和CGLIB动态代理的区别

  • 1、 JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  • 2、CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法因为是继承,所以该类或方法最好不要声明成final ;
  • 3、两者的性能差别:在早期的时候,JDK动态代理性能弱于CGLIB动态代理,随着JDK的更新,两者性能以及差别不大(JDK1.6和JDK1.7的时候,CGLib更快;1.8的时候,JDK动态代理更快)。
  • 4、CGLib在创建对象的时候所花费的时间却比JDK动态代理多。
  • 5、单例的代理对象或者具有实例池的代理,因为无需频繁的创建代理对象,所以比较适合采用CGLib动态代理,反之,则适合用JDK动态代理。
  • 6、JDK生成的代理类类型是Proxy(因为继承的是Proxy),CGLIB生成的代理类类型是Enhancer类型。
  • 7、如果要被代理的对象是个实现类,那么Spring会默认使用JDK动态代理来完成操作;如果要被代理的对象不是实现类,那么Spring会强制使用CGLib来实现动态代理。

1.4 Spring在运行时通知对象

  通过在代理类中包裹切面,Spring在运行期把切面织入到Spring管理的Bean中。代理封装了目标类,并拦截被通知方法的调用,再把调用转发给真正的目标bean。当代理拦截到方法调用时,在调用目标Bean方法之前,会执行切面逻辑
  直到应用需要被代理的bean时,Spring才创建代理对象。如果使用的是ApplicationContext的话,在ApplicationContext从BeanFactory中加载所有Bean的时候,Spring才会创建被代理的对象。因为Spring运行时才创建代理对象,所以我们不需要特殊的编译器来织入Spring AOP的切面。

1.5 Spring通知类型

  在AOP术语中,切面的工作被称为通知,实际上是程序执行时要通过Spring AOP框架触发的代码段。
  Spring切面可以应用5种类型的通知:

  • 1、前置通知(Before advice)
     在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
  • 2、返回后通知(After returning advice)
     在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
  • 3、抛出异常后通知(After throwing advice)
     在方法抛出异常退出时执行的通知。
  • 4、后通知(After (finally) advice)
     当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
  • 5、环绕通知(Around Advice)
     包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。

  五种通知的常见使用场景:

前置通知---->记录日志(方法将被调用)
环绕通知---->控制事务、权限控制
后置通知---->记录日志(方法已经成功调用)
异常通知---->异常处理、控制事务
最终通知---->记录日志(方法已经调用,但不一定成功)

  环绕通知的执行顺序是优于普通通知的
  同一个Aspect,不同advice的执行顺序:

  • 1、没有异常情况下的执行顺序

around before advice
before advice
目标方法执行
around after advice
after advice
afterReturning

  如果出现异常的时候,在环绕通知中解决了,那么普通通知是接受不到的。如果想让普通通知接收到,需要在环绕通知中进行抛出异常。

  • 2、有异常情况下的执行顺序

around before advice
before advice
目标方法执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生

1.6 多个切面类的执行顺序

  按照切面类的名称的首字母进行排序操作,按照字典序。比如有两个切面类:LogUtil、SecurityUtil,就是LogUtil先执行。
  如果需要人为地规定顺序,可以在切面类上添加@Order注解同时可以添加具体的值,值越小,越优先。示例:

	@Aspect
	@Component
	@Order(100)
	public class SecurityUtil {
	}

	@Aspect
	@Component
	@Order(200)
	public class LogUtil {
	}

  此时就是SecurityUtil切面先执行。

1.7 使用注解实现AOP示例

  定义注解实现的AnnotationPointCut增强类:

@Aspect //标注这个类是一个切面
public class AnnotationPointCut {

  @Before("execution(* com.spring.test.UserServiceImpl.*(..))")
  public void before(){
      System.out.println("====方法执行前====");
  }

  @After("execution(* com.spring.test.UserServiceImpl.*(..))")
  public void after(){
      System.out.println("====方法执行后====");
  }

  //在环绕增强中,我们可以给定一个参数,代表我们要获取处理切入的点;
  @Around("execution(* com.spring.test.UserServiceImpl.*(..))")
  public void around(ProceedingJoinPoint jp) throws Throwable{
      System.out.println("环绕前");

      Signature signature = jp.getSignature();// 获得签名
      System.out.println("signature:"+signature);

      Object proceed = jp.proceed(); //执行方法

      System.out.println("环绕后");

      System.out.println(proceed);
  }
}

  配置文件中增加对注解的配置:

    
    <bean id="userService" class="com.spring.test.UserServiceImpl"/>
    <bean id="beforeLog" class="com.spring.test.BeforeLog"/>
    <bean id="afterLog" class="com.spring.test.AfterLog"/>

    
    <bean id="annotationPointCut" class="com.spring.test.AnnotationPointCut"/>
    
    <aop:aspectj-autoproxy/>

  测试结果:

环绕前
signature:void com.spring.test.UserService.add()
方法执行前
增加了一个用户!
环绕后
null
方法执行后

1.8 AOP常用的几个注解

  • 1、@Pointcut
      定义切入点,常见的方式:采用表达式批量添加切入点,如下方法,表示AopController下的所有public方法都添加该切面,示例:
	@Pointcut(value = "execution(public * com.train.aop.controller.AopController.*(..))")
	public void pointCut(){
	
	}

  以execution(* com.itcodai.course09.controller..*.*(..)))表达式为例,语法如下:

execution() :表达式主体;
第一个 * 号的位置:表示返回值类型, * 表示所有类型;
包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包, com.itcodai.course09.controller 包、子包下所有类的方法;
第二个 * 号的位置:表示类名, * 表示所有类;
*(..) :这个星号表示方法名, * 表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数。

  • 2、@Before
      前置方法,在目标方法执行前触发。@Before注解指定的方法在切面切入目标方法之前执行,可以做一些log处理,也可以做一些信息的统计。
  • 3、@After
      后置方法,与@Before相反,在目标方法执行完毕后执行,也可以做一些完成某方法之后的log处理。
  • 4、@AfterReturning
      后置通知,在将返回值返回时执行。@AfterReturning注解和@After有些类似,区别在于@AfterReturning注解可以用来捕获切入方法执行完之后的返回值,对返回值进行业务逻辑上的增强处理。
  • 5、@AfterThrowing
      @AfterThrowing注解是当被切方法执行时抛出异常时,会进@AfterThrowing 注解的方法中执行,在该方法中可以做一些异常的处理逻辑。要注意的是throwing属性的值必须要和参数一致,否则会报错。该方法中的第二个入参即为抛出的异常。示例:
	@AfterThrowing(pointcut = "pointCut()", throwing = "ex")
	public void afterThrowing(JoinPoint joinPoint, Throwable ex)
  • 6、@Around
      环绕通知,可以说是使用最频繁的方式,会将整个方法包围起来。
  • 7、@EnableAspectJAutoProxy
      该注解表示开启AOP代理自动配置。

二、Spring事务

  事务管理一般在Service层。如果在dao层,回滚的时候只能回滚到当前方法,但一般我们的service层的方法都是由很多dao层的方法组成的。同时,如果在dao层,commit的次数会过多。

2.1 Spring事务的实现方式和实现原理

  Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。Spring事务是在代码层面利用AOP实现,执行事务的时候使用TransactionInceptor进行拦截,然后处理。本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。
  开启事务开关后,在代码里就可以加入@Transactional注解,就可以使用事务,这也是事务的一般使用方式。
  如果一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性
  如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
  对于只读查询,可以指定事务类型为readonly,即只读事务。由于只读事务不存在数据的修改,因此数据库将会为只读事务提供一些优化手段。示例:

	@Transactional(readOnly = true)

  运行时异常默认回滚,编译时异常默认不回滚,可以手动指定哪些异常不会滚:

	@Transactional(noRollbackFor = {ArithmeticException.class,NullPointerException.class})

  设置哪些异常回滚:

	@Transactional(rollbackFor = {FileNotFoundException.class})

2.2 Spring的事务传播行为

2.2.1 事务传播行为分类

  spring事务的传播行为说的是,当多个事务同时存在(当一个事务方法被另一个事务方法调用)的时候,Spring如何处理这些事务的行为。 事务的传播行为,默认值为 Propagation.REQUIRED。其七个分类:

事务传播行为类型 说明
REQUIRED (重要) 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
REQUIRES_NEW(重要) 当前的方法必须启动新事务,并且在它自己的事务内运行,如果有事务正在运行,应该将它挂起。
SUPPORTS 如果有事务在运行,当前的方法就在这个事务内运行,否则它可以不运行在事务中
NOT_SUPPORTED 当前方法不应该运行在事务中,如果当前存在事务,就把当前事务挂起。
NEVER 当前方法不应该运行在事务中,如果当前存在事务,则抛出异常。
MANDATORY 当前的方法必须运行在事务内部,如果当前没有事务,就抛出异常。
NESTED(重要) 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则就启动一个新事物,并在它自己的事务内运行

  使用示例:

	@Transactional(propagation = Propagation.REQUIRED)

  展开来说:

  • 1、REQUIRED
      假如当前正要运行的事务不在另外一个事务里,那么就起一个新的事务 比方说,ServiceB.methodB的事务级别定义PROPAGATION_REQUIRED, 那么因为执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务。这时调用ServiceB.methodB,ServiceB.methodB看到自己已经执行在ServiceA.methodA的事务内部。就不再起新的事务。
      而假如ServiceA.methodA执行的时候发现自己没有在事务中,他就会为自己分配一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的不论什么地方出现异常。事务都会被回滚。即使ServiceB.methodB的事务已经被提交,可是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚。
  • 2、REQUIRES_NEW
      假如ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW。那么当运行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起。ServiceB.methodB会起一个新的事务。等待ServiceB.methodB的事务完毕以后,ServiceA.methodA才继续运行。
      他与PROPAGATION_REQUIRED 的事务差别在于事务的回滚程度了。由于ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。假设ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚。ServiceB.methodB是不会回滚的。假设ServiceB.methodB失败回滚,假设他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
  • 3、SUPPORTS
      假设当前在事务中,就以事务的形式执行。假设当前不在一个事务中,那么就以非事务的形式执行。
  • 4、MANDATORY
      必须在一个事务中执行。也就是说,他仅仅能被一个父事务调用。否则,他就要抛出异常。
  • 5、NOT_SUPPORTED
      当前不支持事务。比方ServiceA.methodA的事务级别是PROPAGATION_REQUIRED 。而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,那么当执行到ServiceB.methodB时。ServiceA.methodA的事务挂起。而他以非事务的状态执行完,再继续ServiceA.methodA的事务。
  • 6、NEVER
      不能在事务中执行。
      如果ServiceA.methodA的事务级别是PROPAGATION_REQUIRED。 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,那么ServiceB.methodB就要抛出异常了。
  • 7、NESTED
      如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
      PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务. 嵌套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint.嵌套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.
      由此可见, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back。

2.2.2 使用注解的方式进行事务配置

	<!--开启注解的方式--> 
	<tx:annotation-driven transaction-manager="transactioManager" />

  Java代码示例:

	//如果有事务, 那么加入事务, 没有的话新建一个(默认情况)
	@Transactional(propagation=Propagation.REQUIRED)
	
	//容器不为这个方法开启事务
	@Transactional(propagation=Propagation.NOT_SUPPORTED)

	//不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
	@Transactional(propagation=Propagation.REQUIRES_NEW)

	//必须在一个已有的事务中执行,否则抛出异常
	@Transactional(propagation=Propagation.MANDATORY)
	
	//必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
	@Transactional(propagation=Propagation.NEVER)

	//如果其他bean调用这个方法,在其他bean中声明事务,那就用事务;
	//如果其他bean没有声明事务,那就不用事务
	@Transactional(propagation=Propagation.SUPPORTS)

2.3 声明式事务和编程式事务

  Spring事务在具体使用时,可以分为两类:编程式事务和声明式事务。

  • 1、声明式事务
      大多数Spring框架的用户选择声明式事务管理,因为声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理少了一点灵活性。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

  声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

  • 2、编程式事务
      编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,Spring推荐使用TransactionTemplate。

2.4 Spring事务回滚

  如果捕获了异常,还想让事务生效,示例:

	@Transactional
	public void createUserRight1(String name) {
	    try {
	        userRepository.save(new UserEntity(name));
	        throw new RuntimeException("error");
	    } catch (Exception ex) {
	        log.error("create user failed", ex);
	        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	    }
	}

   Spring Boot默认的事务规则是遇到运行异常(RuntimeException)和程序错误(Error)才会回滚。比如代码中抛出RuntimeException 就没有问题,但是抛出SQLException就无法回滚。针对非运行时异常,如果要进行事务回滚的话,可以在@Transactional注解中使用rollbackFor 属性来指定异常,比如 @Transactional(rollbackFor =Exception.class),这样就没有问题了,所以在实际项目中,一定要指定异常。

2.5 @Transactional的使用

2.5.1 常用参数

  • 1、传播行为参数
      @Transactional(propagation=Propagation.REQUIRED) :如果有事务, 那么加入事务, 没有的话新建一个(默认情况下);
      @Transactional(propagation=Propagation.NOT_SUPPORTED) :容器不为这个方法开启事务
      @Transactional(propagation=Propagation.REQUIRES_NEW) :不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
      @Transactional(propagation=Propagation.MANDATORY) :必须在一个已有的事务中执行,否则抛出异常
      @Transactional(propagation=Propagation.NEVER) :必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
      @Transactional(propagation=Propagation.SUPPORTS) :如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
  • 2、超时参数
      @Transactional(timeout=30) ,超时时间默认是30秒。
  • 3、隔离级别参数
      @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读, 不可重复读) 基本不使用;
      @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读);
      @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读);
      @Transactional(isolation = Isolation.SERIALIZABLE):串行化。
      MYSQL: 默认为REPEATABLE_READ级别。
      SQLSERVER: 默认为READ_COMMITTED。
  • 4、常用参数汇总
参数名称 功能描述
readOnly 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false。例如:@Transactional(readOnly=true)
rollbackFor 该属性用于设置需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,则进行事务回滚。例如:
指定单一异常类:@Transactional(rollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(rollbackFor={RuntimeException.class, Exception.class})
rollbackForClassName 该属性用于设置需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,则进行事务回滚。例如:
指定单一异常类名称:@Transactional(rollbackForClassName=“RuntimeException”)
指定多个异常类名称:@Transactional(rollbackForClassName={“RuntimeException”,“Exception”})
noRollbackFor 该属性用于设置不需要进行回滚的异常类数组,当方法中抛出指定异常数组中的异常时,不进行事务回滚。例如:
指定单一异常类:@Transactional(noRollbackFor=RuntimeException.class)
指定多个异常类:@Transactional(noRollbackFor={RuntimeException.class, Exception.class})
noRollbackForClassName 该属性用于设置不需要进行回滚的异常类名称数组,当方法中抛出指定异常名称数组中的异常时,不进行事务回滚。例如:
指定单一异常类名称:@Transactional(noRollbackForClassName=“RuntimeException”)
指定多个异常类名称:@Transactional(noRollbackForClassName={“RuntimeException”,“Exception”})
propagation 该属性用于设置事务的传播行为,例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
isolation 该属性用于设置底层数据库的事务隔离级别,事务隔离级别用于处理多事务并发的情况,通常使用数据库的默认隔离级别即可,基本不需要进行设置
timeout 该属性用于设置事务的超时秒数,默认值为-1表示永不超时

2.5.2 使用@Transactional的注意事项

  • 1、@Transactional 只能被应用到public方法上
      对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能。
  • 2、@Transactional被用在具体的类上时才会生效
      @Transactional注解的出现不足于开启事务行为,它仅仅是一种元数据,能够被可以识别@Transactional注解和上述的配置适当的具有事务行为的beans所使用。
      Spring团队的建议是你在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。注解是不能继承的,这就意味着如果你正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。

2.6 事务的嵌套失效

  嵌套是子事务套在父事务中执行,子事务是父事务的一部分,在进入子事务之前,父事务建立一个回滚点,叫save point,然后执行子事务,这个子事务的执行也算是父事务的一部分,然后子事务执行结束,父事务继续执行。重点就在于save point。
  如果子事务回滚,父事务会回滚到进入子事务前建立的save point,然后尝试其他的事务或者其他的业务逻辑,父事务之前的操作不会受到影响,更不会自动回滚。
  如果父事务回滚,父事务回滚,子事务也会跟着回滚。为什么呢,因为父事务结束之前,子事务是不会提交的。
  事务的提交:子事务是父事务的一部分,由父事务统一提交。

三、Spring常用注解

3.1 注解分类

  • 1、声明Bean的注解
      @Component:组件,没有明确的角色。
      @Service:在业务逻辑层使用。
      @Repository :在数据访问层使用。
      @Controller:在展现层使用,控制层的声明。
      @RestController :@Controller和@ResponseBody组合。
  • 2、注入Bean的注解
      @Autowired:Spring自带的注解,用来自动装配Bean。
      @Resource:Java自带的注解,用来自动装配Bean。
  • 3、 配置类相关注解
      @Configuration :声明当前类为配置类,相当于xml形式的Spring配置(类上),声明当前类为配置类,其中内部组合了@Component注解,表明这个类是一个Bean(类上)。
      @Bean:注解在方法上,声明当前方法的返回值为一个Bean,替代xml中的方式(方法上)。
      @ComponentScan :声明扫描注解的范围 。
  • 4、 AOP相关注解
      @Aspect:声明一个切面(类上),可直接将拦截规则(切点)作为参数。
      @After :在方法执行之后执行(方法上)。
      @Before :在方法执行之前执行(方法上)。
      @Around :在方法执行之前与之后执行(方法上)。
      @PointCut :声明切点在Java配置类中使用@EnableAspectJAutoProxy注解,开启Spring对AspectJ代理的支持(类上)。
  • 5、 注入属性注解
      @Value:为属性注入值。
  • 6、 异步相关注解
      @EnableAsync:配置类中,通过此注解开启对异步任务的支持。
      @Async:在实际执行的 bean 方法使用该注解来申明其是一个异步任务(方法上或类上所有的方法都将异步,需要@EnableAsync 开启异步任务)。
  • 7、 定时任务相关注解
      @EnableScheduling :在配置类上使用,开启计划任务的支持(类上)。
      @Scheduled :来申明这是一个任务。
  • 8、 开关相关注解
      这些注解主要用来开启对xxx的支持。
      @EnableAspectJAutoProxy :开启对AspectJ自动代理的支持。
      @EnableAsync :开启异步方法的支持。
      @EnableScheduling :开启计划任务的支持。
      @EnableWebMvc :开启MVC的配置支持。
      @EnableConfigurationProperties :开启对@ConfigurationProperties注解配置Bean的支持。
      @EnableJpaRepositories :开启对SpringData JPA Repository的支持。
      @EnableTransactionManagement :开启注解式事务的支持。
      @EnableCaching :开启注解式的缓存支持。
  • 9、 MVC相关注解
      @EnableWebMvc:在配置类中开启Web MVC的配置支持,如一些 ViewResolver 或者 MessageConverter 等,若无此句,重写WebMvcConfigurerAdapter 方法(用于对Spring MVC 的配置)。
      @Controller:声明该类为SpringMVC中的Controller。
      @RequestMapping:用于映射Web请求,包括访问路径和参数(类或方法上)。
      @ResponseBody:支持将返回值放在response内,而不是一个页面,通常用户返回json 数据(返回值旁或方法上)。
      @PathVariable:用于接收路径参数,比如@RequestMapping(“/hello/{name}”)申明的路径,将注解放在参数中前,即可获取该值,通常作为 Restful 的接口实现方法。
      @RestController :该注解为一个组合注解,相当于@Controller和@ResponseBody的组合,注解在类上,意味着,该Controller的所有方法都默认加上了@ResponseBody。
  • 10、 异常处理相关注解
      @ControllerAdvice :用于处理全局异常。
      @ExceptionHandler :用于更细化的异常处理。

3.2 一些常用注解

  • 1、@Controller
      此注解使用在class上声明此类是一个控制器,说明该类是控制类,Controller主要负责处理前端控制器(DispatcherServlet )发过来的请求,经过业务逻辑层处理之后封装层一个model,并将其返回给view进行展示。
      在Spring MVC中提供了一个非常简便的定义Controller的方法,你无需继承特定的类或实现特定的接口,只需使用@Controller 标记一个类是Controller,然后使用@RequestMapping 和@RequestParam等一些注解用以定义URL请求和Controller方法之间的映射,这样的Controller就能被外界访问到。
      @Controller注解通常用于类上,如果结合Thymeleaf模板使用的话,会返回一个页面。使用该类需要在xml文件中配置注解扫描,代码示例:

    <context:component-scan base-package="com.controller"/>
  • 2、@RestController
      @RestController注解里面包含了@Controller注解和@ResponseBody注解,@ResponseBody 注解是将返回的数据结构转换 JSON格式,即:@RestController = @Controller + @ResponseBody
  • 3、@RequestMapping
      @RequestMapping 是一个用来处理请求地址映射的注解,它可以用于类上,也可以用于方法上。用于类上的注解会将一个特定请求或者请求模式映射到一个控制器之上,表示类中的所有响应请求的方法都是以该地址作为父路径;方法的级别上注解表示进一步指定到处理方法的映射关系。示例:
@Controller
public class IndexController {
    @RequestMapping("/index")
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        ModelAndView mav = new ModelAndView("index");
        mav.addObject("message", "Hello Spring MVC");
        return mav;
    }
}
  • 4、@RequestParam
      处理简单类型的绑定,用@RequestParam绑定HttpServletRequest请求参数到控制器方法参数,即在处理方法入参处使用该注解,可以把请求参数传递给请求方法。示例:
   @RequestMapping ( "requestParam" )
   public String testRequestParam( @RequestParam(required=false) String name, @RequestParam ( "age" ) int age) {
       return "requestParam" ;
   } 
  • 5、@PathVariable
      @PathVariable注解主要用来获取URL参数,Spring Boot支持Restful风格的URL,比如一个GET请求携带一个参数id,我们将id作为参数接收,可以使用@PathVariable注解。

你可能感兴趣的:(【Spring】,spring,java,aop)