• Spring框架(三)


    1、代理模式:

            二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

            为什么使用代理模式:可以增强功能,可以保护代理目标,可以让两个不能直接交互的目标进行交互。

    1、静态代理:

    初始:

    1. package com.songzhishu.proxy.service;
    2. /**
    3. * @BelongsProject: Spring6
    4. * @BelongsPackage: com.songzhishu.proxy.service
    5. * @Author: 斗痘侠
    6. * @CreateTime: 2023-10-17 11:49
    7. * @Description: TODO
    8. * @Version: 1.0
    9. */
    10. public class OrderServiceImpl implements OrderService{
    11. @Override
    12. public void generate() {
    13. try {
    14. Thread.sleep(1234);
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. System.out.println("订单已生成");
    19. }
    20. @Override
    21. public void detail() {
    22. try {
    23. Thread.sleep(2541);
    24. } catch (InterruptedException e) {
    25. e.printStackTrace();
    26. }
    27. System.out.println("订单信息如下:******");
    28. }
    29. @Override
    30. public void modify() {
    31. try {
    32. Thread.sleep(1010);
    33. } catch (InterruptedException e) {
    34. e.printStackTrace();
    35. }
    36. System.out.println("订单已修改");
    37. }
    38. }

    现在想统计每一个模块的耗时,怎么办,直接在原来的代码上修改!

    方法一:硬编码

    这样可以是可以,但是每一个模块都要写重复的代码,而且违背OCP的开闭原则!

    方法二:继承重写方法

    创建一个子类,然后继承实现类后重写方法也可以实现功能的拓展。

    这种解决了问题,没有违背OCP原则,但是使用继承增强了耦合度 

    方法三:静态代

    1. package com.songzhishu.proxy.service;
    2. /**
    3. * @BelongsProject: Spring6
    4. * @BelongsPackage: com.songzhishu.proxy.service
    5. * @Author: 斗痘侠
    6. * @CreateTime: 2023-10-17 12:50
    7. * @Description: 代理对象
    8. * @Version: 1.0
    9. */
    10. public class OrderServiceProxy implements OrderService {
    11. //要包含公共的功能 达到和目标对象一样的功能 要执行目标对象中目标方法
    12. //将目标对象作为代理对象的一个属性
    13. private OrderService target; //使用这种方式要比继承的耦合度低 注入公共接口要比实现类好
    14. public OrderServiceProxy(OrderService target) {
    15. //通过构造方法赋值
    16. this.target = target;
    17. }
    18. @Override
    19. public void generate() {
    20. //使用代理方法添加增强功能
    21. long begin = System.currentTimeMillis();
    22. target.generate();
    23. //调用目标对象目标功能
    24. long end = System.currentTimeMillis();
    25. System.out.println("耗费时长"+(end - begin)+"毫秒");
    26. }
    27. @Override
    28. public void modify() {
    29. //使用代理方法添加增强功能
    30. long begin = System.currentTimeMillis();
    31. //调用目标对象目标功能
    32. target.modify();
    33. long end = System.currentTimeMillis();
    34. System.out.println("耗费时长"+(end - begin)+"毫秒");
    35. }
    36. @Override
    37. public void detail() {
    38. //使用代理方法添加增强功能
    39. long begin = System.currentTimeMillis();
    40. //调用目标对象目标功能
    41. target.detail();
    42. long end = System.currentTimeMillis();
    43. System.out.println("耗费时长"+(end - begin)+"毫秒");
    44. }
    45. }

            静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。

    2、动态代理:

            程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题。

    在内存当中动态生成类的技术常见的包括:

    • JDK动态代理技术:只能代理接口。
    • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
    • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。

    公共接口:

    1. package com.songzhishu.spring6.service;
    2. /**
    3. * @BelongsProject: Spring6
    4. * @BelongsPackage: com.songzhishu.proxy.service
    5. * @Author: 斗痘侠
    6. * @CreateTime: 2023-10-17 11:45
    7. * @Description: 公共接口
    8. * @Version: 1.0
    9. */
    10. public interface OrderService {
    11. //生成订单
    12. void generate();
    13. //修改订单
    14. void modify();
    15. //查看订单
    16. void detail();
    17. //获得名字
    18. String getName();
    19. }

    实现类:

    1. package com.songzhishu.spring6.service;
    2. /**
    3. * @BelongsProject: Spring6
    4. * @BelongsPackage: com.songzhishu.proxy.service
    5. * @Author: 斗痘侠
    6. * @CreateTime: 2023-10-17 11:49
    7. * @Description: TODO
    8. * @Version: 1.0
    9. */
    10. public class OrderServiceImpl implements OrderService{
    11. @Override
    12. public void generate() {
    13. try {
    14. Thread.sleep(1234);
    15. } catch (InterruptedException e) {
    16. e.printStackTrace();
    17. }
    18. System.out.println("订单已生成");
    19. }
    20. @Override
    21. public void detail() {
    22. try {
    23. Thread.sleep(2541);
    24. } catch (InterruptedException e) {
    25. e.printStackTrace();
    26. }
    27. System.out.println("订单信息如下:******");
    28. }
    29. @Override
    30. public void modify() {
    31. try {
    32. Thread.sleep(1010);
    33. } catch (InterruptedException e) {
    34. e.printStackTrace();
    35. }
    36. System.out.println("订单已修改");
    37. }
    38. @Override
    39. public String getName() {
    40. System.out.println("getName方法执行");
    41. return "张胜男";
    42. }
    43. }

    客户端:

    1. package com.songzhishu.spring6.client;
    2. import com.songzhishu.spring6.service.OrderService;
    3. import com.songzhishu.spring6.service.OrderServiceImpl;
    4. import com.songzhishu.spring6.service.TimeInvocationHandler;
    5. import com.songzhishu.spring6.utils.ProxyUtil;
    6. import java.lang.reflect.Proxy;
    7. /**
    8. * @BelongsProject: Spring6
    9. * @BelongsPackage: com.songzhishu.spring6.client
    10. * @Author: 斗痘侠
    11. * @CreateTime: 2023-10-17 15:45
    12. * @Description: TODO
    13. * @Version: 1.0
    14. */
    15. public class ClientTest {
    16. public static void main(String[] args) {
    17. //创建目标对象
    18. OrderService target =new OrderServiceImpl();
    19. //创建代理对象
    20. /*
    21. * Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器);
    22. * 第一步 在内存中创建一个代理类的字节码文件
    23. * 第二步 通过代理类来实例化代理类对象
    24. *
    25. * 参数一 ClassLoader loader
    26. * 类加载器:内存中和硬盘上的class其实没有太大区别,都是class文件,都要加载到java的虚拟机中才能运行
    27. * 注意:目标类的类加载器和代理类的加载器要使用的是同一个
    28. *
    29. * 参数二 Class[] interfaces
    30. * 代理类和目标类要实现同一个或者同一些接口
    31. *
    32. * 参数三 InvocationHandler h 调用处理器类 实现一个接口
    33. * 然后可以编写增强代码
    34. * */
    35. //使用啦util工具
    36. OrderService proxy = (OrderService) ProxyUtil.newProxyInstance(target);
    37. //使用代理对象调用代理方法, 如果增强的话,目标方法需要执行
    38. proxy.generate();
    39. proxy.detail();
    40. proxy.modify();
    41. String name = proxy.getName();
    42. System.out.println(name);
    43. }
    44. }

    调用处理器:

    1. package com.songzhishu.spring6.service;
    2. import java.lang.reflect.InvocationHandler;
    3. import java.lang.reflect.Method;
    4. /**
    5. * @BelongsProject: Spring6
    6. * @BelongsPackage: com.songzhishu.spring6.service
    7. * @Author: 斗痘侠
    8. * @CreateTime: 2023-10-17 16:24
    9. * @Description: 调用处理器,用来计时增强功能
    10. * @Version: 1.0
    11. */
    12. public class TimeInvocationHandler implements InvocationHandler {
    13. //目标对象
    14. private Object target;
    15. //构造方法 目标对象
    16. public TimeInvocationHandler(Object target) {
    17. //给目标对象赋值
    18. this.target=target;
    19. }
    20. /*
    21. * invoke方法什么时候调用 只有代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法就
    22. * 会被调用
    23. *
    24. * invoke方法里面的参数
    25. * 参数一 代理对象
    26. * 参数二 目标对象的目标方法
    27. * 参数三 目标方法上的实参
    28. *
    29. *
    30. * */
    31. @Override
    32. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    33. //使用代理方法添加增强功能
    34. long begin = System.currentTimeMillis();
    35. Object revalue = method.invoke(target, args);
    36. //调用目标对象目标功能
    37. long end = System.currentTimeMillis();
    38. System.out.println("耗费时长"+(end - begin)+"毫秒");
    39. //如果代理对象需要返回值的话,invoke方法必须将目标对象的目标方法的执行结果返回
    40. return revalue; //返回方法的返回值!!!!!
    41. }
    42. }

    2、面向切面编程AOP:

            AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现,在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

            Spring的AOP使用的动态代理是:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB。当然,你也可以强制通过一些配置让Spring只使用CGLIB。

            一般一个系统当中都会有一些系统服务,例如:日志、事务管理、安全等。这些系统服务被称为:交叉业务      核心业务是纵向的!这些交叉业务几乎是通用的,不管你是做银行账户转账,还是删除用户数据。日志、事务管理、安全,这些都是需要做的。

    如果在每一个业务处理过程当中,都掺杂这些交叉业务代码进去的话,存在两方面问题:

    • 第一:交叉业务代码在多个业务流程中反复出现,显然这个交叉业务代码没有得到复用。并且修改这些交叉业务代码的话,需要修改多处。
    • 第二:程序员无法专注核心业务代码的编写,在编写核心业务代码的同时还需要处理这些交叉业务。

            用一句话总结AOP:将与核心业务无关的代码独立的抽取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOP。

    AOP的优点:

    • 第一:代码复用性增强。
    • 第二:代码易维护。
    • 第三:使开发者更关注业务逻辑。

    1、AOP的七大术语:

    • 连接点 Joinpoint在程序的整个执行流程中,可以织入切面的位置。方法的执行前后,异常抛出之后等位置。 
    • 切点 Pointcut在程序执行流程中,真正织入切面的方法。(一个切点对应多个连接点),本质上就是方法 
    • 通知 Advice通知又叫增强,就是具体你要织入的代码 ,连接点的位置,通知包含:
    1. 前置通知
    2. 最终通知
    3. 异常通知
    4. 环绕通知
    5. 后置通知
    • 切面 Aspect:切点 + 通知就是切面。
    • 织入 Weaving: 把通知应用到目标对象上的过程。
    • 代理对象 Proxy:一个目标对象被织入通知后产生的新对象。
    • 目标对象 Target:被织入通知的对象。

    通过下图,大家可以很好的理解AOP的相关术语:

    2、切点表达式:

    切点表达式用来定义通知(Advice)往哪些方法上切入。语法格式:

    execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

    访问控制权限修饰符:可选项

    • 没写,就是4个权限都包括。
    • 写public就表示只包括公开的方法。

    返回值类型:必填项

    • * 表示返回值类型任意。

    全限定类名:可选项

    • 两个点“..”代表当前包以及子包下的所有类。
    • 省略时表示所有的类。

    方法名:必填项

    • *表示所有方法。
    • set*表示所有的set方法。

    形式参数列表:必填项

    • () 表示没有参数的方法
    • (..) 参数类型和个数随意的方法
    • (*) 只有一个参数的方法
    • (*, String) 第一个参数类型随意,第二个参数是String的。

    异常:可选项

    • 省略时表示任意异常类型。

    3、使用Spring的AOP:

    Spring对AOP的实现包括以下3种方式:

    • 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
    • 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
    • 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。

     1、基于注解:

    spring配置文件:

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xmlns:aop="http://www.springframework.org/schema/aop"
    6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    7. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    8. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    9. <context:component-scan base-package="com.songzhishu.spring6.service"/>
    10. <aop:aspectj-autoproxy proxy-target-class="true" />
    11. beans>

    目标类:

     切面:

    好啦这就是大概的流程

     2、通知类型顺序:

    通知类型包括:

    • 前置通知:@Before 目标方法执行之前的通知
    • 后置通知:@AfterReturning 目标方法执行之后的通知
    • 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知。
    • 异常通知:@AfterThrowing 发生异常之后执行的通知
    • 最终通知:@After 放在finally语句块中的通知

    切面:

    1. package com.songzhishu.spring6.service;
    2. import org.aspectj.lang.ProceedingJoinPoint;
    3. import org.aspectj.lang.annotation.*;
    4. import org.springframework.stereotype.Component;
    5. import org.springframework.stereotype.Service;
    6. /**
    7. * @BelongsProject: Spring6
    8. * @BelongsPackage: com.songzhishu.spring6.service
    9. * @Author: 斗痘侠
    10. * @CreateTime: 2023-10-17 20:14
    11. * @Description: TODO
    12. * @Version: 1.0
    13. */
    14. @Component("logAspect")
    15. @Aspect //这个注解表示是一个切面 ,如果没有这个注解就不是切面
    16. public class LogAspect { //切面
    17. //切面等于通知+切点
    18. /*
    19. 通知是以方法的形式出现 (方法可以写增强代码)
    20. @before(切点表达式)表示是一个前置通知,然后切点表达式就是可以表示要切入的方法
    21. */
    22. @Before("execution(* com.songzhishu.spring6.service..*(..))")
    23. public void before() {
    24. System.out.println("前置");
    25. }
    26. //后置
    27. @AfterReturning("execution(* com.songzhishu.spring6.service..*(..))")
    28. public void afterReturningAdvice() {
    29. System.out.println("后置");
    30. }
    31. //环绕 是最大的通知, 在前置之前 在后置之后
    32. @Around("execution(* com.songzhishu.spring6.service..*(..))")
    33. public void surround(ProceedingJoinPoint joinPoint) throws Throwable {
    34. //前
    35. System.out.println("前环绕");
    36. //目标方法
    37. joinPoint.proceed();
    38. //后
    39. System.out.println("后环绕");
    40. }
    41. //异常通知
    42. @AfterThrowing("execution(* com.songzhishu.spring6.service..*(..))")
    43. public void afterThrowing(){
    44. System.out.println("异常通知");
    45. }
    46. //最终通知 finally
    47. @After("execution(* com.songzhishu.spring6.service..*(..))")
    48. public void ultimately(){
    49. System.out.println("最终");
    50. }
    51. }

            然后这个是没有出现异常的时候的顺序!

    然后我手动的扔出来一个异常后的执行顺序!

     

    3、切面顺序:

    相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。

    • 优先级高的切面:外面

    • 优先级低的切面:里面

    使用@Order注解可以控制切面的优先级:

    • @Order(较小的数):优先级高

    • @Order(较大的数):优先级低

     4、重用切入点表达式:

    定义切点:

    1. //切点重用:
    2. @Pointcut("execution(* com.songzhishu.spring6.service..*(..))")
    3. public void pointCut(){}

     同切面重用:

    1. @Before("pointCut()")
    2. public void before() {
    3. System.out.println("日志前置");
    4. }

     不同的切面重用:

    1. @Before("com.songzhishu.spring6.service.LogAspect.pointCut()1")
    2. public void beforeAdvice(){
    3. System.out.println("安全前置通知");
    4. }

     5、获取通知的相关信息:

    ①获取连接点信息

    获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参

    1. @Before("execution(public int com.atguigu.aop.annotation.CalculatorImpl.*(..))")
    2. public void beforeMethod(JoinPoint joinPoint){
    3. //获取连接点的签名信息
    4. String methodName = joinPoint.getSignature().getName();
    5. //获取目标方法到的实参信息
    6. String args = Arrays.toString(joinPoint.getArgs());
    7. System.out.println("Logger-->前置通知,方法名:"+methodName+",参数:"+args);
    8. }

     ②获取目标方法的返回值

    @AfterReturning中的属性returning,用来将通知方法的某个形参,接收目标方法的返回值

    1. @AfterReturning(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", returning = "result")
    2. public void afterReturningMethod(JoinPoint joinPoint, Object result)
    3. {    String methodName = joinPoint.getSignature().getName();  
    4.  System.out.println("Logger-->返回通知,方法名:"+methodName+",结果:"+result);}

    ③获取目标方法的异常

    @AfterThrowing中的属性throwing,用来将通知方法的某个形参,接收目标方法的异常

    1. @AfterThrowing(value = "execution(* com.atguigu.aop.annotation.CalculatorImpl.*(..))", throwing = "ex")
    2. public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){
    3. String methodName = joinPoint.getSignature().getName();
    4. System.out.println("Logger-->异常通知,方法名:"+methodName+",异常:"+ex);
    5. }

     6、全注解开发:

    使用配置类代替配置文件

    1. @Configuration//代替配置文件
    2. @ComponentScan({"com.songzhishu.spring6.service"})//组件扫描
    3. @EnableAspectJAutoProxy(proxyTargetClass = true)//启用自动代理
    4. public class SpringConfig {
    5. }

    测试:

    1. //全注解
    2. @Test
    3. public void test2(){
    4. ApplicationContext applicationContext=new AnnotationConfigApplicationContext(SpringConfig.class);
    5. UserService bean = applicationContext.getBean("userService", UserService.class);
    6. bean.login();
    7. }

    7、基于xml的方式实现:

    1. <context:component-scan base-package="com.atguigu.aop.xml">context:component-scan>
    2. <aop:config>
    3. <aop:aspect ref="loggerAspect">
    4. <aop:pointcut id="pointCut"
    5. expression="execution(* com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
    6. <aop:before method="beforeMethod" pointcut-ref="pointCut">aop:before>
    7. <aop:after method="afterMethod" pointcut-ref="pointCut">aop:after>
    8. <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointCut">aop:after-returning>
    9. <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut">aop:after-throwing>
    10. <aop:around method="aroundMethod" pointcut-ref="pointCut">aop:around>
    11. aop:aspect>
    12. aop:config>

    4、AOP的事务处理:

    什么是事务:

    • 在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。
    • 多条DML要么同时成功,要么同时失败,这叫做事务。
    • 事务:Transaction(tx)

    事务的四个处理过程:

    • 第一步:开启事务 (start transaction)
    • 第二步:执行核心业务代码
    • 第三步:提交事务(如果核心业务处理过程中没有出现异常)(commit transaction)
    • 第四步:回滚事务(如果核心业务处理过程中出现异常)(rollback transaction)

    事务的四个特性:

    • A 原子性:事务是最小的工作单元,不可再分。
    • C 一致性:事务要求要么同时成功,要么同时失败。事务前和事务后的总量不变。
    • I 隔离性:事务和事务之间因为有隔离性,才可以保证互不干扰。
    • D 持久性:持久性是事务结束的标志。

    Spring实现事务的两种方式:

            编程式事务:通过编写代码的方式来实现事务的管理。

            声明式事务:(1)基于注解方式(2)基于XML配置方式

    1、声明式事务之注解实现方式:

    配置文件:

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xmlns:tx="http://www.springframework.org/schema/tx"
    6. xmlns:aop="http://www.springframework.org/schema/aop"
    7. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    8. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    9. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
    10. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    11. <context:component-scan base-package="com.songzhishu.spring6"/>
    12. <context:property-placeholder location="jdbc.properties">context:property-placeholder>
    13. <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    14. <property name="driverClassName" value="${jdbc.driver}">property>
    15. <property name="url" value="${jdbc.url}">property>
    16. <property name="password" value="${jdbc.password}">property>
    17. <property name="username" value="${jdbc.username}">property>
    18. bean>
    19. <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    20. <property name="dataSource" ref="druidDataSource">property>
    21. bean>
    22. <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    23. <property name="dataSource" ref="druidDataSource">property>
    24. bean>
    25. <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
    26. beans>
    • 第一步:在spring配置文件中配置事务管理器。
    1. <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    2. <property name="dataSource" ref="dataSource"/>
    3. bean>
    • 第二步:在spring配置文件中引入tx命名空间。
    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:context="http://www.springframework.org/schema/context"
    5. xmlns:tx="http://www.springframework.org/schema/tx"
    6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    7. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
    8. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    • 第三步:在spring配置文件中配置“事务注解驱动器”,开始注解的方式控制事务。
    <tx:annotation-driven transaction-manager="transactionManager"/>
    • 第四步:在service类上或方法上添加@Transactional注解

            在类上添加该注解,该类中所有的方法都有事务。在某个方法上添加该注解,表示只有这个方法使用事务。

    2、事务中的重点属性:

    • 事务传播行为
    • 事务隔离级别
    • 事务超时
    • 只读事务
    • 设置出现哪些异常回滚事务
    • 设置出现哪些异常不回滚事务
     (1)事务传播行为:

            在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

    事务传播行为在spring框架中被定义为枚举类型:

            

    一共有七种传播行为:

    • REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】

    • SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】

    • MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】

    • REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】

    • NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】

    • NEVER:以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】

    • NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

    3、事务的隔离级别:

    隔离级别在spring中以枚举类型存在:

    @Transactional(isolation = Isolation.READ_COMMITTED)

    数据库中读取数据存在的三大问题:(三大读问题)

    • 脏读:读取到没有提交到数据库的数据,叫做脏读。(读的是缓存
    • 不可重复读:在同一个事务当中,第一次和第二次读取的数据不一样。
    • 幻读:读到的数据是假的。

    事务隔离级别包括四个级别:

    读未提交:READ_UNCOMMITTED

      • 这种隔离级别,存在脏读问题,所谓的脏读(dirty read)表示能够读取到其它事务未提交的数据。

    读提交:READ_COMMITTED

      • 解决了脏读问题,其它事务提交之后才能读到,但存在不可重复读问题。

    可重复读:REPEATABLE_READ

      • 解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据一直都是一样的。但存在幻读问题。

    序列化:SERIALIZABLE

      • 解决了幻读问题,事务排队执行。不支持并发。

    大家可以通过一个表格来记忆:

    隔离级别

    脏读

    不可重复读

    幻读

    读未提交

    读提交

    可重复读

    序列化

     4、事务超时:

    @Transactional(timeout = 10)

    以上代码表示设置事务的超时时间为10秒。

            表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。

    默认值-1,表示没有时间限制。

            这里有个坑,事务的超时时间指的是哪段时间?(最后一条DML之前的时间!!!)

            在当前事务当中,最后一条DML语句执行之前的时间。如果最后一条DML语句后面很有很多业务逻辑,这些业务代码执行的时间不被计入超时时间。

    5、只读事务:

    @Transactional(readOnly = true)

    将当前事务设置为只读事务,在该事务执行过程中只允许select语句执行,delete insert update均不可执行。

    该特性的作用是:启动spring的优化策略。提高select语句执行效率。

    如果该事务中确实没有增删改操作,建议设置为只读事务。

    6、设置哪些异常回滚事务

    默认的是只要有异常就会回滚事务,设置运行时异常以及子类都会回滚!!

    @Transactional(rollbackFor = RuntimeException.class)

    7、设置哪些异常不回滚事务

    表示发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。

    @Transactional(noRollbackFor = NullPointerException.class)

    5、基于XML的方式实现事务

    1. <aop:config>
    2. <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.spring.tx.xml.service.impl.*.*(..))">aop:advisor>
    3. aop:config>
    4. <tx:advice id="txAdvice" transaction-manager="transactionManager">
    5. <tx:attributes>
    6. <tx:method name="get*" read-only="true"/>
    7. <tx:method name="query*" read-only="true"/>
    8. <tx:method name="find*" read-only="true"/>
    9. <tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
    10. <tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
    11. <tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
    12. tx:attributes>
    13. tx:advice>

  • 相关阅读:
    《大数据面试通关》(第十四讲)——10 大业务场景 500 个离线实时指标
    非关系型数据库技术课程 第二周作业(Redis中字符串和列表数据类型练习)
    【Tools】如何在VSCode上使用C++ 保姆教程
    MySQL中特别实用的几种SQL语句
    谷歌计划从Chrome119起测试IP隐私保护功能
    管理需因人而异,因时而变
    18.Redis系列之AOF方式持久化
    什么品牌的台灯护眼比较好?推荐护眼效果好的护眼灯品牌
    CSS多行省略的几种方法
    Java简系 - Java入门「一」
  • 原文地址:https://blog.csdn.net/keleID/article/details/133880288