JavaWeb学习——事务管理、AOP学习

目录

一、事务管理

1、事务回顾

 2、事务进阶

a、rollbackFor属性

b、propagation属性

二、AOP学习

1、基础了解

2、AOP进阶


一、事务管理

1、事务回顾

        事务的概念:事务必须服从ACID原则。ACID指的是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。通俗理解,事务其实就是一系列指令的集合。

        事务的特性

        原子性:操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
        一致性:事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
        隔离性:在该事务执行的过程中,无论发生的任何数据的改变都应该只存在于该事务之中,对外界不存在任何影响。只有在事务确定正确提交之后,才会显示该事务对数据的改变。其他事务才能获取到这些改变后的数据。
        持久性:当事务正确完成后,它对于数据的改变是永久性的。

下面提供一个事务操作发生的图进行述说:

        感想:其实联系我之前学习的MySql知识,可以发现此事务就是当时学习到的事务概念,主要的是对数据库操作进行一个集合类的统合,实现数据的同步修改性。

接下来举一个案例来说明使用事务的原因

        分析:上图是一个对部门表实现删除部门的代码案例,首先它能实现基础的连带部门信息和部门人员信息的删除,但当代码执行过程中出现异常,如上图设置的1/0,就会中断代码执行,在前端表现就是只删除了部门信息,数据库里也是如此,它们不能一致的发生删除或不删除,由此我们就要利用到事务来进行管理,实现同步。

使用注释,进行事务集合使用(@Transactional)

        分析:由图可知该注释可以使用在方法、类、接口上,自由度很高,而要解决上面提到的问题,只要在方法上实现就行。

补充:spring事务管理日志依赖添加

        添加原因:Spring 事务管理是 Spring 框架中一个重要的模块,它提供了强大的事务管理能力,使得我们可以对事务操作进行细粒度的控制。

        添加代码

#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

 2、事务进阶

a、rollbackFor属性

        作用:在 Spring 数据访问操作中,默认情况下只有未检查(unchecked)异常(继承自 RuntimeException 的异常)或者 Error 会导致事务回滚,而受检查的异常(即非 RuntimeException 异常)则不会导致事务回滚。rollbackFor 属性就是用来解决这个问题的,它可以指定受检查的异常也可以导致事务回滚。你可以设置任何你希望导致此行为的异常类型。

代码示例

 @Transactional(rollbackFor = Exception.class) //spring事务管理
    //@Transactional
    @Override
    public void delete(Integer id) throws Exception {
        try {
            deptMapper.deleteById(id); //根据ID删除部门数据

            int i = 1/0;
            //if(true){throw new Exception("出错啦...");}

            empMapper.deleteByDeptId(id); //根据部门ID删除该部门下的员工
        } finally {
            DeptLog deptLog = new DeptLog();
            deptLog.setCreateTime(LocalDateTime.now());
            deptLog.setDescription("执行了解散部门的操作,此次解散的是"+id+"号部门");
            deptLogService.insert(deptLog);
        }
    }

        分析:上图代码的效果仍是删除部门相关信息,只是最后的完成版,在这我们不用太在意另外的代码,只用看@Transactional后面括号里的rollbackFor使用,这里是指定所有的异常,我们也可以换另外的指定异常类型。

b、propagation属性

        作用propagation 是 Spring 的 @Transactional 注解的一个属性,它用于指定事务的传播行为。事务的传播行为决定了在方法被调用时,事务是如何沿着调用链传播的。

使用类型

  1. REQUIRED(Propagation.REQUIRED):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认的设置。

  2. SUPPORTS(Propagation.SUPPORTS):如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

  3. MANDATORY(Propagation.MANDATORY):如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

  4. REQUIRES_NEW(Propagation.REQUIRES_NEW):创建一个新的事务,如果当前存在事务,则把当前事务挂起。

  5. NOT_SUPPORTED(Propagation.NOT_SUPPORTED):以非事务方式运行操作,如果当前存在事务,就把当前事务挂起。

  6. NEVER(Propagation.NEVER):以非事务方式运行,如果当前存在事务,则抛出异常。

  7. NESTED(Propagation.NESTED):如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED

使用视图

 

        感想:这一属性的使用就是可以自由设置当多重事务关联触发时,我们来决定它们的运行顺序和存在周期。

二、AOP学习

1、基础了解

AOP的概述

        AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,旨在促进更高效、组织性更好的代码。这种模式主要聚焦于程序逻辑的横向关注点,也就是那些几乎出现在所有层级代码中的公共功能(例如日志、事务管理等),而这种功能在没有 AOP 的情况下,往往需要重复编写,这会导致代码冗余。

以下是 AOP 的主要概念

  1. Aspect(切面):一个模块,具有一组提供公共功能的 APIs。在 AOP 中,切面用于封装每个横切关注点的逻辑,这些逻辑可以在运行时动态地应用到不同的对象和函数上。

  2. Join Point(连接点):程序执行过程中的某个特定点,如方法被调用时、异常被抛出时等。在 Spring AOP 中,一个连接点总是代表一个方法的执行。

  3. Advice(通知):是切面必须完成的工作,也就是切面的具体行为。根据其所在位置,通知的类型有 before、after、around、afterThrowing、afterReturning 等。

  4. Pointcut(切入点):匹配连接点的规则或概述。在 Spring AOP 中,切入点可以使用 AspectJ 的切入点表达式语言来指定。

  5. Target Object(目标对象):被一个或多个切面所通知的对象。

  6. Weaving(织入):将切面和其它应用类型或者对象连接起来,创建一个被通知的对象。这可以在编译时(使用 AspectJ 编译器),类加载时,或在运行时完成。

        Spring AOP 框架将这些概念与 Spring 的 IoC(Inversion of Control,控制反转)容器和 Spring 提供的其它功能无缝集成,从而提供了一个完整、易用,而且与 Java 语言和 Spring 框架高度集成的 AOP 实现。

应用场景

        AOP(面向切面编程)在许多编程场景中都能发挥重要作用,主要因为它有助于代码的解耦和重复代码的减少。以下是一些 AOP 常见的应用场景:

  1. 日志记录:通过把日志记录的功能放到切面中处理,可以简化主要业务逻辑代码的清晰度,并且可以非常方便地控制日志记录的粒度和范围。

  2. 事务管理:在许多企业级应用中,事务管理是一项关键任务。AOP 可以简化事务操作,通过预定义的策略,使开发者可以透明地管理和配置事务。

  3. 性能监控:AOP 可以用于收集方法调用的统计信息,例如:方法调用的次数以及每次调用的持续时间等。这些信息可以用于后续的性能分析和优化。

  4. 安全检查:可以用AOP实现统一的安全检查,避免在每个需要进行安全检查的方法中单独实现。

  5. 错误处理:可以通过 AOP 对所有方法进行统一的错误捕获和处理,减少重复的错误处理代码。

  6. 缓存:可以通过 AOP 实现方法结果的缓存,提高系统的性能。

  7. 验证和格式化:AOP 可以用于验证对象的状态或格式化对象的输出,将这些横切关注点从主要任务中分离出来。

以上是 AOP 最常见的应用场景,但是并不受此限。

Spring AOP与动态代理

Spring AOP(面向切面编程)在它的底层实现上主要使用了动态代理。

        动态代理是一种设计模式,主要是用于接口的非入侵式代理,不改变类文件和原有业务逻辑的情况下,增加额外的功能。Java的java.lang.reflect包中提供的Proxy类和InvocationHandler接口,就是用于生成动态代理类和处理代理方法调用的。

Spring AOP 使用了两种类型的动态代理:

1. **JDK动态代理**:基于接口的动态代理,JDK动态代理通过实现InvocationHandler接口,并重写invoke方法来处理代理逻辑。如果目标对象有接口,Spring AOP会选择使用JDK动态代理。

2. **CGLIB代理**:基于类的动态代理,CGLIB通过生成目标类的子类来处理代理逻辑。如果目标对象没有实现任何接口,Spring AOP会选择使用CGLIB代理。

在Spring AOP中,切面的逻辑(例如事务管理、日志记录等)通常会写在Advice中,然后这些通知会被动态织入到你指定的方法上,这样在调用这些方法时,就会执行对应的通知逻辑。

        总的来说,动态代理构成了Spring AOP的核心,Spring通过动态代理技术实现了其强大的面向切面编程功能。

举例说明(统计各个业务层方法执行耗时)

在实现AOP的使用前,我们要加入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

 编写的AOP使用案例代码(实现统计执行耗时)

package com.sunny.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect //AOP类
public class TimeAspect {

    @Around("execution(* com.sunny.service.impl.DeptServiceImpl.*(..))") //切入点表达式
    //@Around("com.sunny.aop.MyAspect1.pt()")
    public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
        //1. 记录开始时间
        long begin = System.currentTimeMillis();

        //2. 调用原始方法运行
        Object result = joinPoint.proceed();

        //3. 记录结束时间, 计算方法执行耗时
        long end = System.currentTimeMillis();
        log.info(joinPoint.getSignature()+"方法执行耗时: {}ms", end-begin);

        return result;
    }

}

AOP的主要概念联系

这里提到的主要概念就是上面的主要概念,接下来举一个例子来进行说明。

以下是这些概念在一次典型的AOP操作中如何协同工作的示例:

  1. 定义一个Aspect,例如日志切面,将日志记录通知(Advice)与切入点(Pointcut,例如全部的服务操作方法)关联起来。

  2. 当程序执行到切入点的方法时,即触发了连接点(Join point),例如调用服务操作。

  3. 由于切面的存在,切入点的方法会被切面中的通知Advice捕获,这时候,日志切面的日志记录代码就在目标方法执行之前或之后执行,这个过程就是织入(Weaving)。

        总结起来,AOP的核心概念关联使用,可以使系统的服务模块关注于核心业务,而像日志,事务,安全等服务可以通过AOP的方式进行模块化和集中管理,提高了系统的可维护性和可重用性。

补充:Advice的使用类型

有以下几种类型的advice:

  1. Before advice:在切入点选择的连接点之前执行的通知(比如,一个特定的方法调用之前)。
  2. After returning advice:在切入点选择的连接点成功完成后,例如,方法成功返回结果后,执行的通知。
  3. After throwing advice:在方法抛出异常退出时执行的通知。
  4. After (finally) advice:当切入点结束后执行的通知,无论方法退出是正常还是异常返回。
  5. Around advice:包围着被通知的方法,可以在被通知的方法前后自定义行为。

        这些advice中,我们自定义的代码就是那些横切关注点的实现, 例如在方法调用前记录日志(Before advice),在方法异常时发送通知(After throwing advice)等。

下面给些代码案例

package com.sunny.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
//@Aspect
public class MyAspect1 {

    @Pointcut("execution(* com.sunny.service.impl.DeptServiceImpl.*(..))")
    //使用切入,指定表达式
    public void pt(){}

    @Before("pt()")//Advice使用
    public void before(){
        log.info("before ...");
    }

    @Around("pt()")//Advice使用
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before ...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();

        log.info("around after ...");
        return result;
    }

    @After("pt()")//Advice使用
    public void after(){
        log.info("after ...");
    }

    @AfterReturning("pt()")//Advice使用
    public void afterReturning(){
        log.info("afterReturning ...");
    }

    @AfterThrowing("pt()")//Advice使用
    public void afterThrowing(){
        log.info("afterThrowing ...");
    }
}

2、AOP进阶

@Around的使用补充

@Pointcut的使用图示

多个切点的通知顺序分析(多个切面的切入点匹配到了相同的目标方法)

切入点表达式学习

常见形式

execution详细使用

代码示例

@Pointcut("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
    @Pointcut("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
    @Pointcut("execution(void delete(java.lang.Integer))") //包名.类名不建议省略
    @Pointcut("execution(void com.itheima.service.DeptService.delete(java.lang.Integer))")

    @Pointcut("execution(void com.itheima.service.DeptService.*(java.lang.Integer))")
    @Pointcut("execution(* com.*.service.DeptService.*(*))")
    @Pointcut("execution(* com.itheima.service.*Service.delete*(*))")

    @Pointcut("execution(* com.itheima.service.DeptService.*(..))")
    @Pointcut("execution(* com..DeptService.*(..))")
    @Pointcut("execution(* com..*.*(..))")
    @Pointcut("execution(* *(..))") //慎用

annotation详细使用

代码示例

package com.sunny.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 创建名为MyLog的切入点
 */
@Retention(RetentionPolicy.RUNTIME) //描述注解什么时候使用到
@Target(ElementType.METHOD) //描述注解的使用对象——方法
public @interface MyLog {
}


@MyLog //加入设定的annotation注解
    @Override
    public List<Dept> list() {
        List<Dept> deptList = deptMapper.list();
        return deptList;
    }

    @MyLog //加入设定的annotation注解
    @Override
    public void delete(Integer id) {
        //1. 删除部门
        deptMapper.delete(id);
    }

连接点使用

代码案例

package com.sunny.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
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.util.Arrays;

//切面类
@Slf4j
@Aspect
@Component
public class MyAspect8 {

    @Pointcut("execution(* com.sunny.service.DeptService.*(..))")
    private void pt(){}

    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info("MyAspect8 ... before ...");
    }

    @Around("pt()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("MyAspect8 around before ...");

        //1. 获取 目标对象的类名 .
        String className = joinPoint.getTarget().getClass().getName();
        log.info("目标对象的类名:{}", className);

        //2. 获取 目标方法的方法名 .
        String methodName = joinPoint.getSignature().getName();
        log.info("目标方法的方法名: {}",methodName);

        //3. 获取 目标方法运行时传入的参数 .
        Object[] args = joinPoint.getArgs();
        log.info("目标方法运行时传入的参数: {}", Arrays.toString(args));

        //4. 放行 目标方法执行 .
        Object result = joinPoint.proceed();

        //5. 获取 目标方法运行的返回值 .
        log.info("目标方法运行的返回值: {}",result);

        log.info("MyAspect8 around after ...");
        return result;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值