能够做二维码网站,WordPress怎么去掉主题也没,十大保洁公司,互联网官网入口目录
十五、面向切面编程AOP
15.1 AOP介绍
总结
15.2 AOP的七大术语
15.3 切点表达式
15.4 使用Spring的AOP
15.4.1 准备工作
15.4.1.1Spring AOP 基于注解之实现步骤
15.4.1.2-Spring AOP 基于注解之切点表达式
代码
运行结果#xff1a;
代码
运行结果 通知类…目录
十五、面向切面编程AOP
15.1 AOP介绍
总结
15.2 AOP的七大术语
15.3 切点表达式
15.4 使用Spring的AOP
15.4.1 准备工作
15.4.1.1Spring AOP 基于注解之实现步骤
15.4.1.2-Spring AOP 基于注解之切点表达式
代码
运行结果
代码
运行结果 通知类型
代码
运行结果
加上异常
代码
运行结果 切面的先后顺序
再定义一个切面类如下
另一个切面类并设置优先级
设置切面类MyAspect的优先级
执行测试程序 通过修改Order注解的整数值来切换顺序执行测试程序
优化使用切点表达式
小细节---Spring AOP基于注解之连接点
测试代码
运行结果
代码
运行结果
全注解式开发AOP
代码【编写一个类】
运行结果
15.4.3 基于XML配置方式的AOP了解
15.5 AOP的实际案例事务处理
代码 银行账户的业务类 订单业务类
事务切面类
编写测试程序
运行结果
有异常时
增添代码
运行结果
15.6 AOP的实际案例安全日志
spring.xml
用户业务类
VIP业务类
编写一个负责安全的切面类
测试程序
运行结果
十六、Spring对事务的支持
16.1 事务概述
16.2 引入事务场景 第一步准备数据库表
第二步创建包结构
第三步准备POJO类
第四步编写持久层
第五步编写业务层
第六步编写Spring配置文件
第七步编写表示层测试程序
代码
执行结果
数据变化
模拟异常
执行结果 数据库表中数据
16.3 Spring对事务的支持
Spring实现事务的两种方式
编程式事务
声明式事务
Spring事务管理API
声明式事务之注解实现方式
代码有异常
运行结果
假如没有异常
运行结果 数据同步更新变化 事务属性
事务属性包括哪些
事务传播行为
事务隔离级别
测试读未提交
代码
运行结果
测试读提交
代码
运行结果
事务超时
代码如下 以下代码的超时不会被计入超时时间
以下代码超时时间会被计入超时时间
只读事务
代码如下
设置哪些异常回滚事务
代码如下
设置哪些异常不回滚事务
代码如下
事务的全注解式开发
代码
运行结果
模拟异常看事务是否起作用
代码
运行结果
声明式事务之XML实现方式
配置步骤
编写测试程序
执行结果
数据库表中记录 十五、面向切面编程AOP
IoC使软件组件松耦合。AOP让你能够捕捉系统中经常使用的功能把它转化成组件。
AOPAspect Oriented Programming面向切面编程面向方面编程。AOP是一种编程技术
AOP是对OOP的补充延伸。
AOP底层使用的就是动态代理来实现的。
Spring的AOP使用的动态代理是JDK动态代理 CGLIB动态代理技术。Spring在这两种动态代理中灵活切换如果是代理接口会默认使用JDK动态代理如果要代理某个类这个类没有实现接口就会切换使用CGLIB。当然你也可以强制通过一些配置让Spring只使用CGLIB。
15.1 AOP介绍
一般一个系统当中都会有一些系统服务例如日志、事务管理、安全等。这些系统服务被称为交叉业务
这些交叉业务几乎是通用的不管你是做银行账户转账还是删除用户数据。日志、事务管理、安全这些都是需要做的。
如果在每一个业务处理过程当中都掺杂这些交叉业务代码进去的话存在两方面问题
第一交叉业务代码在多个业务流程中反复出现显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话需要修改多处。第二程序员无法专注核心业务代码的编写在编写核心业务代码的同时还需要处理这些交叉业务。
使用AOP可以很轻松的解决以上问题。
请看下图可以帮助你快速理解AOP的思想 总结
用一句话总结AOP将与核心业务无关的代码独立的抽取出来形成一个独立的组件然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。
AOP的优点
第一代码复用性增强。第二代码易维护。第三使开发者更关注业务逻辑。
15.2 AOP的七大术语 public class UserService{public void do1(){System.out.println(do 1);}public void do2(){System.out.println(do 2);}public void do3(){System.out.println(do 3);}public void do4(){System.out.println(do 4);}public void do5(){System.out.println(do 5);}// 核心业务方法public void service(){do1();do2();do3();do5();}
}
连接点 Joinpoint 在程序的整个执行流程中可以织入切面的位置。方法的执行前后异常抛出之后等位置。
切点 Pointcut 在程序执行流程中真正织入切面的方法。一个切点对应多个连接点
通知 Advice 通知又叫增强就是具体你要织入的代码。通知包括 前置通知后置通知环绕通知异常通知最终通知
切面 Aspect 切点 通知就是切面。
织入 Weaving 把通知应用到目标对象上的过程。
代理对象 Proxy 一个目标对象被织入通知后产生的新对象。
目标对象 Target 被织入通知的对象。
通过下图大家可以很好的理解AOP的相关术语 15.3 切点表达式
切点表达式用来定义通知Advice往哪些方法上切入。
切入点表达式语法格式
execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
访问控制权限修饰符
可选项。没写就是4个权限都包括。写public就表示只包括公开的方法。
返回值类型
必填项。* 表示返回值类型任意。
全限定类名
可选项。两个点“..”代表当前包以及子包下的所有类。省略时表示所有的类。
方法名
必填项。*表示所有方法。set*表示所有的set方法。
形式参数列表
必填项
() 表示没有参数的方法(..) 参数类型和个数随意的方法(*) 只有一个参数的方法(*, String) 第一个参数类型随意第二个参数是String的。
异常
可选项。省略时表示任意异常类型。
理解以下的切点表达式
service包下所有的类中以delete开始的所有方法
execution(public * com.powernode.mall.service.*.delete*(..))
mall包下所有的类的所有的方法
execution(* com.powernode.mall..*(..))
所有类的所有方法
execution(* *(..))
15.4 使用Spring的AOP
Spring对AOP的实现包括以下3种方式
第一种方式Spring框架结合AspectJ框架实现的AOP基于注解方式。第二种方式Spring框架结合AspectJ框架实现的AOP基于XML方式。第三种方式Spring框架自己实现的AOP基于XML配置方式。
实际开发中都是SpringAspectJ来实现AOP。所以我们重点学习第一种和第二种方式。
什么是AspectJEclipse组织的一个支持AOP的框架。AspectJ框架是独立于Spring框架之外的一个框架Spring框架用了AspectJ
AspectJ项目起源于帕洛阿尔托Palo Alto研究中心缩写为PARC。该中心由Xerox集团资助Gregor Kiczales领导从1997年开始致力于AspectJ的开发1998年第一次发布给外部用户2001年发布1.0 release。为了推动AspectJ技术和社团的发展PARC在2003年3月正式将AspectJ项目移交给了Eclipse组织因为AspectJ的发展和受关注程度大大超出了PARC的预期他们已经无力继续维持它的发展。
15.4.1 准备工作
使用SpringAspectJ的AOP需要引入的依赖如下
pom.xml
!--spring context依赖--
dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion6.0.0-M2/version
/dependency
!--spring aop依赖--
dependencygroupIdorg.springframework/groupIdartifactIdspring-aop/artifactIdversion6.0.0-M2/version
/dependency
!--spring aspects依赖--
dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion6.0.0-M2/version
/dependency
Spring配置文件中添加context命名空间和aop命名空间
spring-aspectj-aop-annotation.xml
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd/beans
15.4.1.1Spring AOP 基于注解之实现步骤
spring.xml
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd!--组件扫描--context:component-scan base-packagecom.dong.service/!--开启aspectj 自动代理--!--spring容器在扫描类的时候查看该类上是否有Aspect 注解如果有则给这个类生成代理对象--!--proxy-target-classtrue 表示强制使用CGLIB动态代理proxy-target-classfalse 这是默认值表示接口使用JDK动态代理反之使用CGLIB动态代理--aop:aspectj-autoproxy proxy-target-classtrue//beans
UserService.java
package com.dong.service;import org.springframework.stereotype.Service;Service(userService)
public class UserService { //目标类public void login(){ //目标方法System.out.println(系统正在验证登录。。。。。。);}
}LogAspect.java
package com.dong.service;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;Component(logAspect)
Aspect //切面类是需要使用Aspect 注解进行标注的
public class LogAspect { //切面//切面 通知 切点//通知就是增强就是具体的要编写的增强代码//这里通知Advice以方法的形式出现。因为方法中可以写代码// Before注解标注的方法就是一个前置通知Before(execution(* com.dong.service.UserService.*(..)))public void 增强(){System.out.println(这是一段增强代码。。。。。。);}}测试类SpringAOPTest.java
package com.dong.spring6.test;import com.dong.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringAOPTest {Testpublic void testBefore(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(spring.xml);UserService userService applicationContext.getBean(userService, UserService.class);userService.login();}
}运行结果 可以看出是一个前置通知。
15.4.1.2-Spring AOP 基于注解之切点表达式
代码
package com.dong.service;import org.springframework.stereotype.Service;Service(userService)
public class UserService { //目标类public void login(){ //目标方法System.out.println(系统正在验证登录。。。。。。);}public void logout(){System.out.println(退出系统。。。。);}
}package com.dong.spring6.test;import com.dong.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringAOPTest {Testpublic void testBefore(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(spring.xml);UserService userService applicationContext.getBean(userService, UserService.class);userService.login();userService.logout();}
}运行结果 代码
package com.dong.service;import org.springframework.stereotype.Service;Service(orderService)
public class OrderService { //目标类//目标方法public void generate(){System.out.println(生成订单。。。);}
}package com.dong.service;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;Component(logAspect)
Aspect //切面类是需要使用Aspect 注解进行标注的
public class LogAspect { //切面//切面 通知 切点//通知就是增强就是具体的要编写的增强代码//这里通知Advice以方法的形式出现。因为方法中可以写代码// Before注解标注的方法就是一个前置通知Before(execution(* com.dong.service..*(..)))public void 增强(){System.out.println(这是一段增强代码。。。。。。);}}package com.dong.spring6.test;import com.dong.service.OrderService;
import com.dong.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringAOPTest {Testpublic void testBefore(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(spring.xml);UserService userService applicationContext.getBean(userService, UserService.class);userService.login();userService.logout();OrderService orderService applicationContext.getBean(orderService, OrderService.class);orderService.generate();}
}运行结果 通知类型
通知类型包括
前置通知Before 目标方法执行之前的通知后置通知AfterReturning 目标方法执行之后的通知环绕通知Around 目标方法之前添加通知同时目标方法执行之后添加通知。异常通知AfterThrowing 发生异常之后执行的通知最终通知After 放在finally语句块中的通知 代码
package com.dong.service;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;Component(logAspect)
Aspect //切面类是需要使用Aspect 注解进行标注的
public class LogAspect { //切面//切面 通知 切点//通知就是增强就是具体的要编写的增强代码//这里通知Advice以方法的形式出现。因为方法中可以写代码// Before注解标注的方法就是一个前置通知
/* Before(execution(* com.dong.service..*(..)))public void 增强(){System.out.println(这是一段增强代码。。。。。。);}*/Before(execution(* com.dong.service..*(..)))public void beforeAdvice(){System.out.println(前置通知);}AfterReturning(execution(* com.dong.service..*(..)))public void afterReturningAdvice(){System.out.println(后置通知);}//环绕通知环绕是最大的通知在前置通知之前在后置通知之后Around(execution(* com.dong.service..*(..)))public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {//前面的代码System.out.println(前环绕);//执行目标joinPoint.proceed(); //执行目标//后面的代码System.out.println(后环绕);}//异常通知
/* AfterThrowing(execution(* com.dong.service..*(..)))public void afterThrowingAdvice(){System.out.println(异常通知);}*///最终通知(Finally语句块中的通知)After(execution(* com.dong.service..*(..)))public void afterAdvice(){System.out.println(最终通知);}}package com.dong.spring6.test;import com.dong.service.OrderService;
import com.dong.service.UserService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringAOPTest {Testpublic void testBefore(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(spring.xml);/* UserService userService applicationContext.getBean(userService, UserService.class);userService.login();userService.logout();*/OrderService orderService applicationContext.getBean(orderService, OrderService.class);orderService.generate();}
}运行结果 加上异常
代码
package com.dong.service;import org.springframework.stereotype.Service;Service(orderService)
public class OrderService { //目标类//目标方法public void generate(){System.out.println(生成订单。。。);if (11){throw new RuntimeException(运行时异常);}}
}//异常通知AfterThrowing(execution(* com.dong.service..*(..)))public void afterThrowingAdvice(){System.out.println(异常通知);}
运行结果 切面的先后顺序
我们知道业务流程当中不一定只有一个切面可能有的切面控制事务有的记录日志有的进行安全控制如果多个切面的话顺序如何控制可以使用Order注解来标识切面类为Order注解的value指定一个整数型的数字数字越小优先级越高。
再定义一个切面类如下 另一个切面类并设置优先级
package com.dong.spring6.service;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;Aspect
Component
Order(1) //设置优先级
public class YourAspect {Around(execution(* com.powernode.spring6.service.OrderService.*(..)))public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println(YourAspect环绕通知开始);// 执行目标方法。proceedingJoinPoint.proceed();System.out.println(YourAspect环绕通知结束);}Before(execution(* com.powernode.spring6.service.OrderService.*(..)))public void beforeAdvice(){System.out.println(YourAspect前置通知);}AfterReturning(execution(* com.powernode.spring6.service.OrderService.*(..)))public void afterReturningAdvice(){System.out.println(YourAspect后置通知);}AfterThrowing(execution(* com.powernode.spring6.service.OrderService.*(..)))public void afterThrowingAdvice(){System.out.println(YourAspect异常通知);}After(execution(* com.powernode.spring6.service.OrderService.*(..)))public void afterAdvice(){System.out.println(YourAspect最终通知);}
}设置切面类MyAspect的优先级
package com.dong.spring6.service;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;// 切面类
Component
Aspect
Order(2) //设置优先级
public class MyAspect {Around(execution(* com.powernode.spring6.service.OrderService.*(..)))public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println(环绕通知开始);// 执行目标方法。proceedingJoinPoint.proceed();System.out.println(环绕通知结束);}Before(execution(* com.powernode.spring6.service.OrderService.*(..)))public void beforeAdvice(){System.out.println(前置通知);}AfterReturning(execution(* com.powernode.spring6.service.OrderService.*(..)))public void afterReturningAdvice(){System.out.println(后置通知);}AfterThrowing(execution(* com.powernode.spring6.service.OrderService.*(..)))public void afterThrowingAdvice(){System.out.println(异常通知);}After(execution(* com.powernode.spring6.service.OrderService.*(..)))public void afterAdvice(){System.out.println(最终通知);}}
执行测试程序 通过修改Order注解的整数值来切换顺序执行测试程序 优化使用切点表达式 缺点是
第一切点表达式重复写了多次没有得到复用。第二如果要修改切点表达式需要修改多处难维护。
可以这样做将切点表达式单独的定义出来在需要的位置引入即可。如下
package com.dong.service;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;Component(logAspect)
Aspect //切面类是需要使用Aspect 注解进行标注的
public class LogAspect { //切面//切面 通知 切点//通知就是增强就是具体的要编写的增强代码//这里通知Advice以方法的形式出现。因为方法中可以写代码// Before注解标注的方法就是一个前置通知
/* Before(execution(* com.dong.service..*(..)))public void 增强(){System.out.println(这是一段增强代码。。。。。。);}*///定义通用切点Pointcut(execution(* com.dong.service..*(..)))public void 通用切点(){//这个方法只是一个标记方法名随意方法体中也不需要写任何代码}Before(通用切点())public void beforeAdvice(){System.out.println(前置通知);}AfterReturning(通用切点())public void afterReturningAdvice(){System.out.println(后置通知);}//环绕通知环绕是最大的通知在前置通知之前在后置通知之后Around(通用切点())public void aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {//前面的代码System.out.println(前环绕);//执行目标joinPoint.proceed(); //执行目标//后面的代码System.out.println(后环绕);}//异常通知AfterThrowing(通用切点())public void afterThrowingAdvice(){System.out.println(异常通知);}//最终通知(Finally语句块中的通知)After(通用切点())public void afterAdvice(){System.out.println(最终通知);}}使用Pointcut注解来定义独立的切点表达式。
注意这个Pointcut注解标注的方法随意只是起到一个能够让Pointcut注解编写的位置。
执行测试程序 小细节---Spring AOP基于注解之连接点
测试代码 Before(通用切点())public void beforeAdvice(JoinPoint joinPoint){System.out.println(前置通知);//这个JoinPoint joinPoint在Spring容器调用这个方法的时候自动传过来//Signature signature joinPoint.getSignature();获取目标方法的签名。//通过方法的签名可以获取到一个方法的具体信息。//获取目标方法的方法名System.out.println(目标方法的方法名joinPoint.getSignature().getName());//获取目标方法的的方法修饰符//获取目标方法的方法返回值}
运行结果 代码 Before(通用切点())public void beforeAdvice(JoinPoint joinPoint){System.out.println(前置通知);//这个JoinPoint joinPoint在Spring容器调用这个方法的时候自动传过来//Signature signature joinPoint.getSignature();获取目标方法的签名。//通过方法的签名可以获取到一个方法的具体信息。//获取目标方法的方法名System.out.println(目标方法的方法名joinPoint.getSignature().getName());//获取目标方法的的方法修饰符System.out.println(目标方法的的方法修饰符:joinPoint.getSignature().getModifiers());//获取目标方法的方法返回值System.out.println(joinPoint.getSignature());System.out.println(目标方法的的全限定类名joinPoint.getSignature().getDeclaringTypeName());}
运行结果 全注解式开发AOP
代码【编写一个类】
package com.dong.service;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;Configuration//代替spring.xml 文件
ComponentScan({com.dong.service})
EnableAspectJAutoProxy(proxyTargetClass true)//启用aspect的自动代理
public class SpringConfig {
}//全注解式开发Testpublic void testNoXml(){ApplicationContext applicationContextnew AnnotationConfigApplicationContext(SpringConfig.class);OrderService orderService applicationContext.getBean(orderService, OrderService.class);orderService.generate();}
运行结果 15.4.3 基于XML配置方式的AOP了解 15.5 AOP的实际案例事务处理
项目中的事务控制是在所难免的。在一个业务流程当中可能需要多条DML语句共同完成为了保证数据的安全这多条DML语句要么同时成功要么同时失败。这就需要添加事务控制的代码。例如以下伪代码
伪代码
class 业务类1{public void 业务方法1(){try{// 开启事务startTransaction();// 执行核心业务逻辑step1();step2();step3();....// 提交事务commitTransaction();}catch(Exception e){// 回滚事务rollbackTransaction();}}public void 业务方法2(){try{// 开启事务startTransaction();// 执行核心业务逻辑step1();step2();step3();....// 提交事务commitTransaction();}catch(Exception e){// 回滚事务rollbackTransaction();}}public void 业务方法3(){try{// 开启事务startTransaction();// 执行核心业务逻辑step1();step2();step3();....// 提交事务commitTransaction();}catch(Exception e){// 回滚事务rollbackTransaction();}}
}class 业务类2{public void 业务方法1(){try{// 开启事务startTransaction();// 执行核心业务逻辑step1();step2();step3();....// 提交事务commitTransaction();}catch(Exception e){// 回滚事务rollbackTransaction();}}public void 业务方法2(){try{// 开启事务startTransaction();// 执行核心业务逻辑step1();step2();step3();....// 提交事务commitTransaction();}catch(Exception e){// 回滚事务rollbackTransaction();}}public void 业务方法3(){try{// 开启事务startTransaction();// 执行核心业务逻辑step1();step2();step3();....// 提交事务commitTransaction();}catch(Exception e){// 回滚事务rollbackTransaction();}}
}
//......
可以看到这些业务类中的每一个业务方法都是需要控制事务的而控制事务的代码又是固定的格式都是
try{// 开启事务startTransaction();// 执行核心业务逻辑//......// 提交事务commitTransaction();
}catch(Exception e){// 回滚事务rollbackTransaction();
}
这个控制事务的代码就是和业务逻辑没有关系的“交叉业务”。以上伪代码当中可以看到这些交叉业务的代码没有得到复用并且如果这些交叉业务代码需要修改那必然需要修改多处难维护怎么解决
可以采用AOP思想解决。可以把以上控制事务的代码作为环绕通知切入到目标类的方法当中。接下来我们做一下这件事有两个业务类如下
代码
spring.xml
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd!--组件扫描--context:component-scan base-packagecom.dong.spring6.service/!--启动自动代理--aop:aspectj-autoproxy/
/beans 银行账户的业务类
package com.dong.spring6.service;import org.springframework.stereotype.Service;Service
public class AccountService { //目标对象//目标方法//转账的业务方法public void transfer(){System.out.println(银行账户正在完成转账操作....);}//目标方法//取款的业务方法public void withdraw(){System.out.println(正在取款请稍后......);}
}订单业务类
package com.dong.spring6.service;import org.springframework.stereotype.Service;Service
public class OrderService { //目标对象//目标方法//生成订单的业务方法public void generate(){System.out.println(正在生成订单.....);}//目标方法//订单取消的业务方法public void cancel(){System.out.println(订单取消...);}
}注意以上两个业务类已经纳入spring bean的管理因为都添加了Component注解。
接下来我们给以上两个业务类的4个方法添加事务控制代码使用AOP来完成
事务切面类
package com.dong.spring6.service;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;/*** 切面*/
Component
Aspect
public class TransactionAspect {Around(execution(* com.dong.spring6.service..*(..)))public void aroundAdvice(ProceedingJoinPoint joinPoint){//执行目标try {//前环绕System.out.println(开启事务.....);//执行目标joinPoint.proceed();//后环绕System.out.println(提交事务.....);} catch (Throwable e) {System.out.println(回滚事务);}}
}上述事务控制代码只需要写一次就行了并且修改起来也没有成本。
编写测试程序
package com.dong.spring6.test;import com.dong.spring6.service.AccountService;
import com.dong.spring6.service.OrderService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class AOPRealAppTest {//编程式事务解决方案。Testpublic void testTransaction(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(spring.xml);AccountService accountService applicationContext.getBean(accountService, AccountService.class);OrderService orderService applicationContext.getBean(orderService, OrderService.class);accountService.transfer();accountService.withdraw();orderService.generate();orderService.cancel();}
}运行结果 有异常时
增添代码
package com.dong.spring6.service;import org.springframework.stereotype.Service;Service
public class OrderService { //目标对象//目标方法//生成订单的业务方法public void generate(){System.out.println(正在生成订单.....);}//目标方法//订单取消的业务方法public void cancel(){System.out.println(订单取消...);//空指针异常String snull;s.toString();}
}运行结果 15.6 AOP的实际案例安全日志
需求是这样的项目开发结束了已经上线了。运行正常。客户提出了新的需求凡事在系统中进行修改操作的删除操作的新增操作的都要把这个人记录下来。因为这几个操作是属于危险行为。例如有业务类和业务方法
spring.xml
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd!--组件扫描--context:component-scan base-packagecom.dong.spring6/!--启动自动代理--aop:aspectj-autoproxy/
/beans
用户业务类
package com.dong.spring6.biz;import org.springframework.stereotype.Service;Service
public class UserService {public void saveUser(){System.out.println(新增用户信息);}public void deleteUser(){System.out.println(删除用户信息);}public void modifyUser(){System.out.println(修改用户信息);}public void getUser(){System.out.println(获取用户信息);}}VIP业务类
package com.dong.spring6.biz;import org.springframework.stereotype.Service;Service
public class VipService {public void saveVip(){System.out.println(新增会员信息);}public void deleteVip(){System.out.println(删除会员信息);}public void modifyVip(){System.out.println(修改会员信息);}public void getVip(){System.out.println(获取会员信息);}}注意已经添加了Servic ( Component )注解。
接下来我们使用aop来解决上面的需求
编写一个负责安全的切面类
package com.dong.spring6.biz;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;import java.text.SimpleDateFormat;
import java.util.Date;Component
Aspect
public class SecurityLogAspect {Pointcut(execution(* com.dong.spring6.biz..save*(..)))public void savePointCut(){}Pointcut(execution(* com.dong.spring6.biz..delete*(..)))public void deletePointCut(){}Pointcut(execution(* com.dong.spring6.biz..modify*(..)))public void modifyPointCut(){}Pointcut(execution(* com.dong.spring6.biz..get*(..)))public void getPointCut(){}Before(savePointCut()||deletePointCut()||modifyPointCut())public void beforeAdvice(JoinPoint joinPoint){SimpleDateFormat simpleDateFormatnew SimpleDateFormat(yyyy-MM-dd HH:mm:ss SSS);String nowTime simpleDateFormat.format(new Date());//输出日志信息System.out.println(nowTime zhangsanjoinPoint.getSignature().getDeclaringTypeName().joinPoint.getSignature().getName());}
}测试程序 Testpublic void testSecurityLog(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(spring.xml);UserService userService applicationContext.getBean(userService, UserService.class);VipService vipService applicationContext.getBean(vipService, VipService.class);userService.saveUser();userService.deleteUser();userService.modifyUser();userService.getUser();vipService.saveVip();vipService.deleteVip();vipService.modifyVip();vipService.getVip();}
运行结果 十六、Spring对事务的支持
16.1 事务概述
什么是事务 在一个业务流程当中通常需要多条DMLinsert delete update语句共同联合才能完成这多条DML语句必须同时成功或者同时失败这样才能保证数据的安全。多条DML要么同时成功要么同时失败这叫做事务。事务Transactiontx
事务的四个处理过程 第一步开启事务 (start transaction)第二步执行核心业务代码第三步提交事务如果核心业务处理过程中没有出现异常(commit transaction)第四步回滚事务如果核心业务处理过程中出现异常(rollback transaction)
事务的四个特性 A 原子性事务是最小的工作单元不可再分。C 一致性事务要求要么同时成功要么同时失败。事务前和事务后的总量不变。I 隔离性事务和事务之间因为有隔离性才可以保证互不干扰。D 持久性持久性是事务结束的标志。
16.2 引入事务场景
以银行账户转账为例学习事务。两个账户act-001和act-002。act-001账户向act-002账户转账10000必须同时成功或者同时失败。一个减成功一个加成功 这两条update语句必须同时成功或同时失败。
连接数据库的技术采用Spring框架的JdbcTemplate。
采用三层架构搭建 模块名spring6-013-tx-bank依赖如下
pom.xml
?xml version1.0 encodingUTF-8?
project xmlnshttp://maven.apache.org/POM/4.0.0xmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexsi:schemaLocationhttp://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsdmodelVersion4.0.0/modelVersiongroupIdcom.dong/groupIdartifactIdspring6-013-tx-bank/artifactIdversion1.0-SNAPSHOT/versionpackagingjar/packaging!--依赖--dependencies!--spring context依赖--dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion6.0.11/version/dependency!--spring aspects依赖--dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion6.0.11/version/dependency!--junit依赖--dependencygroupIdjunit/groupIdartifactIdjunit/artifactIdversion4.13.2/versionscopetest/scope/dependency!--spring jdbc--dependencygroupIdorg.springframework/groupIdartifactIdspring-jdbc/artifactIdversion6.0.11/version/dependency!--mysql驱动--dependencygroupIdmysql/groupIdartifactIdmysql-connector-java/artifactIdversion8.0.33/version/dependency!--德鲁伊连接池--dependencygroupIdcom.alibaba/groupIdartifactIddruid/artifactIdversion1.2.16/version/dependency!--Resource注解--dependencygroupIdjakarta.annotation/groupIdartifactIdjakarta.annotation-api/artifactIdversion2.1.1/version/dependency/dependenciespropertiesmaven.compiler.source19/maven.compiler.sourcemaven.compiler.target19/maven.compiler.targetproject.build.sourceEncodingUTF-8/project.build.sourceEncoding/properties/project 第一步准备数据库表
表结构 表数据 第二步创建包结构
com.dong.bank.pojo
com.dong.bank.service
com.dong.bank.service.impl
com.dong.bank.dao
com.dong.bank.dao.impl
第三步准备POJO类
package com.dong.bank.pojo;public class Account {private String actno;private double balance;Overridepublic String toString() {return Account{ actno actno \ , balance balance };}public Account() {}public Account(String actno, double balance) {this.actno actno;this.balance balance;}public String getActno() {return actno;}public void setActno(String actno) {this.actno actno;}public double getBalance() {return balance;}public void setBalance(double balance) {this.balance balance;}
}第四步编写持久层
package com.dong.bank.dao;import com.dong.bank.pojo.Account;public interface AccountDao {/*** 根据账号查询账户信息* param actno* return*/Account selectByActNo(String actno);/*** 更新账户信息* param account* return*/int update(Account account);
}package com.dong.bank.dao.impl;import com.dong.bank.dao.AccountDao;
import com.dong.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;Repository(accountDao)
public class AccountDaoImpl implements AccountDao {Resource(name jdbcTemplate)private JdbcTemplate jdbcTemplate;Overridepublic Account selectByActNo(String actno) {String sqlselect actno, balance from t_act where actno?;Account account jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper(Account.class), actno);return account;}Overridepublic int update(Account account) {String sqlupdate t_act set balance? where actno?;int count jdbcTemplate.update(sql, account.getBalance(), account.getActno());return count;}
}第五步编写业务层
package com.dong.bank.service;/*** 业务接口* 事务就是在这个接口下控制的*/
public interface AccountService {/*** 转账业务方法* param fromActno 从这个账户转出* param toActno 转入这个账户* param money 转账金额*/void transfer(String fromActno,String toActno,double money);}package com.dong.bank.service.impl;import com.dong.bank.dao.AccountDao;
import com.dong.bank.pojo.Account;
import com.dong.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;Service(accountService)
public class AccountServiceImpl implements AccountService {Resource(name accountDao)private AccountDao accountDao;//控制事务因为在这个方法中要完成所有的转账业务Overridepublic void transfer(String fromActno, String toActno, double money) {//查询转出账户的余额是否充足Account fromAct accountDao.selectByActNo(fromActno);if (fromAct.getBalance()money){throw new RuntimeException(余额不足);}//余额充足Account toAct accountDao.selectByActNo(toActno);//将内存中的两个对象的余额先修改fromAct.setBalance(fromAct.getBalance()-money);toAct.setBalance( toAct .getBalance()money);//数据库更新int count accountDao.update(fromAct);countaccountDao.update(toAct);if (count!2){throw new RuntimeException(转账失败请联系银行);}}
}第六步编写Spring配置文件
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd!--组件扫描--context:component-scan base-packagecom.dong.bank/!--配置数据源--bean iddataSource classcom.alibaba.druid.pool.DruidDataSourceproperty namedriverClassName valuecom.mysql.cj.jdbc.Driver/property nameurl valuejdbc:mysql://localhost:3306/spring6/property nameusername valueroot/property namepassword value15088143946Sld//bean!--配置jdbcTemplate--bean idjdbcTemplate classorg.springframework.jdbc.core.JdbcTemplateproperty namedataSource refdataSource//bean/beans第七步编写表示层测试程序
代码
package com.dong.bank.test;import com.dong.bank.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringTxTest {Testpublic void testSpringTx(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(spring.xml);AccountService accountService applicationContext.getBean(accountService, AccountService.class);try {accountService.transfer(act-001, act-002, 10000);System.out.println(转账成功。);}catch (Exception e){e.printStackTrace();System.out.println(转账失败。);}}
}执行结果 数据变化 模拟异常
package com.dong.bank.service.impl;import com.dong.bank.dao.AccountDao;
import com.dong.bank.pojo.Account;
import com.dong.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;Service(accountService)
public class AccountServiceImpl implements AccountService {Resource(name accountDao)private AccountDao accountDao;//控制事务因为在这个方法中要完成所有的转账业务TransactionalOverridepublic void transfer(String fromActno, String toActno, double money) {//第一步:开启事务//第二步:执行核心业务逻辑//查询转出账户的余额是否充足Account fromAct accountDao.selectByActNo(fromActno);if (fromAct.getBalance()money){throw new RuntimeException(余额不足);}//余额充足Account toAct accountDao.selectByActNo(toActno);//将内存中的两个对象的余额先修改fromAct.setBalance(fromAct.getBalance()-money);toAct.setBalance(toAct.getBalance()money);//数据库更新int count accountDao.update(fromAct);countaccountDao.update(toAct);//模拟异常String snull;s.toString();if (count!2){throw new RuntimeException(转账失败请联系银行);}//第三步如果执行业务流程过程中没有异常。提交事务//第四步:如果执行业务流程过程中有异常回滚事务}
}执行结果 数据库表中数据 转出成功但是转入失败 不同步更新 丢了1万
16.3 Spring对事务的支持
Spring实现事务的两种方式 编程式事务 通过编写代码的方式来实现事务的管理。 声明式事务 基于注解方式基于XML配置方式
Spring事务管理API
Spring对事务的管理底层实现方式是基于AOP实现的。采用AOP的方式进行了封装。所以Spring专门针对事务开发了一套APIAPI的核心接口如下 PlatformTransactionManager接口spring事务管理器的核心接口。在Spring6中它有两个实现
DataSourceTransactionManager支持JdbcTemplate、MyBatis、Hibernate等事务管理。JtaTransactionManager支持分布式事务管理。
如果要在Spring6中使用JdbcTemplate就要使用DataSourceTransactionManager来管理事务。Spring内置写好了可以直接用。
声明式事务之注解实现方式
第一步在spring配置文件中配置事务管理器。第二步在spring配置文件中引入tx命名空间。第三步在spring配置文件中配置“事务注解驱动器”开始注解的方式控制事务。第四步在service类上或方法上添加Transactional注解
在类上添加该注解该类中所有的方法都有事务。在某个方法上添加该注解表示只有这个方法使用事务。
代码有异常
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:txhttp://www.springframework.org/schema/txxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd!--组件扫描--context:component-scan base-packagecom.dong.bank/!--配置数据源--bean iddataSource classcom.alibaba.druid.pool.DruidDataSourceproperty namedriverClassName valuecom.mysql.cj.jdbc.Driver/property nameurl valuejdbc:mysql://localhost:3306/spring6/property nameusername valueroot/property namepassword value15088143946Sld//bean!--配置jdbcTemplate--bean idjdbcTemplate classorg.springframework.jdbc.core.JdbcTemplateproperty namedataSource refdataSource//bean!--配置事务管理器--bean idtxManager classorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refdataSource//bean!--开启事务注解驱动器开启事务注解。告诉spring框架采用注解的方式控制事务。--tx:annotation-driven transaction-managertxManager//beanspackage com.dong.bank.service.impl;import com.dong.bank.dao.AccountDao;
import com.dong.bank.pojo.Account;
import com.dong.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;Service(accountService)
public class AccountServiceImpl implements AccountService {Resource(name accountDao)private AccountDao accountDao;//控制事务因为在这个方法中要完成所有的转账业务TransactionalOverridepublic void transfer(String fromActno, String toActno, double money) {//第一步:开启事务//第二步:执行核心业务逻辑//查询转出账户的余额是否充足Account fromAct accountDao.selectByActNo(fromActno);if (fromAct.getBalance()money){throw new RuntimeException(余额不足);}//余额充足Account toAct accountDao.selectByActNo(toActno);//将内存中的两个对象的余额先修改fromAct.setBalance(fromAct.getBalance()-money);toAct.setBalance(toAct.getBalance()money);//数据库更新int count accountDao.update(fromAct);countaccountDao.update(toAct);//模拟异常
/* String snull;s.toString();*/if (count!2){throw new RuntimeException(转账失败请联系银行);}//第三步如果执行业务流程过程中没有异常。提交事务//第四步:如果执行业务流程过程中有异常回滚事务}
}package com.dong.bank.test;import com.dong.bank.service.AccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class SpringTxTest {Testpublic void testSpringTx(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(spring.xml);AccountService accountService applicationContext.getBean(accountService, AccountService.class);try {accountService.transfer(act-001, act-002, 10000);System.out.println(转账成功。);}catch (Exception e){e.printStackTrace();System.out.println(转账失败。);}}
}运行结果 一分钱没丢 虽然出现异常了再次查看数据库表中数据 假如没有异常 package com.dong.bank.service.impl;import com.dong.bank.dao.AccountDao;
import com.dong.bank.pojo.Account;
import com.dong.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;Service(accountService)
public class AccountServiceImpl implements AccountService {Resource(name accountDao)private AccountDao accountDao;//控制事务因为在这个方法中要完成所有的转账业务TransactionalOverridepublic void transfer(String fromActno, String toActno, double money) {//第一步:开启事务//第二步:执行核心业务逻辑//查询转出账户的余额是否充足Account fromAct accountDao.selectByActNo(fromActno);if (fromAct.getBalance()money){throw new RuntimeException(余额不足);}//余额充足Account toAct accountDao.selectByActNo(toActno);//将内存中的两个对象的余额先修改fromAct.setBalance(fromAct.getBalance()-money);toAct.setBalance(toAct.getBalance()money);//数据库更新int count accountDao.update(fromAct);countaccountDao.update(toAct);//模拟异常
/* String snull;s.toString();*/if (count!2){throw new RuntimeException(转账失败请联系银行);}//第三步如果执行业务流程过程中没有异常。提交事务//第四步:如果执行业务流程过程中有异常回滚事务}
}运行结果 数据同步更新变化 事务属性
事务属性包括哪些 事务中的重点属性
事务传播行为事务隔离级别事务超时只读事务设置出现哪些异常回滚事务设置出现哪些异常不回滚事务
事务传播行为
什么是事务的传播行为
在service类中有a()方法和b()方法a()方法上有事务b()方法上也有事务当a()方法执行过程中调用了b()方法事务是如何传递的合并到一个事务里还是开启一个新的事务这就是事务传播行为。
事务传播行为在spring框架中被定义为枚举类型 一共有七种传播行为
REQUIRED支持当前事务如果不存在就新建一个(默认)【没有就新建有就加入】SUPPORTS支持当前事务如果当前没有事务就以非事务方式执行【有就加入没有就不管了】MANDATORY必须运行在一个事务中如果当前没有事务正在发生将抛出一个异常【有就加入没有就抛异常】REQUIRES_NEW开启一个新的事务如果一个事务已经存在则将这个存在的事务挂起【不管有没有直接开启一个新事务开启的新事务和之前的事务不存在嵌套关系之前事务被挂起】NOT_SUPPORTED以非事务方式运行如果有事务存在挂起当前事务【不支持事务存在就挂起】NEVER以非事务方式运行如果有事务存在抛出异常【不支持事务存在就抛异常】NESTED如果当前正有一个事务在进行中则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在行为就像REQUIRED一样。【有事务的话就在这个事务里再嵌套一个完全独立的事务嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
在代码中设置事务的传播行为
Transactional(propagation Propagation.REQUIRED)
可以编写程序测试一下传播行为
1号service
Transactional(propagation Propagation.REQUIRED)
public void save(Account act) {// 这里调用dao的insert方法。accountDao.insert(act); // 保存act-003账户// 创建账户对象Account act2 new Account(act-004, 1000.0);try {accountService.save(act2); // 保存act-004账户} catch (Exception e) {}// 继续往后进行我当前1号事务自己的事儿。
}
2号service
Override
//Transactional(propagation Propagation.REQUIRED)
Transactional(propagation Propagation.REQUIRES_NEW)
public void save(Account act) {accountDao.insert(act);// 模拟异常String s null;s.toString();// 事儿没有处理完这个大括号当中的后续也许还有其他的DML语句。
}
一定要集成Log4j2日志框架在日志信息中可以看到更加详细的信息。
事务隔离级别
事务隔离级别类似于教室A和教室B之间的那道墙隔离级别越高表示墙体越厚。隔音效果越好。
数据库中读取数据存在的三大问题三大读问题
脏读读取到没有提交到数据库的数据叫做脏读。不可重复读在同一个事务当中第一次和第二次读取的数据不一样。幻读读到的数据是假的。
事务隔离级别包括四个级别
读未提交READ_UNCOMMITTED 这种隔离级别存在脏读问题所谓的脏读(dirty read)表示能够读取到其它事务未提交的数据。
读提交READ_COMMITTED 解决了脏读问题其它事务提交之后才能读到但存在不可重复读问题。
可重复读REPEATABLE_READ 解决了不可重复读可以达到可重复读效果只要当前事务不结束读取到的数据一直都是一样的。但存在幻读问题。
序列化SERIALIZABLE 解决了幻读问题事务排队执行。不支持并发。
大家可以通过一个表格来记忆 测试读未提交 isolation Isolation.READ_UNCOMMITTED
代码
package com.dong.bank.service.impl;import com.dong.bank.dao.AccountDao;
import com.dong.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;Service(i1)
public class IsolationService1 {Resource(name accountDao)private AccountDao accountDao;//1号//负责查询//当前事务能够读取到别的事务没有提交的数据Transactional(isolation Isolation.READ_UNCOMMITTED)public void getByActno(String actno){Account account accountDao.selectByActNo(actno);System.out.println(查询到的账户信息为account);}
}package com.dong.bank.service.impl;import com.dong.bank.dao.AccountDao;
import com.dong.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;Service(i2)
public class IsolationService2 {Resource(name accountDao)private AccountDao accountDao;//2号//负责插入Transactionalpublic void save(Account account){accountDao.insert(account);//睡眠一会try {Thread.sleep(1000*20);} catch (InterruptedException e) {e.printStackTrace();}}
}Testpublic void testIsolation1(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(spring.xml);IsolationService1 i1 applicationContext.getBean(i1, IsolationService1.class);i1.getByActno(act-004);}Testpublic void testIsolation2(){ApplicationContext applicationContextnew ClassPathXmlApplicationContext(spring.xml);IsolationService2 i2 applicationContext.getBean(i2, IsolationService2.class);Account accountnew Account(act-004,1000);i2.save(account);}
运行结果 测试读提交 Transactional(isolation Isolation.READ_COMMITTED)
代码
package com.dong.bank.service.impl;import com.dong.bank.dao.AccountDao;
import com.dong.bank.pojo.Account;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;Service(i1)
public class IsolationService1 {Resource(name accountDao)private AccountDao accountDao;//1号//负责查询//当前事务能够读取到别的事务没有提交的数据// Transactional(isolation Isolation.READ_UNCOMMITTED)//对方事务提交之后的数据我们才能读取得到Transactional(isolation Isolation.READ_COMMITTED)public void getByActno(String actno){Account account accountDao.selectByActNo(actno);System.out.println(查询到的账户信息为account);}
}运行结果 事务超时
代码如下
Transactional(timeout 10)
以上代码表示设置事务的超时时间为10秒。
表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话最终结果会选择回滚。
默认值-1表示没有时间限制。
这里有个坑事务的超时时间指的是哪段时间
在当前事务当中最后一条DML语句执行之前的时间。如果最后一条DML语句后面很有很多业务逻辑这些业务代码执行的时间不被计入超时时间。 以下代码的超时不会被计入超时时间
Transactional(timeout 10) // 设置事务超时时间为10秒。
public void save(Account act) {accountDao.insert(act);// 睡眠一会try {Thread.sleep(1000 * 15);} catch (InterruptedException e) {e.printStackTrace();}
}
以下代码超时时间会被计入超时时间
Transactional(timeout 10) // 设置事务超时时间为10秒。
public void save(Account act) {// 睡眠一会try {Thread.sleep(1000 * 15);} catch (InterruptedException e) {e.printStackTrace();}accountDao.insert(act);
}
当然如果想让整个方法的所有代码都计入超时时间的话可以在方法最后一行添加一行无关紧要的DML语句。
只读事务 代码如下 Transactional(readOnly true)
将当前事务设置为只读事务在该事务执行过程中只允许select语句执行delete insert update均不可执行。
该特性的作用是启动spring的优化策略。提高select语句执行效率。
如果该事务中确实没有增删改操作建议设置为只读事务。
设置哪些异常回滚事务
代码如下
Transactional(rollbackFor RuntimeException.class)
表示只有发生RuntimeException异常或该异常的子类异常才回滚。
设置哪些异常不回滚事务
代码如下
Transactional(noRollbackFor NullPointerException.class)
表示发生NullPointerException或该异常的子类异常不回滚其他异常则回滚。
事务的全注解式开发
代码
package com.dong.bank;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;import javax.sql.DataSource;Configuration //代替spring.xml配置文件在这个类中完成配置
ComponentScan(com.dong.bank) //组件扫描
EnableTransactionManagement //开启事务注解驱动器开启事务注解。
public class Spring6Config {//Spring 框架看到这个Bean 注解后会调用这个被标注的方法这个方法返回值是一个java对象这个Java对象会自动纳入IOC容器管理。//返回的对象就是spring容器中的一个Bean了//并且这个bean的名字是dataSourceBean(name dataSource)public DruidDataSource getDataSource(){DruidDataSource dataSourcenew DruidDataSource();dataSource.setDriverClassName(com.mysql.cj.jdbc.Driver);dataSource.setUrl(jdbc:mysql://localhost:3306/spring6);dataSource.setUsername(root);dataSource.setPassword(15088143946Sld);return dataSource;}Bean(name jdbcTemplate)public JdbcTemplate getJdbcTemplate(DataSource dataSource){ //spring在调用这个方法的时候会自动给我们传递过来一个dataSource对象JdbcTemplate jdbcTemplatenew JdbcTemplate();jdbcTemplate.setDataSource(dataSource);return jdbcTemplate;}Bean(name txManager)public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){DataSourceTransactionManager txManagernew DataSourceTransactionManager();txManager.setDataSource(dataSource);return txManager;}
}Testpublic void testNoXML(){ApplicationContext applicationContextnew AnnotationConfigApplicationContext(Spring6Config.class);AccountService accountService applicationContext.getBean(accountService, AccountService.class);try {accountService.transfer(act-001, act-002, 10000);System.out.println(转账成功。);}catch (Exception e){e.printStackTrace();System.out.println(转账失败。);}}
运行结果 模拟异常看事务是否起作用
代码
在 AccountServiceImpl 类中添加
//模拟异常 String snull; s.toString();
package com.dong.bank.service.impl;import com.dong.bank.dao.AccountDao;
import com.dong.bank.pojo.Account;
import com.dong.bank.service.AccountService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;Service(accountService)
public class AccountServiceImpl implements AccountService {Resource(name accountDao)private AccountDao accountDao;//控制事务因为在这个方法中要完成所有的转账业务TransactionalOverridepublic void transfer(String fromActno, String toActno, double money) {//第一步:开启事务//第二步:执行核心业务逻辑//查询转出账户的余额是否充足Account fromAct accountDao.selectByActNo(fromActno);if (fromAct.getBalance()money){throw new RuntimeException(余额不足);}//余额充足Account toAct accountDao.selectByActNo(toActno);//将内存中的两个对象的余额先修改fromAct.setBalance(fromAct.getBalance()-money);toAct.setBalance(toAct.getBalance()money);//数据库更新int count accountDao.update(fromAct);countaccountDao.update(toAct);//模拟异常String snull;s.toString();if (count!2){throw new RuntimeException(转账失败请联系银行);}//第三步如果执行业务流程过程中没有异常。提交事务//第四步:如果执行业务流程过程中有异常回滚事务}
}运行结果 数据没有变化-----事务起作用了。 声明式事务之XML实现方式
配置步骤
第一步配置事务管理器第二步配置通知第三步配置切面
记得添加aspectj的依赖
pom.xml
!--aspectj依赖--
dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion6.0.11/version
/dependency
Spring配置文件如下
记得添加aop的命名空间。
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:txhttp://www.springframework.org/schema/txxmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsdcontext:component-scan base-packagecom.powernode.bank/bean iddataSource classcom.alibaba.druid.pool.DruidDataSourceproperty namedriverClassName valuecom.mysql.cj.jdbc.Driver/property nameurl valuejdbc:mysql://localhost:3306/spring6/property nameusername valueroot/property namepassword valueroot//beanbean idjdbcTemplate classorg.springframework.jdbc.core.JdbcTemplateproperty namedataSource refdataSource//bean!--配置事务管理器--bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManagerproperty namedataSource refdataSource//bean!--配置通知--tx:advice idtxAdvice transaction-managertxManagertx:attributestx:method namesave* propagationREQUIRED rollback-forjava.lang.Throwable/tx:method namedel* propagationREQUIRED rollback-forjava.lang.Throwable/tx:method nameupdate* propagationREQUIRED rollback-forjava.lang.Throwable/tx:method nametransfer* propagationREQUIRED rollback-forjava.lang.Throwable//tx:attributes/tx:advice!--配置切面--aop:configaop:pointcut idtxPointcut expressionexecution(* com.powernode.bank.service..*(..))/!--切面 通知 切点--aop:advisor advice-reftxAdvice pointcut-reftxPointcut//aop:config/beans
将AccountServiceImpl类上的Transactional注解删除。
编写测试程序
Test
public void testTransferXml(){ApplicationContext applicationContext new ClassPathXmlApplicationContext(spring2.xml);AccountService accountService applicationContext.getBean(accountService, AccountService.class);try {accountService.transfer(act-001, act-002, 10000);System.out.println(转账成功);} catch (Exception e) {e.printStackTrace();}
}
执行结果 数据库表中记录 通过测试可以看到配置XML已经起作用了。