事务管理
事务回顾
事务 是一组操作的集合,他是一个不可分割的工作单位,这些操作 要么同时成功,要么同时失败。
操作: 开启事务(一组操作开始前):start transaction/begin;
提交事务(这组操作全部成功后): commit;
回滚事务(中间任何一个操作出现异常):rollback;
spring事务管理
注解@Transactional
位置:业务(service)层的方法上,类上,接口上
作用:将当前方法交给spring进行事务管理,方法执行前,开启事务。成功执行完毕,提交事务。出现异常,回滚事务
在application中开启事务日志
//empmapper
// 根据部门id删除该部门下的员工数据
@Delete("delete from emp where dept_id=#{deptId}")
void deleteByDeptID(Integer deptId);
//deptservice实现类
@Autowired
private EmpMapper empMapper;//注入(在mapper接口中)
@Transactional//spring事务管理
@Override
public void delete(Integer id) {
deptMapper.deleteById(id);//删除部门
empMapper.deleteByDeptID(id);//根据id删除部门下员工
}
//application中开启事务日志
#开启事务管理
logging:
level:
org.springframework.jdbc.support.JdbcTransactionManager:debug
事务进阶
rollbackFor
默认情况下,只有出现RuntimeException才会回滚异常。rollbackFor属性用于控制出现何种异常类型,回滚事务
@Transactional(rollbackFor = Exception.class)
//service层
@Transactional(rollbackFor = Exception.class)//spring事务管理
@Override
public void delete(Integer id) throws Exception {
deptMapper.deleteById(id);//删除部门
if (true){
throw new Exception("出错啦"); //抛异常
}
empMapper.deleteByDeptID(id);//根据id删除部门下员工
}
//controller层
/*删除部门*/
@DeleteMapping("/{id}")
public Result delete(@PathVariable Integer id) throws Exception { //抛异常
log.info("根据id删除部门,{}",id);
//调用service删除部门
deptService.delete(id);
return Result.success();
}
propagation
事务传播行为:指的是当一个事务方法被另一个事务方法调用时,这个事务应该如何进行事务控制
//database
create table dept_log
(
id int auto_increment
primary key,
create_time datetime null comment '创建时间',
description varchar(20) null comment '描述信息
'
);
//deptservice实体类 ctrl+alt+t快速创建try final方法
@Transactional
@Override
public void delete(Integer id) throws Exception {
try {
deptMapper.deleteById(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);
}
}
//pojo实体类deptlog 连接数据库
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DeptLog {
private Integer id;
private LocalDateTime createTime;
private String description;
}
//deptlogservice接口
public interface DeptLogService {
void insert(DeptLog deptLog);
}
//deptlogservice实体类
@Service
public class DeptLogServiceImpl implements DeptLogService {
@Autowired
private DeptLogMapper deptLogMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
public void insert(DeptLog deptLog) {
deptLogMapper.insert(deptLog);
}
}
//mapper接口 操作数据库
@Mapper
public interface DeptLogMapper {
@Insert("insert into dept_log(create_time, description) VALUES (#(createTime),#(description))")
void insert(DeptLog log);
}
AOP基础
AOP概述
Aspect Oriented Programming (面向切面编程,面向方面编程) ,其实就是面向特定方法编程
AOP快速入门 统计各个业务层方法执行耗时
//pom.xml引入依赖
<!-- AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
//新建包和类
@Slf4j
@Component//交给ioc容器管理,成为bean对象
@Aspect//AOP类
public class TimeAspect {
@Around("execution(* com.example.springboot.*.*(..))")//切入点表达式
public Object recordTime(ProceedingJoinPoint joinPoint) throws Throwable {
//记录开始时间
long begin = System.currentTimeMillis();
//调用原始方法运行
Object result = joinPoint.proceed();
//记录结束时间,计算方法执行耗时
long end = System.currentTimeMillis();
log.info(joinPoint.getSignature()+"方法执行耗时:{}ms",end-begin);
return result;
}
}
AOP核心概念
AOP进阶
通知类型
PointCut
@Slf4j
@Component
@Aspect
public class MyAspect1 {
@Pointcut("execution(* com.example.springboot.service.impl.DeptServiceImpl.*(..))")
private void pt(){}
@Before("pt()")
public void before(){
log.info("before ...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around defore ...");
//调用原始方法执行
Object result = proceedingJoinPoint.proceed();
log.info("around after ...");
return result;
}
}
通知顺序
切入点表达式
描述切入点方法的一种表达式
作用:
主要用来决定项目中的哪些方法需要加入通知
常见形式:
execution
主要根据方法的返回值,包名,类名,方法名,方法参数等信息来匹配:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
其中带?的表示可以省略的部分
访问修饰符 :可省略(如 public,protected)
包名.类名:可省略(不推荐)、
throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
可使用通配符描述切入点
* 单个独立的任意符号,可通配任意返回值,包名,类名,方法名,任意类型的一个参数,也可以通配包,类,方法名的一部分
.. 多个连续的任意符号,可以通配任意层级的包,或任意类型,个数的参数
@Pointcut("execution(* com.example.springboot.service.impl.DeptServiceImpl.*(..))")
//根据业务需求,可使用 且(&&) 或(||) 非(!)来组合复杂的切入点表达式
书写建议
方法名在命名时 尽量规范,方便表达式快速匹配
描述切入点方法通常基于接口描述 ,增强拓展性
尽量缩小切入点的匹配范围
@annotation
用于匹配标识有特定注解的方法 注解全类面
//aop新建annotion加上这两个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)//在METHOD上生效
//service上要用的方法上加入
@MyLog
@Override
public List<Dept> list(){
return deptMapper.list();
}
//要用的方法
@Pointcut("@annotation(com.example.springboot.aop.MyLog)")
连接点
在spring中JoinPoint抽象了连接点,用它可以获得方法执行是的相关信息,如目标类名,方法名,方法参数等
对于@Around 通知,获取连接点信息只能用 ProceedingJoinPoint
对于其他四种通知,获取连接点信息只能用JoinPoint,他是ProceedingJoinPoint的父类型
AOP案例 案例中增删改查相关接口操作日志记录到数据库表中
//引入aop的起步依赖 pom中
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
//数据库中新建数据库表结构,并引入实体类放入pojo下,创建mapper方法
//创建注解包anno,新建annotation类型的类(自定义注解@Log)
@Retention(RetentionPolicy.RUNTIME)//运行
@Target(ElementType.METHOD)
//新建切面类
@Slf4j
@Component
@Aspect //切面类
public class LogAspect {
@Autowired
private HttpServletResponse request;
@Autowired
private OperateLogMapper operateLogMapper;
@Around("@annotation(com.example.springboot.anno.Log)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
//操作人ID -当前登录员工ID
//获取请求头中的JWT令牌,解析令牌
String jwt=request.getHeader("token");
Map<String, Object> claims = JwtUtils.ParseJwt(jwt);
Integer operateUser= (Integer) claims.get("id");
//操作时间
LocalDateTime operateTime=LocalDateTime.now();
//操作类名
String className = joinPoint.getTarget().getClass().getName();
//操作方法名
String methodName = joinPoint.getSignature().getName();
//操作方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);
long begin=System.currentTimeMillis();
//调用原始目标方法运行
Object result=joinPoint.proceed();
long end=System.currentTimeMillis();
//方法返回值
String returnValue=JSONObject.toJSONString(result);
//操作耗时
Long costTime=end-begin;
//记录操作日志
OperateLog operateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue);
operateLogMapper.insert(operateLog);
return result;
}
}
//controller层中在要加入的内容下
@Log