1.事务管理

1.1、事务回顾

  • 是一组操作的集合,是一个不可分割的工作单位,这些操作要么全部成功,要么全部失败
  • 操作
    • 开启事务:strat transaction
    • 提交事务 commit
    • 回滚事务 rollback

1.2、Spring事务管理

  • 案例:解散部门:删除部门同时删除该部门下的员工

  • DeptServiceImpl

@Override
public void deleteDept(Integer id) throws Exception {
    deptMapper.deleteDept(id); //根据ID删除部门
    int a = 1/0;
    empMapper.deleteEmpByDeptId(id); //根据部门ID删除该部门下的所有员工
}

即使程序运行抛出了异常,部门依然删除了,但是部门下的员工却没有删除,造成了数据的不一致

  • @Transactional
    • 位置:业务层的方法上、类上、接口上
    • 作用:将当前方法交给spring进行事务管理,方法执行前,开启事务;成功执行,提交事务,出现异常,回滚事务
  • DeptServiceImpl
@Transactional
@Override
public void deleteDept(Integer id) throws Exception {
    deptMapper.deleteDept(id); //根据ID删除部门
    int a = 1/0;
    empMapper.deleteEmpByDeptId(id); //根据部门ID删除该部门下的所有员工
}

1.3、事务进阶

1.3.1、rollbackFor

  • 默认情况下,只有出现 RunTimeException 才会回滚异常。roallbackFor属性用于控制出现何种异常类型,都回滚事务
@Transactional(rollbackFor = Exception.class); //所有异常都回滚

1.3.2、propagation

  • 事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制

image-20230810234953746.png

案例:解散部门时,记录操作日志(无论如何都执行该日志信息)

  • DeptLogMapper
@Mapper
public interface DeptLogMapper {
    @Insert("insert into dept_log(create_time, description) values (now(),#{description})")
    void insertDeptLog(DeptLog deptLog);
}
  • DeptLogService
public interface DeptLogService {
    void insertDeptLog(DeptLog deptLog);
}
  • DeptLogServiceImpl
@Service
public class DeptLogServiceImpl implements DeptLogService {
    @Autowired
    private DeptLogMapper deptLogMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void insertDeptLog(DeptLog deptLog) {
        deptLogMapper.insertDeptLog(deptLog);
    }
}
  • DeptServiceImpl
@Transactional(rollbackFor = Exception.class)
@Override
public void deleteDept(Integer id) throws Exception {
    try {
        deptMapper.deleteDept(id); //根据ID删除部门

        int a = 1/0;
        //            if(true){
        //                throw new Exception("出现错误");
        //            }
        empMapper.deleteEmpByDeptId(id); //根据部门ID删除该部门下的所有员工
    } finally {
        DeptLog deptLog = new DeptLog();
        deptLog.setDescription("执行了解散部门操作,解散了"+id+"号部门");
        deptLogService.insertDeptLog(deptLog);
    }
}
  • REQUIRED
    • 大部分情况下都是用该传播行为即可(默认)
  • REQUIRES_NEW
    • 当我们不希望事物之间互相影响时,可以使用该传播行为。例如:下订单前需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功

2.AOP基础

2.1、AOP概述

  • AOP:Aspect Oriented Programming(面向切面编程、面向方面编程),就是面向特定方法编程
  • 场景:
    • 案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时
  • 实现:
    • 动态代理是面向前面编程最主流的实现,而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程

2.2、AOP快速入门

  • 导入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
  • 编写AOP程序:针对方法的业务进行编程【记录方法运行耗时】
@Component
@Aspect  //标识为AOP类
@Slf4j
public class TimeAspect {
    //@Around("execution(* com.mhn.service.*.*(..))") //切入点表达式
    @Around("com.mhn.aop.MyAspect.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()+"方法执行完成,耗时:"+(end - begin)+"ms");
        return result;
    }
}
  • 场景
    • 记录操作日志
    • 权限控制
    • 事务管理
  • 优势
    • 代码无侵入
    • 减少重复代码
    • 提高开发效率
    • 维护方便

2.3、AOP核心概念

  • 连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
  • 通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)
  • 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
  • 切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
  • 目标对象:Target,通知所应用的对象

image-20230810235008622.png

  • 执行流程

image-20230810235014230.png

【当执行joinPointProceed()方法时,意为创建了代理对象,而控制层中依赖注入的dpetService,运行时注入的会是创建的代理对象】

3.AOP进阶

3.1、通知类型

  • 通知类型
    • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都会被执行【重点掌握】
    • @Before:前置通知
    • @After:后置通知
    • @AfterReturning:返回后通知
    • @AfterThrowing:异常后通知
  • 注意事项
    • @Around环绕通知需要字节调用 ProceedingJoinPoint.proceed()来让原始方法执行,其他通知不需要考虑目标方法执行
      • @Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值
@Slf4j
@Component
@Aspect
public class MyAspect {
    @Before("execution(* com.mhn.service.serviceImpl.DeptServiceImpl.*(..))")
    public void before(){
        log.info("-----before-----");
    }
    @Around("execution (* com.mhn.service.serviceImpl.DeptServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        log.info("-----around before-----");

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

        log.info("-----around after-----");
        return result;
    }
    @After("execution(* com.mhn.service.serviceImpl.DeptServiceImpl.*(..))")
    public void after(){
        log.info("-----after-----");
    }
    @AfterReturning("execution(* com.mhn.service.serviceImpl.DeptServiceImpl.*(..))")
    public void afterReturning(){
        log.info("-----afterReturning-----");
    }
    @AfterThrowing("execution(* com.mhn.service.serviceImpl.DeptServiceImpl.*(..))")
    public void afterThrowing(){
        log.info("-----afterThrowing-----");
    }
}
  • @Pointcut
    • 该注解的作用是将公共的切点表达式抽取出来,需要用时,引用该切点表达式即可
@Pointcut("execution(* com.mhn.service.serviceImpl.DeptServiceImpl.*(..))")
public void pt(){}

@Before("pt()")
public void before(){
    log.info("-----before-----");
}

注意,标注@Pointcut注解的该方法修饰符会有所影响:private(仅在当前切面类中引用该表达式)public(在其他外部的切面类中也可以引用该表达式)

3.2、通知顺序

当有多个切面的切入点都匹配到了目标方法,目标方法运行时,多个通知方法都会被执行

  • 执行顺序

    • 不同切面类中,默认按照切面类的类名字母排序
      • 目标方法前的通知方法:字母排名靠前的先执行
      • 目标方法后的通知方法:字母排名靠后的先执行
    • 使用@Order(数字)注解来标注先后次序
      • 方法前的通知方法:数字较小的,先执行
      • 方法后的通知方法:数字较大的,后执行
  • MyAspect3

@Order(3)
@Slf4j
@Component
@Aspect
public class MyAspect3 {
    @Before("execution(* com.mhn.service.DeptService.*(..))")
    public void before(){
        log.info("----before...3----");
    }

    @After("execution(* com.mhn.service.DeptService.*(..))")
    public void after(){
        log.info("----after...3----");
    }
}
  • MyAspect4
@Slf4j
@Component
@Aspect
@Order(1)
public class MyAspect4 {
    @Before("execution(* com.mhn.service.DeptService.*(..))")
    public void before(){
        log.info("----before...4----");
    }

    @After("execution(* com.mhn.service.DeptService.*(..))")
    public void after(){
        log.info("----after...4----");
    }
}
  • MyAspect5
@Order(2)
@Slf4j
@Component
@Aspect
public class MyAspect5 {
    @Before("execution(* com.mhn.service.DeptService.*(..))")
    public void before(){
        log.info("----before...5----");
    }

    @After("execution(* com.mhn.service.DeptService.*(..))")
    public void after(){
        log.info("----after...5----");
    }
}

3.3、切入点表达式

  • 切点表达式

    • 描述切入点方法的一种表达式
  • 作用

    • 用来决定项目中的哪些方法需要加入通知
  • 常见形式:

    • execution(...):根据方法的签名来匹配
    • @annotation(...):根据注解匹配
  • 切入点表达式-execution

    • 主要根据方法的返回值、表名、类名、方法名、方法参数等信息来匹配
    execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
    
    • 其中带 ? 的表示可以省略的部分
      • 访问修饰符
      • 包名.类名
      • throws 异常:方法上声明抛出的异常,不是实际抛出的异常
@Slf4j
@Component
@Aspect
public class MyAspect6 {
    //@Pointcut("execution(public void com.mhn.service.serviceImpl.DeptServiceImpl.deleteDept(..))")
    //@Pointcut("execution( void com.mhn.service.serviceImpl.DeptServiceImpl.deleteDept(..))")
    //@Pointcut("execution( void deleteDept(..))")//包名.类名 不建议使用
    //@Pointcut("execution( void com.mhn.service.DeptService.deleteDept(..))")

    //@Pointcut("execution( void com.mhn.service.DeptService.*(..))")
    //@Pointcut("execution(* com.*.service.DeptService.*(*))")
    //@Pointcut("execution(* com.mhn.service.*Service.delete*(*))")

    //@Pointcut("execution(* com.mhn.service.DeptService.*(..))")
    @Pointcut("execution(* com.mhn.service.DeptService.list()) || " +
            "execution(* com.mhn.service.DeptService.deleteDept(..))")
    public void pt(){}
    @Before("pt()")
    public void before(){
        log.info("MyAspect6  before...");
    }
}

根据业务需要,可以使用且(&&)、或(||)、非(!)来组合比较复杂的切入点表达式

  • 书写建议

    • 所有业务方法名命名时尽量规范,方便切入点表达式快速匹配。如:查询类方法是find开头,更新类是update开头
    • 描述切入点方法通常基于接口描述,而不是直接描述实现类,增强拓展性
    • 在满足业务需要的前提下,尽量缩小切入点的匹配范围
  • 切入点表达式-@annotation

    • 用于匹配标识有特定注解的方法
    @annotation(com.mhn.aop.Log)
    
@Slf4j
@Component
//Aspect
public class MyAspect7 {
    //匹配DeptServiceImpl中的list()和deleteDept(Integer id)方法
    /*@Pointcut("execution(* com.mhn.service.DeptService.list()) || " +
            "execution(* com.mhn.service.DeptService.deleteDept(..))")*/
    @Pointcut("@annotation(com.mhn.aop.MyLog)")
    public void pt(){}
    @Before("pt()")
    public void before(){
        log.info("MyAspect6  before...");
    }
}

【方法】

@Override
@MyLog
public List<Dept> list() {
    return deptMapper.list();
}

3.4、连接点

  • 在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息(类名,方法名,方法参数等)
    • 对于@Around通知,获取连接点只能使用 ProceedingJoinPoint
    • 其他通知,只能使用JoinPoint
@Slf4j
@Component
@Aspect
public class MyAspect8 {
    @Pointcut("execution(* com.mhn.service.DeptService.*(..))")
    public void pt(){}

    @Before("pt()")
    public void before(JoinPoint joinPoint){

    }

    @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;
    }
}

4.AOP案例

  • 需求:将 增、删、改相关接口的操作日志记录到数据表中

  • DeptController

public interface DeptService {
@Slf4j
@RestController()
@RequestMapping("/depts")
public class DeptController {
    @Autowired
    private DeptService deptService;

    @GetMapping
    public Result deptList(){
        List<Dept> list = deptService.list();
        log.info("查询部门数据",list.toString());
        return Result.success(list);
    }

    @DeleteMapping("/{id}")
    @MyLog
    public Result deleteDept(@PathVariable Integer id) throws Exception {
        deptService.deleteDept(id);
        log.info("删除部门");
        return Result.success();
    }
    @PostMapping
    @MyLog
    public Result addDept(@RequestBody Dept dept){
        deptService.addDept(dept);
        return Result.success();
    }

    @PutMapping
    @MyLog
    public Result updateDept(@RequestBody Dept dept){
        deptService.updateDempt(dept);
        return Result.success();
    }

    @GetMapping("/{id}")
    public Result selectById(@PathVariable Integer id){
        Dept dept = deptService.getDeptById(id);
        return Result.success(dept);
    }
}
  • EmpController
@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {
    @Autowired
    private EmpService empService;

    @GetMapping
    public Result getEmps(@RequestParam(value = "name",required = false) String name,
                          @RequestParam(value = "gender",required = false) Short gender,
                          @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                          @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end,
                          @RequestParam(defaultValue = "1") Integer page,
                          @RequestParam(defaultValue = "5")Integer pageSize) {
        PageModel pageModel = empService.getEmpsPage(name,gender,begin,end,page,pageSize);
        log.info("总条数",pageModel.getTotal().toString());
        return Result.success(pageModel);
    }

    @DeleteMapping("/{ids}")
    @MyLog
    public Result deleteEmpByIds(@PathVariable List<Integer> ids){
        empService.deleteEmpByIds(ids);
        return Result.success();
    }

    @PostMapping
    @MyLog
    public Result insertEmp(@RequestBody Emp emp){
        empService.insertEmp(emp);
        return Result.success();
    }
    @GetMapping("/{id}")
    public Result getEmpById(@PathVariable Integer id){
        Emp emp = empService.getEmpById(id);
        return Result.success(emp);
    }
    @PutMapping
    @MyLog
    public Result updateEmpById(@RequestBody Emp emp){
        empService.updateEmpById(emp);
        return Result.success();
    }
}

results matching ""

    No results matching ""