spring事务管理
对员工管理系统删除员工的优化

Transactional注解:spring框架中已封装好,将注解加入到所用方法、类、接口(通常用在多次数据访问修改的方法上),spring则会对相应方法进行事务管理,遇到异常自动进行事务回滚
@Transactional//将当前方法交给spring框架统一进行事务管理
@Override
public void delete(Integer id) {
deptMapper.deleteByID(id);//删除员工
empMapper.deleteByDeptId(id);//删除部门对应员工
}
Transactinoal日志配置文件
#spring事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
事务管理属性
rollbackFor属性:
默认情况下,只有运行时异常才会回滚事务
rollbackFor属性则规定了事务回滚的异常类型
@Transactional(rollbackFor = Exception.class)
//所有异常都会回滚事务
propagation属性(传播行为)

public void delete(Integer id) {
try {
deptMapper.deleteByID(id);//删除员工
int i = 1/0;
empMapper.deleteByDeptId(id);//删除部门对应员工
} finally {
DeptLog deptLog = new DeptLog();
deptLog.setCreateTime(LocalDateTime.now());
deptLog.setDescription("执行了解散部门,解散部门为" + id + "号部门");
deptLogService.insert(deptLog);//调用操作日志记录方法,与当前事务相互独立
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)/* 当被事务调用时,该方法会创建一个新事务
,并将原事务挂起直到当前事务完成 */
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}//记录操作日志,无论是否原事务是否抛出异常

AOP(面向切面编程/面向特定方法编程)⭐️⭐️
AOP(Aspect-Oriented Programming),即面向切面编程,是一种编程范式,它旨在解决软件开发中的横切关注点问题。
核心思想:分离关注点
想象一下,您的应用中有许多不同的业务逻辑(比如用户注册、订单处理、部门解散)。在这些业务逻辑中,有一些功能是重复出现的、与核心业务无关的,例如:
-
日志记录(记录每个方法开始和结束)
-
事务管理(确保数据库操作要么全成功要么全失败)
-
权限检查(验证用户是否有权执行某个操作)
在传统编程中,您必须在每个业务方法中都重复编写这些代码,导致代码冗余和难以维护。
AOP 的解决方式
AOP 将这些重复的、分散在各个业务逻辑中的功能(横切关注点)抽象并集中起来,形成一个独立的模块,这个模块就叫做 “切面”(Aspect)。
-
切面 (Aspect): 封装了横切关注点的模块(如事务切面、日志切面)。
-
切入点 (Pointcut): 决定切面在哪里执行(例如:在所有以
Service结尾的类的方法执行时)。 -
通知 (Advice): 切面在特定切入点要执行的动作(例如:在方法之前执行日志记录,在方法之后执行事务提交)。
总结
AOP 的目的就是让您只关注核心业务逻辑,而将那些重复的、通用的服务(如日志、事务、安全)通过 动态代理 等技术,自动地、无侵入地织入到业务代码中。
简单记忆: AOP 就是把散落在各处的、非核心的通用功能,像一把刀一样切开业务代码,然后植入进去。

动态代理
动态代理(Dynamic Proxy)是程序设计中一种非常强大的设计模式,它属于 代理模式 的一种。它的核心思想是在 运行时 动态地创建一个代理对象,这个代理对象可以代替真实对象(目标对象)执行操作,并在不修改真实对象代码的前提下,增加额外的功能和控制。
| 概念 | 解释 | 作用 |
| 真实对象 (Real Subject) | 包含核心业务逻辑的对象。 | 完成实际的任务。 |
| 代理对象 (Proxy) | 动态生成的一个对象,它实现了与真实对象相同的接口。 | 接收外部的调用请求,并在将请求转发给真实对象之前或之后,插入额外的逻辑(增强)。 |
| 接口 (Interface) | 真实对象和代理对象共同实现的规范。 | 保证代理对象和真实对象可以互换使用。 |
| 增强 (Enhancement) | 代理对象在调用真实方法前后添加的额外功能(例如:日志记录、权限检查、事务管理等)。 | 实现了无侵入式的代码扩展。 |
AOP的使用

@Around:切入点表达式
AOP核心概念

1. 切面 (Aspect)
-
定义: 封装横切关注点的模块。它是一个类,将**通知(Advice)和切入点(Pointcut)**组合在一起。
-
代码体现: 被
@Aspect注解标记的类(如你之前写的TimeAspect)。
2. 连接点 (Join Point)
-
定义: 程序执行过程中可以插入通知(Advice)的所有潜在位置。
-
例子: 方法的调用、方法的执行、设置字段值、抛出异常等。
-
Spring AOP 的限制: Spring AOP 只支持方法执行作为连接点。
3. 通知/增强 (Advice)
-
定义: 真正被织入到连接点上的代码(动作)。它定义了**“何时(When)”和“如何(How)”**执行额外的行为。
| 通知类型 | 注解 | 执行时机 | 作用 |
| 前置通知 | @Before | 目标方法执行之前。 | 校验权限、启动日志。 |
| 后置通知 | @AfterReturning | 目标方法正常返回之后。 | 处理返回值、提交日志。 |
| 异常通知 | @AfterThrowing | 目标方法抛出异常之后。 | 回滚事务、记录错误信息。 |
| 最终通知 | @After (或 AfterFinally) | 目标方法执行完毕(无论是否异常)。 | 释放资源、关闭连接。 |
| 环绕通知 | @Around | 包围目标方法。 | 计时、事务控制。最强大且常用。 |
4. 切入点 (Pointcut)
-
定义: 一个表达式,用来精确匹配或筛选出真正要执行通知的连接点。它是一个规则过滤器。
-
代码体现:
execution(...)表达式,如你在@Around中定义的execution(* com.itheima.service.*.*(..))。
5. 目标对象 (Target Object)
-
定义: 被一个或多个切面(Aspect)所通知的对象。就是那个原始的、等待被增强的类。
6. 织入 (Weaving)
-
定义: 将切面(Aspect)应用到目标对象(Target Object)上,创建出被通知的新对象的过程。
-
Spring AOP 实现: 主要在运行时(Runtime)通过动态代理(JDK Proxy 或 CGLIB)实现织入。
环绕通知
返回值必须为Object接受原始方法的返回值
| 通知类型 | @Around 环绕通知 | 其他通知 (@Before, @After 等) |
| 控制权 | 完全控制(Full Control)。 | 旁观和依附(Passive Attachment)。 |
是否调用 proceed() | 必须调用 joinPoint.proceed()。 | 不能调用 joinPoint.proceed()。 |
| 工作方式 | “取代” 原始方法。环绕方法是原始方法的替代品。你必须手动决定原始方法是否运行、何时运行、以及运行后如何处理结果。 | “附加” 到原始方法上。框架会自动处理原始方法的运行,你的通知只是在某个特定时刻被回调执行。 |
| 返回值 | 必须返回一个 Object,作为最终返回给调用方的值。 | 返回值通常是 void 或无意义的(除了 @AfterReturning 可以接收返回值)。 |
| AOP 参数 | 必须使用 ProceedingJoinPoint。 | 通常使用 JoinPoint 即可。 |
切入点表达式(@Pointcut注解)
@Pointcut("execution(* com.itheima.service.*.*(..))")
public void pt(){};//抽取替换切入点表达式
@Around("pt()")//切入点表达式
Execution切入点表达式


*
| 位置 | 示例 | 匹配说明 |
| 返回值类型 | execution(public * *.*(..)) | 匹配 所有 返回类型的公有方法。 |
| 包名 | execution(* *.*.Service.*(..)) | 匹配任意包路径下的 Service 类。 |
| 类名/方法名 | execution(* com.test.User*.*(..)) | 匹配 com.test 包下所有以 User 开头的类中的所有方法。 |
..
| 位置 | 示例 | 匹配说明 |
| 包路径 | execution(* com.app..*.*(..)) | 匹配 com.app 包 及其所有子包 下的 所有类 中的所有方法。 |
| 方法参数 | execution(* com.app.Service.method(..)) | 匹配 method 方法,不论它有没有参数,或者参数是什么类型。 (..) 是最常用的参数匹配方式。 |
annotation
自定义注解
import java.lang.annotation.*; // 引入元注解
@Target(ElementType.METHOD) // 只能用于方法
@Retention(RetentionPolicy.RUNTIME) // 在运行时保留,可以通过反射读取
public @interface Mylog {
// 元素(Element):注解的参数,定义为方法形式
String value() default "Default Log"; // 默认值为 "Default Log"
int level() default 1;
}
//可被带有annotation的通知识别


| 元注解名称 | 作用/目的 | 必须使用的位置 |
@Target | 指定目标:定义注解可以应用在哪些程序元素上(如类、方法、字段等)。 | 自定义注解定义 |
@Retention | 指定生命周期:定义注解的保留级别(编译期、类加载时、运行时)。最常用的是 RUNTIME。 | 自定义注解定义 |
@Documented | 生成文档:标记注解会被 Javadoc 等工具包含在生成的文档中。 | 自定义注解定义 |
@Inherited | 允许继承:标记注解可以被子类继承。 | 自定义注解定义 |
@Repeatable | 可重复注解:允许在同一个元素上多次使用同一个注解。 | 自定义注解定义 |
通知顺序(同一切面类内部)
一、正常执行顺序 (无异常)
当目标方法正常执行,没有抛出异常时,通知的执行顺序如下:
-
@Around(环绕通知) 的 前置部分 (即joinPoint.proceed()之前) -
@Before(前置通知) -
目标方法执行
-
@Around(环绕通知) 的 后置部分 (即joinPoint.proceed()之后) -
@After(最终通知/AfterFinally) -
@AfterReturning(返回通知)
二、异常执行顺序
当目标方法执行时抛出异常,或在 @Before 中抛出异常时,顺序会发生变化:
-
@Around(环绕通知) 的 前置部分 -
@Before(前置通知) -
目标方法执行 (💥 抛出异常)
-
@Around(环绕通知) 的 异常处理部分 (通常通过try-catch捕获异常) -
@After(最终通知/AfterFinally) -
@AfterThrowing(异常通知) -
(异常向上抛出)
通知顺序(不同切面类)
默认以类名排序
通过@Order注解(数字)规定执行顺序,数字越小越先执行(栈模式)
员工管理系统完善(日志记录)
//日志记录接口
@Mapper
public interface OperateLogMapper {
//插入日志数据
@Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
"values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
public void insert(OperateLog log);
}
//annotation注解
@Retention(RetentionPolicy.RUNTIME)//运行指示生效
@Target(ElementType.METHOD)//当前注解作用在方法上
public @interface Log {
}
//切面类
@Slf4j
@Component
@Aspect
public class LogAspect {
@Autowired
private OperateLogMapper operateLogMapper;
@Autowired
private HttpServletRequest request;
@Around("@annotation(com.itheima.anno.Log)")
public Object record(ProceedingJoinPoint joinPoint) throws Throwable{
//操作人ID,获取请求头中jwt令牌解析
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer id = (Integer) claims.get("id");
//操作时间
LocalDateTime operateTime = LocalDateTime.now();
//操作类名
String className = joinPoint.getClass().getName();
//操作方法名
String methodName = joinPoint.getSignature().getName();
//操作方法参数
Object[] args = joinPoint.getArgs();
String params = Arrays.toString(args);
Long startTime = System.currentTimeMillis();
//执行原始方法
Object result = joinPoint.proceed();
Long endTime = System.currentTimeMillis();
//方法返回值
String returnValue = JSONObject.toJSONString(result);
//操作耗时
Long costTime = endTime - startTime;
//日志记录
OperateLog operateLog = new OperateLog(null,id,operateTime,className,methodName,params,returnValue,costTime);
operateLogMapper.insert(operateLog);
log.info("AOP日志记录{}",operateLog);
return result;
}
}
1143

被折叠的 条评论
为什么被折叠?



