事务管理&A O P
事务管理
事务是一组操作的集合,他是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败。在之前的MySQL数据库的使用中就使用到了这个知识点。具体操作如下。
事务的操作
通常用在需要同时成功或者失败的操作上面,例如删除部门表中的部门和部门里面的员工的操作。
例如在删除K-ON!部的时候时候,就应该同时删除部门里面的城南员工。
delete from dept where id = 7;
delete from emp where dept_id==7;
正常情况下,这两句sql语句会正常执行,并删除古典文学部和城南这位员工,但是如果我们在删除员工1的时候故意写错语法,那么就会出现删除部门成功,但是部门中的成员依旧存在的情况。
这个时候就可以使用事务来解决。
start transaction ;
delete from dept where id = 9;
delete from emp where dept_id=9;
commit ;
rollback ;
先执行 start transaction
语句开启事务,然后执行下面两句数据库操作语句。如果成功的话,就可以执行 commit
语句提交事务,将数据写入数据库。如果失败,则可以执行 rollback
语句进行回滚操作。
SpringBoot中的事务功能
案例分析
SpringBoot中已经集成好了事务功能,我们只需要在需要使用的方法,类,或者接口上加上 @Transactional
注解就可以了。
Spring事务管理
我们可以在配置文件中打开事务管理日志
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager: debug
我们故意写一段错误的代码对它进行测试。
@Transactional
@Override
public void delete(Integer id) {
deptMapper.delete(id);
int i = 1/0;
empMapper.deleteByDeptId(id);
}
观察控制台的输出。
可以看出执行了回滚操作。
值得一提的是,在@Transactional
注解中,可以给它转递参数,参数用于控制出现何种异常时,回滚事务。
除了传递 rollbackFor
参数,我们还可以传递propagation
参数,用于指定传播行为。
通常用于被@Transactional
注解的方法去调用另一个也被次注解注解的方法。
A O P
什么是AOP
spring中的AOP主要是用了动态代理功能。要使用spring中的AOP,我们首先需要在配置文件中引入依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后再定义一个方法。
package com.itheima.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;
/**
* {@code @Author} 19667
* {@code @create} 2024/8/2 11:06
*/
@Slf4j
@Component
@Aspect
public class TimeAspect {
@Around("execution(* com.itheima.service.*.*(..))")
public Object recodeTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long begin = System.currentTimeMillis();
Object object = proceedingJoinPoint.proceed();
long end = System.currentTimeMillis();
log.info(proceedingJoinPoint.getSignature()+"执行时间:{}ms",end-begin);
return object;
}
}
注解解释
@Slf4j
主要作用是在日志输出语句。@Component
将类交给IOC容器管理。@Aspect
申明这是一个切面类。@Around
环绕通知。
AOP的应用场景与优势
AOP核心概念
AOP执行流程
Spring框架下的AOP只要是通过动态代理的技术去实行的,可以理解为它先去生成了一个代理对象,在对象中加入自己要实现的逻辑,然后再去调用目标对象的对应方法。当IOC容器进行自动注入的时候,会注入代理对象。
AOP进阶
通知类型
package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* {@code @Author} 19667
* {@code @create} 2024/8/2 11:57
*/
@Slf4j
@Component
@Aspect
public class Aspect1 {
@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void pt(){};
@Before("pt()")
public void before(){
log.info("before");
}
@Around("pt()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before");
Object object = proceedingJoinPoint.proceed();
log.info("around after");
return object;
}
@After("pt()")
public void after(){
log.info("after");
}
@AfterReturning("pt()")
public void afterReturning(){
log.info("afterRetuning");
}
@AfterThrowing("pt()")
public void afterThrowing(){
log.info("after Throwing");
}
}
注解@PintCut
通知顺序
切入点表达式
在AOP中我们通常需要在通知类型的注解中编写切入点表达式来控制需要加入通知的方法。
execution
表达式
在execution
表达式中,我们还可以通过通配符来扩大匹配的范围。
aunotation
表达式
如果要匹配的方法使用execution
表达式太过复杂,我们也可以使用aunotation
表达式。
具体使用方法就是自定义一个注解。
package com.itheima.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@code @Author} 19667
* {@code @create} 2024/8/2 14:11
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
@Retention
注解配置次注解在什么时候生效
@Target
注解配置这个注解加在什么上面,例如。
/**
* 新增员工
* @param emp
* @return
*/
@Log
@PostMapping
public Result save(@RequestBody Emp emp){
empService.addEmp(emp);
return Result.success();
}
这样我们就可以使用@Around("@annotation(com.itheima.anno.Log)")
来指定方法加入通知。
连接点
在案例中使用AOP
由于增删改的方法的方法名没有太多共通性,决定使用aunotation
定义Log注解
package com.itheima.anno;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* {@code @Author} 19667
* {@code @create} 2024/8/2 14:11
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Log {
}
构造AOP方法
package com.itheima.aop;
import com.alibaba.fastjson.JSONObject;
import com.itheima.mapper.OperateLogMapper;
import com.itheima.pojo.OperateLog;
import com.itheima.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import jakarta.servlet.http.HttpServletRequest;
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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Arrays;
/**
* {@code @Author} 19667
* {@code @create} 2024/8/2 14:13
*/
@Slf4j
@Component
@Aspect
public class LogAspect {
@Autowired
private HttpServletRequest request;
@Autowired
private OperateLogMapper operateLogMapper;
@Around("@annotation(com.itheima.anno.Log)")
public Object recodeLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
//获取id
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");
//时间
LocalDateTime operateTime = LocalDateTime.now();
//类名
String className = proceedingJoinPoint.getTarget().getClass().getName();
//方法名
String methodName = proceedingJoinPoint.getSignature().getName();
//操作方法参数
String methodParams = Arrays.toString(proceedingJoinPoint.getArgs());
//操作返回值
long begin = System.currentTimeMillis();
Object object = proceedingJoinPoint.proceed();
long end = System.currentTimeMillis();
String returnValue = JSONObject.toJSONString(object);
//操作耗时
long costTime = end - begin;
OperateLog operateLog = new OperateLog(null, operateUser, operateTime, className, methodName, methodParams, returnValue, costTime);
operateLogMapper.insert(operateLog);
log.info("AOP记录日志:{}", operateLog);
return object;
}
}
值得一提的是,我们想要获取操作的用户的id的时候,需要用到HttpServletRequest
但是在AOP中,对方法的参数有严格的限制,所以我们选择创建接口加上自动注入的形式来获取。
在增删改的方法上加上注解
略
进行测试
至此,功能实现成功
eTime, className, methodName, methodParams, returnValue, costTime);
operateLogMapper.insert(operateLog);
log.info(“AOP记录日志:{}”, operateLog);
return object;
}
}
值得一提的是,我们想要获取操作的用户的id的时候,需要用到`HttpServletRequest` 但是在AOP中,对**方法的参数**有严格的限制,所以我们选择创建接口加上自动注入的形式来获取。
## 在增删改的方法上加上注解
略
## 进行测试
[外链图片转存中...(img-4uBJUpHS-1722585401056)]
至此,功能实现成功