一、MySQL事务回顾
二、Spring事务管理
Spring框架的第一大核心:IOC控制反转
在DeptServiceImpl下删除部门方法下新加一个删除员工信息的操作,注意:此时的id是部门id。
1、问题分析
2、@Transactional-Spring事务管理
一般是在Service实现类的方法上加Transactional注解
执行多次数据访问的增删改操作上需要用到事务
三、Spring事务进阶
1、rollbackFor
Int i = 1/0 是属于运行时异常
这是service层,需要继续往上抛异常
默认情况下只有运行时异常才会进行事务回滚
2、propagation
因为我们需要解散部门时,无论成功还是失败,都要记录操作日志,所以需要用到@Transaction的REQUIRES_NEW的属性,来新建一个事务
pojo/DeptLog
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptLog {
private Integer id;
private LocalDateTime createTime;
private String description;
}
DeptServiceImple
package com.itheima.service.impl;
import com.itheima.mapper.DeptLogMapper;
import com.itheima.mapper.DeptMapper;
import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Dept;
import com.itheima.pojo.DeptLog;
import com.itheima.service.DeptLogService;
import com.itheima.service.DeptService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.List;
import java.util.logging.LogManager;
@Service
public class DeptServiceImpl implements DeptService {
@Autowired
private DeptMapper deptMapper;
@Autowired
private EmpMapper empMapper;// 删除部门id的同时,需要删除与部门id关联的员工信息
@Autowired
private DeptLogService deptLogService;// 添加日志对象注入
// @Autowired
// private DeptLogMapper deptLogMapper;
/*1、查询操作* */
// 实现方法
public List<Dept> list(){
List<Dept> deptList = deptMapper.select();
return deptList;
}
/*2、删除指定id
* */
@Transactional(rollbackFor = Exception.class) // 交给了Spring进行事务管理,将所有异常进行事务处理
@Override
public void deleteById(Integer id) throws Exception{
try {
deptMapper.deleteById(id); // 删除部门id
int i = 1/0;
// if (true){throw new Exception("出错了...");}
empMapper.deleteById(id); // 删除员工信息
} finally {// 记录日志描述
DeptLog deptLog = new DeptLog();
deptLog.setCreateTime(LocalDateTime.now());
deptLog.setDescription("执行了解散部门的操作,此次解散的部门id是"+id);
deptLogService.insert(deptLog);
/**
*可以不用写DeptLogService和DeptServiceImpl,直接写一个DeptLogMapper然后交给IOC控制器,再在
* 这个实现类中注入DeptLogMapper的对象直接调用insert方法,在方法前加入
* @Transactional(propagation = Propagation.REQUIRES_NEW)
*/
// deptLogMapper.insert(deptLog);
}
}
/*3、新增部门*/
public void add(Dept dept){
dept.setCreateTime(LocalDateTime.now());
dept.setUpdateTime(LocalDateTime.now());
deptMapper.insert(dept);
}
/*根据ID查询*/
public Dept selectById(Integer id){
Dept dept = deptMapper.selectById(id);
return dept;
}
@Override
/*更新部门名称*/
public void update(Dept dept) {
dept.setUpdateTime(LocalDateTime.now());
dept.setCreateTime(LocalDateTime.now());
deptMapper.update(dept);
}
}
DpetLogService
package com.itheima.service;
import com.itheima.pojo.DeptLog;
public interface DeptLogService {
void insert(DeptLog deptLog);
}
DeptLogServiceImpl
package com.itheima.service.impl;
import com.itheima.mapper.DeptLogMapper;
import com.itheima.pojo.DeptLog;
import com.itheima.service.DeptLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class DeptLogServiceImpl implements DeptLogService {
@Autowired
private DeptLogMapper deptLogMapper;
// 开启一个新事务
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}
DeptLogMapper
package com.itheima.mapper;
import com.itheima.pojo.DeptLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Mapper
public interface DeptLogMapper {
@Insert("insert into dept_log(create_time, description) VALUES (#{createTime},#{description})")
// @Transactional(propagation = Propagation.REQUIRES_NEW)
public void insert(DeptLog deptLog);
}
四、AOP基础-Spring框架的第二大核心
AOP概述
AOP快速入门
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;
@Slf4j
@Component // 交给IOC容器
@Aspect // 表示这个是AOP类
public class TimeAspect {
// 当前共性功能应该加在哪些方法上
@Around("execution(* com.itheima.service.*.*(..))") //切入点表达式
public void recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
// 1、获取方法运行开始时间
long begin = System.currentTimeMillis();
// 2、运行原始方法,o是原始方法的返回值
Object o = joinPoint.proceed();
// 3、获取方法结束运行结束时间
long end = System.currentTimeMillis();
// 计算方法耗时
// joinPoint.getSignature():原始方法的签名
log.info(joinPoint.getSignature()+"方法耗时:{}ms",end-begin);
}
}
小结
五、AOP基础-核心概念
AOP核心概念
AOP执行流程
小结
连接点JoinPoint | 能够被AOP所控制的方法 |
---|---|
切入点PointCut | 实际被AOP控制的方法,通过切入点表达式 |
通知Advice | 将所有共性功能封装起来的方法 |
切面Aspect | 描述通知与切入点之间的对应关系 |
目标对象Target | 通知所对应的对象 |
六、AOP进阶
1、通知类型
切入点表达式抽取
小结
2、通知顺序
3、切入点表达式
execution
MyAspect6.java
package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@Aspect
public class MyAspect6 {
// @Pointcut("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
// @Pointcut("execution(void com.itheima.service.DeptService.delete(java.lang.Integer))")//基于接口描述
// @Pointcut("execution(void delete(java.lang.Integer))")包名.类名不建议省略,匹配的范围过大,影响匹配的效率
// @Pointcut("execution(* com.itheima.service.DeptService.*(*))")//匹配返回值为任意,DeptService接口下所有方法任意类型的一个参数
// @Pointcut("execution(* com..DeptService.get(*))")//匹配返回值为任意,com包下任意子包中DeptService接口/类下get方法,为任意类型的一个参数
@Pointcut("execution(* com.itheima.service.DeptService.delete(java.lang.Integer)) ||"+
"execution(* com.itheima.service.DeptService.list())")
//匹配前面一个或者后面任意一个
public void pt(){}
@Before("pt()")
public void before(){
System.out.println("before ...6");
}
}
@annotation
MyLog
package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)//该注解表示注解的生命周期
@Target(ElementType.METHOD)//表示该注解作用在哪一部分
public @interface MyLog {
}
MyAspect7
package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Slf4j
@Component
@Aspect
public class MyAspect7 {
@Pointcut("@annotation(com.itheima.aop.MyLog)")
public void pc(){}
@Before("pc()")
public void func(){
log.info("MyAspect7...");
}
}
只需要在想要匹配的方法上加@MyLog注解就可以调用通知方法
小结
4、连接点
MyAspect8
package com.itheima.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;
@Component
@Slf4j
@Aspect
public class MyAspect8 {
@Pointcut("execution(* com.itheima.service.DeptService.*(..))")
public void pt(){}
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info("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、获取目标方法签名
Signature signature = joinPoint.getSignature();
log.info("目标方法签名:{}",signature);
// 3、获取目标方法名
String signatureName = joinPoint.getSignature().getName();
log.info("目标方法名:{}",signatureName);
// 4、获取目标方法运行参数
Object[] args = joinPoint.getArgs();
String arg = Arrays.toString(args);
log.info("目标方法参数:{}",arg);
// 5、执行原始方法,获取返回值
Object result = joinPoint.proceed();
log.info("目标方法的返回值:{}",result);
log.info("MyAspect8 after ...");
return result;
}
@After("pt()")
public void after(){
log.info("after...");
}
}
测试类
package com.itheima;
import com.itheima.service.DeptService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringbootAopQuickstart1ApplicationTests {
@Autowired
DeptService deptService;
@Test
public void test1(){
deptService.delete(10);
}
@Test
public void test2(){
deptService.list();
}
}
执行delete之后
只有环绕通知才可以执行原始方法
前置通知在原始方法执行前运行,后置通知在原始方法执行后运行
七、AOP案例
1、准备工作
<!--AOP依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.1</version>
</dependency>
-- AOP案例
-- 操作日志表
create table operate_log(
id int unsigned primary key auto_increment comment 'ID',
operate_user int unsigned comment '操作人ID',
operate_time datetime comment '操作时间',
class_name varchar(100) comment '操作的类名',
method_name varchar(100) comment '操作的方法名',
method_params varchar(1000) comment '方法参数',
return_value varchar(2000) comment '返回值',
cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';
sql表格对应的实体类
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
private Integer id; //ID
private Integer operateUser; //操作人ID
private LocalDateTime operateTime; //操作时间
private String className; //操作类名
private String methodName; //操作方法名
private String methodParams; //操作方法参数
private String returnValue; //操作方法返回值
private Long costTime; //操作耗时
}
2、编码
LogAspect.java
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 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 javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;
@Component
@Slf4j
@Aspect //切面类
public class LogAspect {
@Autowired//用于获取jwt令牌
private HttpServletRequest httpServletRequest;
@Autowired
private OperateLogMapper operateLogMapper;
@Around("@annotation(com.itheima.anno.Log)")//切点表达式
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 1、获取操作人ID,当前登录员工id,这一步需要用到jwt令牌,在Header中
String jwt = httpServletRequest.getHeader("token");
// 解析jwt令牌
Claims claims = JwtUtils.parseJwt(jwt);
Integer operateUser = (Integer) claims.get("id");
// 2、操作时间
LocalDateTime operateTime = LocalDateTime.now();
// 3、操作类名
String className = joinPoint.getTarget().getClass().getName();
// 4、操作方法名
String methodName = joinPoint.getSignature().getName();
// 5、操作方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);
// 获取方法开始时间
long begin = System.currentTimeMillis();
// 6、操作方法返回值,转换为json格式的字符串保存起来
Object result = joinPoint.proceed();
String returnValue = JSONObject.toJSONString(result);
// 获取结束时间
long end = System.currentTimeMillis();
// 7、操作耗时
long costTime = end - begin;
// 将日志内容插入到表中
OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
operateLogMapper.insert(operateLog);
log.info("AOP操作日志:{}",operateLog);
return result;//返回的是执行方法的返回值
}
}
OperateLogMapper
package com.itheima.mapper;
import com.itheima.pojo.OperateLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
@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 operateLog);
}