SpringAOP以及事务管理和优化处理

Spring的AOP

本篇章中所有的代码都将会放置到git仓库中去,并且会做一个简要的说明。

一、个人理解描述

Spring中所谓的AOP就是在不修改源码的情况下,来进行增强。所谓的增强其实就是在方法执行前后添加一些额外操作。

所谓的增强,就是我们如何来对方法(以类中的方法为基本单位)处理。处理方法有五种:前置增强、后置增强等等

但是最为常用的还是利用环绕通知来进行增强,习惯于手动控制,更加精细化操作。

简单利用画图说明一下上面的描述:

在这里插入图片描述

以这里的CourseController和UserController为例,希望在CourseService和UserService类中的每个都开始事务操作,而且还不在修改CourseService和UserService类中源码的情况下来进行操作,不破坏原来的代码的完整性。

那么首先来写个简单的Demo来体验一下SpringAOP的强大之处。

二、案例演示

  • 希望只针对UserServiceImpl类中的save无参方法来做日志打印处理。

因为工作中需要,需要利用到注解+xml的方法,所以下面没有提供纯注解的方式来进行操作。

在UserService接口中提供了三个方法来模拟实际开发过程中接口中的方法:

public interface UserService {void save();void save(String name);void save(String name, Integer age);
}

UserServiceImpl是对接口UserService的实现:

@Service("userService")
public class UserServiceImpl implements UserService {@Overridepublic void save() {System.out.println("UserService.save()");try {int randomMs = (int) (Math.random()*1000);Thread.sleep(randomMs);} catch (InterruptedException e) {e.printStackTrace();}}@Overridepublic void save(String name) {System.out.println("UserService.save(String)");}@Overridepublic void save(String name, Integer age) {System.out.println("UserService.save(String, Integer)");}
}

上面两步操作中是最为简单的!下面就要来为我们的save无参方法来进行增强。

所谓的增强,就是调用save无参方法的时候,打印我们的日志。

那么来写一个增强类即可:

@Component
public class MyAdvice {/*** 环绕通知方法。调用者调用时,Spring会执行这个环绕通知方法* @param pjp 由Spring传递进来的切入点对象(目标方法、目标对象、方法实参等等封装成的对象)* @return*/public Object aroundMethod(ProceedingJoinPoint pjp){Object result = null;try {System.out.println("调用业务方法之前,我希望看下是否已经走了动态代理");long start = System.currentTimeMillis();//自己调用目标对象,得到返回值// 固定写法:调用业务方法-----所以上面的逻辑可以称之为在调用方法之前操作result = pjp.proceed(pjp.getArgs());// 下面的逻辑可以表示成调用业务逻辑方法之后的操作System.out.println("调用业务方法之后,我希望看下业务方法执行之后的结果");long end = System.currentTimeMillis();System.out.println("当前业务方法调用过程中花费的是时间有:" + (end - start) + "毫秒");} catch (Throwable throwable) {System.out.println("当前方法抛出异常,对应的异常是:"+throwable);} finally {System.out.println("无论业务方法执行过程中出现了怎样的问题,那么这行代码最终都要来执行");}// 返回最终结果return result;}
}

因为利用到的是注解+xml的方式,所以再写一个xml来配置扫描类和动态代理生成过程:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"><!--扫描组件--><context:component-scan base-package="com.guang.service.impl"/><context:component-scan base-package="com.guang.advice"/><!--一切AOP的配置,都要放在 aop:config标签里--><aop:config><aop:aspect ref="myAdvice"><aop:around method="aroundMethod" pointcut="execution(public void com.guang.service..UserServiceImpl.save())"/></aop:aspect></aop:config>
</beans>

那么来写一段代码测试:

public class XmlAopTest {@Testpublic void test(){ApplicationContext app = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");UserService userService = app.getBean("userService", UserService.class);userService.save();System.out.println("---------------------");userService.save("tom");System.out.println("---------------------");userService.save("tom", 20);}
}

打印结果如下所示:

调用业务方法之前,我希望看下是否已经走了动态代理
UserService.save()
调用业务方法之后,我希望看下业务方法执行之后的结果
当前业务方法调用过程中花费的是时间有:838毫秒
无论业务方法执行过程中出现了怎样的问题,那么这行代码最终都要来执行
---------------------
UserService.save(String)
---------------------
UserService.save(String, Integer)

从这里可以看到,尽管利用userService调用了三个无参方法,但是只有save无参方法进行了增强。那么到底是如何做到的呢?

下面我们先介绍概念,然后从概念来进行入手,着手分析,然后再进行分析。

三、AOP相关的概念

3.1、AOP相关概念
  • 目标对象(Target):要代理的/要增强的目标对象。

  • 代理对象(Proxy):目标对象被AOP织入增强后,就得到一个代理对象

  • 连接点(JoinPoint):能够被拦截到的点,在Spring里指的是方法

    目标类里,所有能够进行增强的方法,都是连接点

  • 切入点(PointCut):要对哪些连接点进行拦截的定义

    已经增强的连接点,叫切入点

  • 通知/增强(Advice):拦截到连接点之后要做的事情

    对目标对象的方法,进行功能增强的代码

  • 切面(Aspect):是切入点和通知的结合

  • 织入(Weaving):把增强/通知 应用到 目标对象来创建代理对象的过程。Spring采用动态代理技术织入,而AspectJ采用编译期织入和装载期织入

那么画图来描述一下,我觉得更为稳妥,如下所示:

在这里插入图片描述

以例子来进行说明:

  • 目标对象:就是courseservice所表示的单例对象;

  • 代理对象:就是需要对courseservice对应的单例对象来进行代理的对象;

  • 所谓的连接点:以上面的courseservice来举例,可以认为是courseservice类中的所有方法;

  • 所谓的切入点:就是我们需要筛选courseservice类中的个别方法来作为特殊的点(如何筛选,那么就需要我们手写point表达式来进行选择);

  • 增强:所谓的增强就是要对原来的方法来做何种操作;

  • 切面:切入点+增强;

3.2、AOP开发前要明确的事项
我们要做的事情:
  • 编写核心业务代码(Target目标类的目标方法)
  • 编写通知类,通知类中有通知方法(Advice增强功能方法)
  • 在配置文件中,配置织入关系,即将哪些通知与哪些切入点 结合,形成切面
Spring的AOP做的事情:
  • 生成动态代理的过程(把通知织入到切入点的过程),是由Spring来实现的
  • Spring会监控切入点方法的执行,一旦发现切入点方法执行,使用代理机制动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。
小结
  • AOP相关的概念/术语
    • 目标类Target:要对哪个类进行增强
    • 代理对象Proxy:对目标类增强后的那个代理对象
    • 连接点JoinPoint:目标类里可增强的方法
    • 切入点PointCut:要增强的方法
    • 通知Advice:功能增强的代码
    • 切面Aspect:切入点 + 通知
    • 织入Weaving:把切入点 和 通知 进行结合,生成代理对象的过程
  • 使用AOP,我们要做的事情:
    • 编写目标类,自己的业务代码
    • 编写通知类
    • 配置切面
  • 使用AOP,Spring做的事情
    • 根据我们配置的切面,进行织入生成代理对象

四、基于XML的AOP

快速入门
1) 需求描述
  • 有目标类UserServiceImpl,有通知类MyAdvice
  • 使用XML方式AOP,对目标类UserServiceImpl的方法进行增强
2) 步骤分析
  1. 创建maven项目,导入AOP相关的依赖坐标
  2. 创建目标类(要增强的类,内部有切入点),创建通知类(内部有增强的方法代码)
  3. 修改配置文件:
    1. 把目标类和通知类都配置成为bean对象
    2. 配置切入点和通知方法(增强方法)的织入关系:配置切面
  4. 测试代码
3) 入门实现
1. 创建maven项目,导入坐标
<dependencies><!--Spring上下文核心包--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><!--AOP的实现包--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version></dependency><!--Spring和单元测试集成--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version></dependency><!--单元测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>
</dependencies>
2. 创建目标类和通知类
  • 目标类:com.guang.aop.UserServiceImpl
public class UserService{void save();
}
public class UserServiceImpl {public void save(){System.out.println("UserServiceImpl.save......");}
}
  • 通知类:com.guang.aop.MyAdvice
public class MyAdvice {public void before(){System.out.println("前置通知...");}
}
3. 修改配置文件
  1. 把目标类和通知类都配置到Spring配置文件中
  2. 配置切入和通知方法(增强方法)的织入关系
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--配置要增强的目标对象--><bean id="userService" class="com.guang.service.impl.UserServiceImpl"/><!--配置切面对象--><bean id="myAdvice" class="com.guang.aop.MyAdvice"/><!--配置AOP--><aop:config><!--配置切面。切面:通知方法+切入点--><aop:aspect ref="myAdvice"><!--配置通知--><aop:before method="before" pointcut="execution(void com.guang.service.impl.UserServiceImpl.save())"/></aop:aspect></aop:config>
</beans>

注意:在xml中增加了aop的名称空间如下:

在这里插入图片描述

4. 测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {@Autowiredprivate UserService userService;@Testpublic void testQuickStart(){userService.save();}
}
4) 步骤小结
  1. 导入jar包:spring-context, aspectjweaver
  2. 编写目标类、编写通知类
  3. 配置切面
<aop:config><aop:aspect ref="通知对象"><aop:before method="通知对象里的通知方法" pointcut="切入点表达式"/></aop:aspect>
</aop:config>
AOP详解
1) 切点表达式的写法
语法:
execution([权限修饰符] 返回值类型 包名.类名.方法名(参数列表))
  • 修饰符:可以省略
  • 返回值类型:
    • 可以指定类型。比如String (如果类型有歧义,就写全限定类名,比如:java.util.Date
    • *,表示任意字符。比如Str*,或者*
  • 包名:
    • 可以写.:表示当前包下的类或者子包。比如com.guang.service
    • 可以写..:表示当前包里所有后代类、后代包。比如com..service
    • *:表示任意字符。比如:com.gua*, com.*
  • 类名:
    • 可以指定类名。比如:UserServiceImpl
    • * 表示任意字符。比如:*ServiceImpl*
  • 方法名:
    • 可以指定方法名
    • * 表示任意字符。比如:save**
  • 参数列表:
    • 可以指定类型。比如:String,Integer表示第一个参数是String,第二个参数是Integer类型
    • *表示任意字符。比如:
      • String, * 表示第一个参数是String,第二个参数是任意类型
      • Str*, Integer表示第一个参数类型Str开头,第二个参数是Integer类型
    • 可以使用..表示任意个数、任意类型的参数
示例
execution(public void com.guang.dao.impl.UserDao.save())
execution(void com.guang.dao.impl.UserDao.*(..))
execution(* com.guang.dao.impl.*.*(..))
execution(* com.guang.dao..*.*(..))
execution(* *..*.*(..)) --不建议使用
2) 通知的种类
通知的语法
<aop:通知类型 method="通知中的方法" pointcut="切点表达式"></aop:通知类型>
通知的类型
名称标签说明
前置通知<aop:before>通知方法在切入点方法之前执行
后置通知<aop:after-returning>在切入点方法正常执行之后,执行通知方法
异常通知<aop:after-throwing>在切入点方法抛出异常时,执行通知方法
最终通知<aop:after>无论切入点方法是否有异常,最终都执行通知方法
环绕通知<aop:around>通知方法在切入点方法之前、之后都执行
通知示例

注意:通知方法的名称随意,我们这里是为了方便理解,才起名称为:before, after等等

  • 前置通知

    • 通知方法定义MyAdvicebefore方法:
    public void before(){System.out.println("前置通知");
    }
    
    • xml配置
    <aop:before method="before" pointcut="execution(void com.guang.service..*.save())"/>
    
  • 后置通知

    • 通知方法定义
    public void afterReturning(){System.out.println("后置通知");
    }
    
    • xml配置
    <aop:after-returning method="afterReturning" pointcut="execution(void com.guang.service..*.save())"/>
    
  • 环绕通知

    • 通知方法定义
    /*** @param pjp ProceedingJoinPoint:正在执行的切入点方法对象* @return 切入点方法的返回值*/
    public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("环绕:前置通知...");Object[] args = pjp.getArgs();//切入点方法执行Object proceed = pjp.proceed(args);System.out.println("环绕:后置通知...");return proceed;
    }
    
    • xml配置
    <aop:around method="around" pointcut="execution(void com.guang.service..*.save())"/>
    
  • 异常抛出通知

    • 通知方法定义
    public void afterThrowing(){System.out.println("抛出异常通知");
    }
    
    • xml配置
    <aop:after-throwing method="afterThrowing" pointcut="execution(void com.guang.service..*.save())"/>
    
  • 最终通知

    • 通知方法定义
    public void after(){System.out.println("最终通知");
    }
    
    • xml配置
    <aop:after method="after" pointcut="execution(void com.guang.service..*.save())/>
    
3) 切点表达式的抽取
  • 当多个切面的切入点表达式相同时,可以将切入点表达式进行抽取;在增强中使用pointcut-ref代替pointcut,来引入切入点表达式。

  • 示例:

    <!--配置AOP-->
    <aop:config><!--配置切入点表达式--><aop:pointcut id="myPointCut" expression="execution(void com.guang.service..*.save())"/><!--配置切面--><aop:aspect ref="myAdvice"><!--前置通知--><aop:before method="before" pointcut-ref="myPointCut"/><!--后置通知--><aop:after-returning method="afterReturning" pointcut-ref="myPointCut"/><!--异常通知--><aop:after-throwing method="afterThrowing" pointcut-ref="myPointCut"/><!--最终通知--><aop:after method="after" pointcut-ref="myPointCut"/></aop:aspect>
    </aop:config>
    
4) 小结
  • 需要我们编写的内容:
    • 编写目标类,编写通知类
    • 配置切面
<aop:config><aop:pointcut id="xxx" expression="切入点表达式"/><aop:aspect ref="通知对象"><aop:before method="通知对象里的通知方法" pointcut-ref="xxx"/><aop:after-returning method="通知对象里的通知方法" pointcut-ref="xxx"/><aop:after-throwing method="通知对象里的通知方法" pointcut-ref="xxx"/><aop:after method="通知对象里的通知方法" pointcut-ref="xxx"/><aop:around method="通知对象里的通知方法" pointcut-ref="xxx"/></aop:aspect>
</aop:config>
  • 注意环绕通知的方法
public Object aroundMethod(ProceedingJoinPoint pjp){Object reuslt = null;try{//写前置通知代码//调用目标对象的方法result = pjp.proceed(pjp.getArgs());//写后置通知代码}catch(Throwable t){//写异常通知代码}finally{//写最终通知代码}
}

五、基于注解的AOP

快速入门
1) 需求描述
  • 有目标类UserServiceImpl,有通知类MyAdvice
  • 使用注解方式的AOP对目标类UserServiceImpl的方法进行增强
2) 步骤分析
  1. 创建maven项目,导入AOP需要的依赖坐标
  2. 创建目标类,创建通知类
    1. 使用注解@Component标注两个类,配置成为bean对象
    2. 在通知类中,使用注解配置织入关系
  3. 在配置文件中,开启组件扫描和AOP的自动代理(自动装配)
  4. 测试
3) 入门实现
1. 创建maven项目,导入坐标
  • 注意:需要增加AOP的实现包:aspectjweaver
<dependencies><!--Spring上下文核心包--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><!--AOP的实现包--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version></dependency><!--Spring和单元测试集成--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version></dependency><!--单元测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency>
</dependencies>
2. 创建目标类,创建通知类
  1. 使用注解标注两个类,配置成为bean对象

    • 实际开发中,使用@Repository, @Service, @Controller注解,按照分层进行配置
  2. 在通知类中,使用注解配置织入关系

    • 目标类com.guang.aop.Target
    public class UserService{void save();
    }
    
    @Service("userService")
    public class UserServiceImpl {public void save(){System.out.println("UserServiceImpl.save......");}
    }
    
    • 通知类com.guang.aop.MyAdvice
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;//声明当前类是切面类:把切入点和通知,在这个类里进行织入,当前类就成为了一个切面类
    @Aspect 
    @Component("myAdvice")
    public class MyAdvice {@Before("execution(void com.guang.impl..*.save())")public void before(){System.out.println("前置通知...");}@AfterReturning("execution(void com.guang.impl..*.save()))")public void afterReturning(){System.out.println("后置通知");}@After("execution(void com.guang.impl..*.save())")public void after(){System.out.println("最终通知");}@AfterThrowing("execution(void com.guang.impl..*.save())")public void afterThrowing(){System.out.println("抛出异常通知");}/*** @param pjp ProceedingJoinPoint:正在执行的切入点方法对象* @return 切入点方法的返回值*/@Around("execution(void com.guang.impl..*.save())")public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("环绕:前置通知...");//切入点方法执行Object proceed = pjp.proceed();System.out.println("环绕:后置通知...");return proceed;}
    }
    
4. 开启组件扫描和AOP自动代理
  • applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--开启组件扫描--><context:component-scan base-package="com.guang"/><!--开启AOP自动代理--><aop:aspectj-autoproxy/>
</beans>

如果要使用纯注解开发,可以使用配置类代替applicationContext.xml,配置类如下:

@Configuration //标记当前类是:配置类
@ComponentScan(basePackage="com.guang") //配置注解扫描
@EnableAspectJAutoProxy //开启AOP自动代理
public class AppConfig{   
}
5. 测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AopTest {@Autowiredprivate UserService userService;@Testpublic void testQuickStart(){userService.save()}
}
4) 步骤小结
AOP详解
1) 通知的种类
通知的语法
@通知注解("切入点表达式")
通知的类型
名称注解说明
前置通知@Before通知方法在切入点方法之前执行
后置通知@AfterRuturning通知方法在切入点方法之后执行
异常通知@AfterThrowing通知方法在抛出异常时执行
最终通知@After通知方法无论是否有异常,最终都执行
环绕通知@Around通知方法在切入点方法之前、之后都执行
  • 注意:
    • 注解方式配置的通知,执行顺序是:前置->最终->后置/异常
    • 如果想要指定执行的顺序,就使用环绕通知
2) 切点表达式的抽取
  • 同xml的AOP一样,当多个切面的切入点表达式相同时,可以将切入点表达式进行抽取;
  • 抽取方法是:
    • 在增强类(切面类,即被@Aspect标的类)上增加方法,在方法上使用@Pointcut注解定义切入点表达式,
    • 在增强注解中引用切入点表达式所在的方法
  • 示例:
@Aspect
@Component("myAdvice1")
public class MyAdvice1 {//定义切入点表达式@Pointcut("execution(void com.guang.service..*.save())")public void myPointcut(){}//引用切入点表达式//完整写法:com.guang.aop.MyAdvice.myPointcut()//简单写法:myPointcut(), 引入当前类里定义的表达式,可以省略包类和类名不写@Before("myPointcut()")public void before(){System.out.println("前置通知...");}@AfterReturning("myPointcut()")public void afterReturning(){System.out.println("后置通知");}@After("myPointcut()")public void after(){System.out.println("最终通知");}@AfterThrowing("myPointcut()")public void afterThrowing(){System.out.println("抛出异常通知");}/*@Around("myPointcut()")public Object around(ProceedingJoinPoint pjp) throws Throwable {System.out.println("前置通知...");//切入点方法执行Object proceed = pjp.proceed();System.out.println("后置通知...");return proceed;}*/
}
3) 小结
  1. 在通知类上加注解@Aspect,声明成一个切面
  2. 在通知类里方法上加注解@Before/@AfterReturning/@AfterThrowing/@After/@Around,配置切入点表达式
  3. 在xml里开启aop的自动代理:<aop:aspectj-autoproxy/>

六、纯注解的AOP

  • 主要是把XML的配置,放到核心配置类上
@Configuration
@ComponentScan(basePackages="com.guang")//开启组件扫描
@EnableAspectJAutoProxy //开启AOP的自动代理
public class AppConfig{}
@Aspect
@Component("myAdvice2")
public class MyAdvice2 {//定义切入点表达式@Pointcut("execution(void com.guang.service..*.save())")public void myPointcut(){}@Before("myPointcut()")public void before(){System.out.println("前置通知...");}@AfterReturning("myPointcut()")public void afterReturning(){System.out.println("后置通知");}@After("myPointcut()")public void after(){System.out.println("最终通知");}@AfterThrowing("myPointcut()")public void afterThrowing(){System.out.println("异常通知");}
}

七、Spring事务管理

在Spring事务管理中,我推荐使用的是编程式事务,而不是声明式事务。

所谓的编程式事务就是我们手动来控制事务,而声明式事务则是有Spring来帮助我们来实现的。

而事务又是我们日常操作过程中最为常用且常用的,我们不知道Spring事务如何给我们操作的,所以建议不要将事务由Spring来进行处理,而是我们手动的来进行管理!!!

编程式事务管理
  • 所谓事务管理,即:按照给定的事务规则,来执行提交或回滚操作。其中:
    • 给定的事务规则:用TransactionDefinition表示
    • 按照…来执行提交或回滚操作:用PlatformTransactionManager来完成
    • TransactionStatus用于表示一个运行着的事务的状态

对于PlatformTransactionManager和TransactionStatus来说,是固定的套路,但是我们最需要关注的是这里的事务规则,也就是TransactionDefinition,在下面会重点关注和介绍。

关于编程式事务的说明以及API介绍
  • 编程式事务管理:通过编写代码的方式实现事务管理

    • 编程式事务管理,因事务管理与业务功能耦合性太强,不方便维护,目前已经基本不用,但是与SpringAOP结合起来使用更佳。

      spring 2.0 就已经提供了 xml配置的声明式事务管理的支持

  • 以下API仅做介绍了解,用于了解Spring事务相关的API,并回顾事务相关的概念

PlatformTransactionManager
  • 是Spring提供的事务管理器接口,它提供了我们常用的操作事务的方法:开启事务、提交事务等
  • 注意:PlatformTransactionManager是接口类型,不同的dao层技术有不同的实现,例如:
    • dao层是jdbcTemplate或Mybatis时,实现类是:DataSourceTransactionManager。而我们最常用的就是mybatis,所以肯定使用的是DataSourceTransactionManager。
    • dao层是Hibernate时,实现类是:HibernateTransactionManager
方法返回值说明
getTransaction(TransactionDefinition td)TransactionStatus开启事务,并得到事务状态
commit(TransactionStatus status)提交事务
rollback(TransactionStatus status)回滚事务
TransactionDefinition
  • 事务的定义信息对象,提供了以下常用方法:
方法参数返回值说明
getIsolationLevel()int获取事务的隔离级别
getPropogationBehavior()int获取事务的传播行为
getTimeout()int获取超时时间
isReadOnly()boolean是否只读的事务
事务的隔离级别:
  • ISOLATION_DEFAULT:默认事务隔离级别
    • MySql默认隔离级别:repeatable read
    • Oracle默认隔离级别:read committed
  • ISOLATION_READ_UNCOMMITTED:读未提交–存在脏读、不可重复读、幻读
  • ISOLATION_READ_COMMITTED:读已提交–存在不可重复读、幻读
  • ISOLATION_REPEATABLE_READ:重复读–存在幻读
  • ISOLATION_SERIALIZABLE:串行化–没有并发问题
事务的传播行为:

用于解决业务方法调用业务方法时,事务的统一性问题的

在这里插入图片描述

也就是说service中的methodA方法调用methodB方法的时候,对于methodB来说,叫做传播行为,决定使用怎么样的事务来调用methodB方法。

以下三个,是要当前事务的

  • PROPAGATION_REQUIRED需要有事务。默认
    • 如果有事务,就使用这个事务
    • 如果没有事务,就创建事务。
  • PROPAGATION_SUPPORTS:支持事务
    • 如果有事务,就使用当前事务,
    • 如果没有事务,就以非事务方式执行(没有事务)
  • PROPAGATION_MANDATORY:强制的
    • 如果有事务,就使用当前事务
    • 如果没有事务,就抛异常

以下三个,是不要当前事务的

  • PROPAGATION_REQUIRES_NEW:新建的
    • 如果有事务,就把事务挂起,再新建事务
    • 如果没有事务,新建事务
  • PROPAGATION_NOT_SUPPORTED:不支持的
    • 如果有事务,就把事务挂起,以非事务方式执行
    • 如果没有事务,就以非事务方式执行
  • PROPAGATION_NEVER:非事务的
    • 如果有事务,就抛异常
    • 如果没有事务,就以非事务方式执行

最后一个,是特殊的

  • PROPAGATION_NESTED:嵌套的
    • 如果有事务,就在事务里再嵌套一个事务执行
    • 如果没有事务,就是类似REQUIRED的操作
事务运行的超时时间:

超时后事务自动回滚

  • 默认值-1,表示没有超时限制
  • 如果有,可以以秒为单位进行设置
是否只读:
  • 如果设置为只读,那么方法只能查询,不能增删改
  • 通常是查询方法设置为只读
TransactionStatus
  • 提供了查询事务具体运行状态的方法,常用方法如下:
方法返回值说明
hasSavePoint()boolean事务是否有回滚点
isCompleted()boolean事务是否已经完成
isNewTransaction()boolean是否是新事务
isRollbackOnly()boolean事务是否是要回滚的状态
小结
  • PlatformTransactionManager接口:
    • 如果dao层用的是Mybatis、JdbcTemplate:用DataSourceTransactionManager
    • 如果dao层用的是Hibernate:用HibernateTransactionManager
  • 事务定义信息:
    • 事务的隔离级别:通常使用默认ISOLATION_DEFAULT
    • 事务的传播行为:通常使用默认PROPAGATION_REQUIRED
    • 事务的超时时间:如果事务执行超时,会回滚。单位是秒。值为-1表示永不超时
    • 事务是否是只读:如果只读,事务里只能执行查询操作,不能增删改
  • 事务状态接口
    • 事务是否有回滚点
    • 事务是否已经完成
    • 是否是新事务
    • 事务是否是要回滚的状态
示例代码一

下面也是来给出例子来进行说明展示:

给出实体类:

public class Account {private Integer id;private String name;private Float money;// 省略get/set方法
}

给出业务逻辑代码:

public interface AccountService {void transfer(String from, String to, Float money);
}

具体实现类中的代码:

@Service("accountService")
public class AccountServiceImpl implements AccountService {// 引入事务操作@Autowiredprivate AccountDao accountDao;@Autowiredprivate TransactionDefinition txDefinition;@Autowiredprivate PlatformTransactionManager txManager;@Overridepublic void transfer(String from, String to, Float money) {//开启事务,得到事务状态TransactionStatus txStatus = txManager.getTransaction(txDefinition);try {//操作daoAccount fromAccount = accountDao.findByName(from);Account toAccount = accountDao.findByName(to);fromAccount.setMoney(fromAccount.getMoney() - money);toAccount.setMoney(toAccount.getMoney() + money);accountDao.edit(fromAccount);accountDao.edit(toAccount);//提交事务txManager.commit(txStatus);} catch (Exception e) {//回滚事务txManager.rollback(txStatus);// 出现异常,进行补偿机制}}
}

那么给出dao代码:

public interface AccountDao {void edit(Account account) throws SQLException;Account findByName(String name) throws SQLException;
}

实现类代码:

package com.guang.dao.impl;import com.guang.dao.AccountDao;
import com.guang.domain.Account;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;import java.sql.SQLException;
import java.util.List;@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {@Autowiredprivate JdbcTemplate jdbcTemplate;@Overridepublic void edit(Account account) throws SQLException {jdbcTemplate.update("update account set name=?, money=? where id=?", account.getName(), account.getMoney(), account.getId());}@Overridepublic Account findByName(String name) throws SQLException {List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<>(Account.class), name);if (accounts == null || accounts.size() == 0) {return null;}else{return accounts.get(0);}}
}

然后给出配置文件代码

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.guang"/><!--事务的定义--><bean id="txDefinition" class="org.springframework.transaction.support.DefaultTransactionDefinition"><!--事务传播特性--><property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/><!--事务隔离级别--><property name="isolationLevelName" value="ISOLATION_DEFAULT"/><!--事务超时时间--><property name="timeout" value="-1"/><!--事务是否只读--><property name="readOnly" value="false"/></bean><!--定义事务管理器--><bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!--定义连接池--><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/spring"/><property name="username" value="root"/><property name="password" value="root"/></bean><!--定义JdbcTemplate--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"/></bean>
</beans>

从上面来看业务逻辑操作的话,可以看到代码侵入性太高!很明显不适合来使用。

@Service("accountService")
public class AccountServiceImpl implements AccountService {@Autowiredprivate TransactionDefinition txDefinition;@Autowiredprivate PlatformTransactionManager txManager;@Overridepublic void transfer(String from, String to, Float money) {//开启事务,得到事务状态TransactionStatus txStatus = txManager.getTransaction(txDefinition);try {// ....//提交事务txManager.commit(txStatus);} catch (Exception e) {//回滚事务txManager.rollback(txStatus);e.printStackTrace();}}
}

但是从这里来看的话,无非就是多用了PlatformTransactionManager和TransactionDefinition而已。

  • 那么我的想法是将上面的代码抽取起来写成一个通知类型,然后配置PointCut和Advice组成Advisor。然而在我即将要动手去写的时候,在查看DefaultTransactionDefinition类的时候,我发现有继承类TransactionTemplate,我一看这玩意儿不是我们在项目中经常使用的一个类吗?以前也写过,也遇到过,但是在这里遇到的时候,我才恍然大悟。原来我们一直就是这么用的,但是从来没有去想过背后的原理,所以这也是写这篇博客的收获之一!非常幸运。

那么首先来分析一下我原来的思路(即按照切面类的想法)

  • 1、针对读写类型来定义事务规则,这里涉及到四个属性,在DefaultTransactionDefinition中存在:

    • propagationBehavior,默认值是PROPAGATION_REQUIRED,即表示有事务就使用这个事务,没有事务就不用;
    • isolationLevel,默认值是:ISOLATION_DEFAULT,即表示默认使用数据库的事务。如果使用的是MySQL,那么就用MySQL的RR,可重复读级别;
    • timeout:默认值是:TIMEOUT_DEFAULT(-1),表示永不超时,超时时间以秒为单位;
    • readOnly:默认值是:false,表示的是增删改查询使用事务,查询不使用事务;

    之前我想的是定义多个事务规则对象,然后配置多个环绕通知方法,在开启事务的时候,根据不同的事务规则对象来获取得到事务。

  • 2、注入事务管理器,然后传入事务规则定义,获取得到当前的事务状态;

  • 3、利用事务管理器通过状态来进行提交或者是回滚事务;

上面可以通过AOP切面类来进行配置,但是看到了TransactionTemplate之后,我发现根本就不需要使用到切面类就可以来进行操作。

那么首先来看下TransactionTemplate类的继承体系:

在这里插入图片描述

分析一下继承体系:

  • 1、继承了DefaultTransactionDefinition类,所以可以用来定义事务规则信息对象;

  • 2、实现了InitializingBean接口,那么肯定是要在初始化方法来做操作;

  • 3、实现了TransactionOperations接口,从接口名称中可以知道这里代表的是事务;

而在TransactionTemplate类中,只有一个属性PlatformTransactionManager,而这是又是我们所需要的事务管理器。

而在InitializingBean接口的初始化方法中做了校验,如下所示:

	public void afterPropertiesSet() {if (this.transactionManager == null) {throw new IllegalArgumentException("Property 'transactionManager' is required");}}

因为TransactionTemplate中存在无参方法,没有注入transactionManager对象,说明我们需要手动注入当前容器中的transactionManager对象。那么配置TransactionTemplate的时候可以配置如下所示:

<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"><property name="transactionManager" ref="transactionManager" /><!--针对于不同的类型来配置传播行为、隔离级别、超时时间和是否只读,因为可以继承父类中的属性--><!--还可以配置传播行为、隔离级别、超时时间和是否只读,因为可以继承父类中的属性-->
</bean>

如下所示,配置多个事务定义规则:

<bean id="transactionTemplate1" class="org.springframework.transaction.support.TransactionTemplate"><property name="transactionManager" ref="transactionManager" /><!--事务传播特性--><property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/><!--事务隔离级别--><property name="isolationLevelName" value="ISOLATION_DEFAULT"/><!--事务超时时间--><property name="timeout" value="-1"/><!--事务是否只读--><property name="readOnly" value="false"/>
</bean><bean id="transactionTemplat2" class="org.springframework.transaction.support.TransactionTemplate"><property name="transactionManager" ref="transactionManager" /><!--事务传播特性--><property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/><!--事务隔离级别--><property name="isolationLevelName" value="ISOLATION_DEFAULT"/><!--事务超时时间--><property name="timeout" value="3"/><!--事务是否只读--><property name="readOnly" value="false"/>
</bean>    <bean id="transactionTemplat3" class="org.springframework.transaction.support.TransactionTemplate"><property name="transactionManager" ref="transactionManager" /><!--事务传播特性--><property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/><!--事务隔离级别--><property name="isolationLevelName" value="ISOLATION_DEFAULT"/><!--事务超时时间--><property name="timeout" value="3"/><!--事务是否只读--><property name="readOnly" value="true"/>
</bean>        

那么只需要在使用的时候,通过以下方式注入即可:

@Autowired
@Qulifier("transactionTemplat1")
private TransactionTemplate transactionTemplate@Autowired
@Qulifier("transactionTemplat2")
private TransactionTemplate transactionTemplate@Autowired
@Qulifier("transactionTemplat3")
private TransactionTemplate transactionTemplate    

通过这种方式来进行注入即可,也是比较方便的。

那么上面说完事务规则定义这块,这块应该有个疑问:在TransactionTemplate从哪里根据事务管理器获取得到事务状态的代码呢?

类似如下的代码:

@Service("accountService")
public class AccountServiceImpl implements AccountService {@Autowiredprivate TransactionDefinition txDefinition;@Autowiredprivate PlatformTransactionManager txManager;@Overridepublic void transfer(String from, String to, Float money) {//开启事务,得到事务状态TransactionStatus txStatus = txManager.getTransaction(txDefinition);try {// ....//提交事务txManager.commit(txStatus);} catch (Exception e) {//回滚事务txManager.rollback(txStatus);e.printStackTrace();}}
}

来从TransactionTemplate中找,发现就提供了一个方法:execute方法,那么重点就关注一下execute方法即可。

这个方法也是非常的简单,重点是这里的TransactionCallback接口,提前看下:

public interface TransactionCallback<T> {T doInTransaction(TransactionStatus status);
}

接口中只有一个抽象方法,又被称之为函数式接口。不懂的话接着向下看

在这里插入图片描述

这里的方法也是非常简单呐,相当于是已经帮助我们做好了。

给出一个使用代码示例:

这里给的是没有返回值的,所以这里给的是Void类型。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;@Service
public class TransactionalService {@Autowiredprivate TransactionTemplate transactionTemplate;public void performTransactionalOperation() {// 不建议使用下面两行代码!因为如果忘记了设置会默认值,那么后面再次使用的时候默认使用的九种方式/**nsactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);**/transactionTemplate.execute(new TransactionCallback<Void>() {public Void doInTransaction(TransactionStatus status) {// 在这里执行事务操作// 可以进行数据库操作、调用其他需要事务支持的方法等return null;}});}
}

再给出一个有值的代码且可以自己来处理事务状态。不利用事务管理中的回滚方法而已!

public Object getObject(String str) {/**  执行带有返回值<Object>的事务管理*/transactionTemplate.execute(new TransactionCallback<Object>() {@Overridepublic Object doInTransaction(TransactionStatus transactionStatus) {try {...//.......   业务代码return new Object();// 注意:这里没有提交代码的逻辑} catch (Exception e) {// 回滚,这里并不是利用事务管理器进行提交的,这里只是设置了一个标记而已// 在事务管理进行事务提交的时候会来检查这里的状态而已transactionStatus.setRollbackOnly();return null;}}});
}
示例代码二

在上面的分析代码中,给出示例代码二:

首先给出配置xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.guang.dao"/><context:component-scan base-package="com.guang.service.impl1."/><!--事务的定义--><bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"><!--配置事务管理器--><property name="transactionManager" ref="txManager"/><!--事务传播特性!如果调用其他方法的时候,直接使用当前方法中的事务--><property name="propagationBehaviorName" value="PROPAGATION_REQUIRED"/><!--事务隔离级别,使用数据库的操作--><property name="isolationLevelName" value="ISOLATION_DEFAULT"/><!--事务超时时间--><property name="timeout" value="-1"/><!--事务是否只读!因为是有更新操作,所以这里进行修改操作--><property name="readOnly" value="false"/></bean><!--定义事务管理器--><bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!--定义连接池--><bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/spring"/><property name="username" value="root"/><property name="password" value="root"/></bean><!--定义JdbcTemplate--><bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"><constructor-arg name="dataSource" ref="dataSource"/></bean>
</beans>

然后给出代码:

package com.guang.service.impl1;import com.guang.dao.AccountDao;
import com.guang.domain.Account;
import com.guang.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;@Service("accountService")
public class AccountServiceImpl1 implements AccountService {@Autowiredprivate AccountDao accountDao;@Autowiredprivate TransactionTemplate transactionTemplate;@Overridepublic void transfer(String from, String to, Float money) {transactionTemplate.execute((status)-> {try {// 来做事务操作Account fromAccount = accountDao.findByName(from);Account toAccount = accountDao.findByName(to);fromAccount.setMoney(fromAccount.getMoney() - money);toAccount.setMoney(toAccount.getMoney() + money);accountDao.edit(fromAccount);int i = 1 / 0;accountDao.edit(toAccount);} catch ( Exception exception) {System.out.println("执行SQL阶段出现异常!不能够进行提交");status.setRollbackOnly();}return null;});System.out.println("使用事务管理器来执行代码完成");}
}

然后将代码放置到仓库中去

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/417553.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Ubuntu使用docker-compose安装chatGPT

ubuntu环境搭建专栏&#x1f517;点击跳转 Ubuntu系统环境搭建&#xff08;十五&#xff09;——使用docker-compose安装chatGPT Welcome to the AI era! 使用docker compose安装 在/usr/local文件夹下创建chatgpt mkdir chatgpt创建docker-compose.yaml vim docker-compos…

什么是OSPF?为什么需要OSPF?OSPF基础概念

什么是OSPF&#xff1f; 开放式最短路径优先OSPF&#xff08;Open Shortest Path First&#xff09;是IETF组织开发的一个基于链路状态的内部网关协议&#xff08;Interior Gateway Protocol&#xff09;。 目前针对IPv4协议使用的是OSPF Version 2&#xff08;RFC2328&#x…

数据库(MySQL库表操作)

目录 1.1 SQL语句基础&#xff08;SQL命令&#xff09; 1.1.1 SQL的简介 1.1.2 SQL语句的分类 1.1.3 SQL语句的书写规范 1.2 数据库操作 1.2.1 查看 1.2.2 自建库 1.2.3 切换数据库 1.2.4 删库 1.3 MySQL字符集 1.3.1 MySQL字符集包括&#xff1a; 1.3.2 utf8 和 u…

性能压力测试:企业成功的关键要素

性能压力测试在现代企业软件开发中扮演着至关重要的角色&#xff0c;它不仅仅是一项技术手段&#xff0c;更是保障企业成功的关键要素。本文将探讨性能压力测试在企业中的重要性&#xff0c;并阐述其对业务稳定性、用户体验和品牌声誉的影响。 一、保障业务稳定性 1、应对高负载…

Java 全栈知识点问题汇总(上)

Java 全栈知识点问题汇总&#xff08;上&#xff09; 1 Java 基础 1.1 语法基础 面向对象特性&#xff1f;a a b 与 a b 的区别3*0.1 0.3 将会返回什么? true 还是 false?能在 Switch 中使用 String 吗?对equals()和hashCode()的理解?final、finalize 和 finally 的不同…

SpringCloud Aliba-Sentinel【中篇】-从入门到学废【5】

&#x1f3b5;歌词分享&#x1f3b5; 岁月在墙上剥落看见小时候。 ——《东风破》 目录 &#x1f953;1.流控规则 &#x1f32d;2. 熔断规则 &#x1f9c8;3.热点规则 &#x1f9c2;4.系统规则 1.流控规则 1.资源名&#xff1a;唯一名称&#xff0c;默认请求路径 2.针对来…

GPT应用_AutoGPT

项目地址&#xff1a;https://github.com/Significant-Gravitas/AutoGPT 1 功能 1.1 整体功能&#xff0c;想解决什么问题 单独使用 ChatGPT 时&#xff0c;只提供基本的聊天&#xff0c;无法实现复杂多步的功能&#xff0c;以及与其它应用交互&#xff0c;如果想提供某种功…

OpenCV实战:控制手势实现无触摸拖拽功能

前言&#xff1a; Hello大家好&#xff0c;我是Dream。 今天来学习一下如何使用OpenCV来控制手势&#xff0c;瞬间提升操作体验&#xff01;跨越界限&#xff0c;OpenCV手势控制拖拽功能现身。 一、主要步骤及库的功能介绍 1.主要步骤 要实现本次实验&#xff0c;主要步骤如下…

Redis缓存问题解决方案

Redis缓存问题解决方案 为什么使用Redis缓存&#xff1a; 1.在高并发的情况下&#xff0c;大量查询进入数据库&#xff0c;会大量占用数据库的连接&#xff08;默认数据库连接数151&#xff09;,数据库压力过大就会出现connection refuse(数据库连接拒绝)问题&#xff0c; 2.…

Codeforces Round 895 (Div. 3)补题

Two Vessels&#xff08;Problem - A - Codeforces&#xff09; 题目大意&#xff1a;有两个无限容器&#xff0c;目前一个容器中有a克水&#xff0c;另一个容器中有b克水&#xff0c;现有一个大小为cg的容器&#xff0c;我们每次可以从一个无限容器中取任意不大于c克的水&…

android中.9图如何制作

在工作中我们需要做界面往往需要设计师给我们提供图标&#xff0c;我们才能把界面友好的显示出来&#xff0c;普通的图标用png、jpg等都可以显示出来&#xff0c;为了让界面更与众不同&#xff0c;设计师们通常会设计一些弯角、弯钩如果我们要作为背景使用的话就不能设置高度&a…

【 Qt 快速上手】-①- Qt 背景介绍与发展前景

文章目录 1.1 什么是 Qt1.2 Qt 的发展史1.3 Qt 支持的平台1.4 Qt 版本1.5 Qt 的优点1.6 Qt的应用场景1.7 Qt的成功案例1.8 Qt的发展前景及就业分析行业发展方向就业方面的发展前景 1.1 什么是 Qt Qt 是一个跨平台的 C 图形用户界面应用程序框架。它为应用程序开发者提供了建立…