Spring框架(三)

1、代理模式:

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

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

1、静态代理:

初始:

package com.songzhishu.proxy.service;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.proxy.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  11:49
 * @Description: TODO
 * @Version: 1.0
 */
public class OrderServiceImpl  implements  OrderService{
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {
        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }
}

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

方法一:硬编码

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

方法二:继承重写方法

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

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

方法三:静态代

package com.songzhishu.proxy.service;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.proxy.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  12:50
 * @Description: 代理对象
 * @Version: 1.0
 */
public class OrderServiceProxy implements OrderService {
    //要包含公共的功能 达到和目标对象一样的功能 要执行目标对象中目标方法

    //将目标对象作为代理对象的一个属性
    private  OrderService target;  //使用这种方式要比继承的耦合度低  注入公共接口要比实现类好

    public OrderServiceProxy(OrderService target) {
        //通过构造方法赋值
        this.target = target;
    }

    @Override
    public void generate() {
        //使用代理方法添加增强功能
        long begin = System.currentTimeMillis();

        target.generate();
        //调用目标对象目标功能
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void modify() {
        //使用代理方法添加增强功能
        long begin = System.currentTimeMillis();

        //调用目标对象目标功能
        target.modify();
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }

    @Override
    public void detail() {
        //使用代理方法添加增强功能
        long begin = System.currentTimeMillis();

        //调用目标对象目标功能
        target.detail();
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");
    }
}

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

2、动态代理:

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

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

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

公共接口:

package com.songzhishu.spring6.service;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.proxy.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  11:45
 * @Description: 公共接口
 * @Version: 1.0
 */
public interface OrderService {
    //生成订单
    void generate();

    //修改订单
    void modify();

    //查看订单
    void detail();

    //获得名字
    String getName();
}

实现类:

package com.songzhishu.spring6.service;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.proxy.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  11:49
 * @Description: TODO
 * @Version: 1.0
 */
public class OrderServiceImpl implements  OrderService{
    @Override
    public void generate() {
        try {
            Thread.sleep(1234);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已生成");
    }

    @Override
    public void detail() {
        try {
            Thread.sleep(2541);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单信息如下:******");
    }

    @Override
    public void modify() {

        try {
            Thread.sleep(1010);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("订单已修改");
    }

    @Override
    public String getName() {
        System.out.println("getName方法执行");
        return "张胜男";
    }
}

客户端:

package com.songzhishu.spring6.client;

import com.songzhishu.spring6.service.OrderService;
import com.songzhishu.spring6.service.OrderServiceImpl;
import com.songzhishu.spring6.service.TimeInvocationHandler;
import com.songzhishu.spring6.utils.ProxyUtil;

import java.lang.reflect.Proxy;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.spring6.client
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  15:45
 * @Description: TODO
 * @Version: 1.0
 */
public class ClientTest {
    public static void main(String[] args) {
        //创建目标对象
        OrderService target =new OrderServiceImpl();

        //创建代理对象
        /*
        *   Proxy.newProxyInstance(类加载器,代理类要实现的接口,调用处理器);
        *   第一步  在内存中创建一个代理类的字节码文件
        *   第二步  通过代理类来实例化代理类对象
        *
        *   参数一  ClassLoader loader
        *          类加载器:内存中和硬盘上的class其实没有太大区别,都是class文件,都要加载到java的虚拟机中才能运行
        *                  注意:目标类的类加载器和代理类的加载器要使用的是同一个
        *
        *   参数二  Class<?>[] interfaces
        *           代理类和目标类要实现同一个或者同一些接口
        *
        *   参数三  InvocationHandler h  调用处理器类 实现一个接口
        *           然后可以编写增强代码
        * */

        //使用啦util工具
        OrderService proxy = (OrderService) ProxyUtil.newProxyInstance(target);

        //使用代理对象调用代理方法, 如果增强的话,目标方法需要执行
        proxy.generate();
        proxy.detail();
        proxy.modify();
        String name = proxy.getName();
        System.out.println(name);
    }
}

调用处理器:

package com.songzhishu.spring6.service;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.spring6.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  16:24
 * @Description: 调用处理器,用来计时增强功能
 * @Version: 1.0
 */
public class TimeInvocationHandler implements InvocationHandler {
    //目标对象
    private  Object target;
    //构造方法 目标对象
    public TimeInvocationHandler(Object target) {
        //给目标对象赋值
        this.target=target;
    }

    /*
    *   invoke方法什么时候调用 只有代理对象调用代理方法的时候,注册在InvocationHandler调用处理器当中的invoke()方法就
    *   会被调用
    *
    *   invoke方法里面的参数
    *         参数一    代理对象
    *         参数二    目标对象的目标方法
    *         参数三    目标方法上的实参
    *
    *
    * */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //使用代理方法添加增强功能
        long begin = System.currentTimeMillis();

        Object revalue = method.invoke(target, args);
        //调用目标对象目标功能
        long end = System.currentTimeMillis();
        System.out.println("耗费时长"+(end - begin)+"毫秒");

        //如果代理对象需要返回值的话,invoke方法必须将目标对象的目标方法的执行结果返回
        return revalue;  //返回方法的返回值!!!!!
    }
}

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配置文件:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

        <!--组件扫描-->
        <context:component-scan base-package="com.songzhishu.spring6.service"/>

        <!--开启自动代理 proxy-target-class="true" 表示强制实现cglib来生成动态代理 false表示如果接口使用jdk 如果是类使用cglib-->
        <aop:aspectj-autoproxy proxy-target-class="true" />
</beans>

目标类:

 切面:

好啦这就是大概的流程

 2、通知类型顺序:

通知类型包括:

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

切面:

package com.songzhishu.spring6.service;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

/**
 * @BelongsProject: Spring6
 * @BelongsPackage: com.songzhishu.spring6.service
 * @Author: 斗痘侠
 * @CreateTime: 2023-10-17  20:14
 * @Description: TODO
 * @Version: 1.0
 */
@Component("logAspect")
@Aspect //这个注解表示是一个切面 ,如果没有这个注解就不是切面
public class LogAspect { //切面
    //切面等于通知+切点

    /*
    通知是以方法的形式出现 (方法可以写增强代码)
        @before(切点表达式)表示是一个前置通知,然后切点表达式就是可以表示要切入的方法
    */
    @Before("execution(* com.songzhishu.spring6.service..*(..))")
    public void before() {
        System.out.println("前置");
    }

    //后置
    @AfterReturning("execution(* com.songzhishu.spring6.service..*(..))")
    public void afterReturningAdvice() {
        System.out.println("后置");
    }

    //环绕 是最大的通知, 在前置之前 在后置之后
    @Around("execution(* com.songzhishu.spring6.service..*(..))")
    public void surround(ProceedingJoinPoint joinPoint) throws Throwable {
        //前
        System.out.println("前环绕");
        //目标方法
        joinPoint.proceed();
        //后
        System.out.println("后环绕");
    }

    //异常通知
    @AfterThrowing("execution(* com.songzhishu.spring6.service..*(..))")
    public  void afterThrowing(){
        System.out.println("异常通知");
    }


    //最终通知 finally
    @After("execution(* com.songzhishu.spring6.service..*(..))")
    public void ultimately(){
        System.out.println("最终");
    }
}

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

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

 

3、切面顺序:

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

  • 优先级高的切面:外面

  • 优先级低的切面:里面

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

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

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

 4、重用切入点表达式:

定义切点:

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

 同切面重用:

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

 不同的切面重用:

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

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

①获取连接点信息

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

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

 ②获取目标方法的返回值

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

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

③获取目标方法的异常

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

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

 6、全注解开发:

使用配置类代替配置文件

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

测试:

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

7、基于xml的方式实现:

<context:component-scan base-package="com.atguigu.aop.xml"></context:component-scan>

<aop:config>
    <!--配置切面类-->
    <aop:aspect ref="loggerAspect">
        <aop:pointcut id="pointCut" 
                   expression="execution(* com.atguigu.aop.xml.CalculatorImpl.*(..))"/>
        <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
        <aop:after method="afterMethod" pointcut-ref="pointCut"></aop:after>
        <aop:after-returning method="afterReturningMethod" returning="result" pointcut-ref="pointCut"></aop:after-returning>
        <aop:after-throwing method="afterThrowingMethod" throwing="ex" pointcut-ref="pointCut"></aop:after-throwing>
        <aop:around method="aroundMethod" pointcut-ref="pointCut"></aop:around>
    </aop:aspect>
</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、声明式事务之注解实现方式:

配置文件:

<?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:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--扫描组件-->
    <context:component-scan base-package="com.songzhishu.spring6"/>


    <!--配置jdbcTemplate文件-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="username" value="${jdbc.username}"></property>
    </bean>
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>

    <!--配置事务管理器-->
    <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--数据源-->
        <property name="dataSource" ref="druidDataSource"></property>
    </bean>

    <!--事务注解驱动器  使用注解的方式来控制事务-->
    <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>

</beans>
  • 第一步:在spring配置文件中配置事务管理器。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
</bean>
  • 第二步:在spring配置文件中引入tx命名空间。
<?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:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           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的方式实现事务

<!-- 切面 -->
<aop:config>
    <!-- 配置事务通知和切入点表达式 -->
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.spring.tx.xml.service.impl.*.*(..))"></aop:advisor>
</aop:config>

<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- tx:method标签:配置具体的事务方法 -->
        <!-- name属性:指定方法名,可以使用星号代表多个字符 -->
        <tx:method name="get*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="find*" read-only="true"/>
    
        <!-- read-only属性:设置只读属性 -->
        <!-- rollback-for属性:设置回滚的异常 -->
        <!-- no-rollback-for属性:设置不回滚的异常 -->
        <!-- isolation属性:设置事务的隔离级别 -->
        <!-- timeout属性:设置事务的超时属性 -->
        <!-- propagation属性:设置事务的传播行为 -->
        <tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
    </tx:attributes>
</tx:advice>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值