事務管理
事務
概念
事務是一組操作的集合,是一個不可分割的工作單位,這些操作要么同時成功,要么同時失敗
操作
開啟事務(一組操作開始前,開啟事務):start transaction / begin
提交事務(這組操作全部成功后,提交事務):commit
回滾事務(中間任何一個操作出現異常,回滾事務):rollback
Spring事務管理
案例:解散部門,刪除部門的同時要刪除該部門下的員工
注解:@Transactional
位置:業務(Service)層的方法、類、接口上
作用:將當前方法交給spring進行事務管理,方法執行前開啟事務,成功執行完畢提交事務;出現異常回滾事務
在兩次刪除操作之間添加異常代碼
@Transactional@Overridepublic void delete(Integer id) {deptMapper.deleteById(id); //根據id刪除部門int i = 1/0;empMapper.deleteByDeptId(id); //根據部門id刪除員工}
再進行刪除操作
這樣就成功實現事務管理
事務進階
rollbackFor
默認情況下,只有出現RuntimeException才會回滾異常,rollbackFor屬性用于控制出現何種異常類型,回滾事務
@Transactional(rollbackFor = Exception.class)@Overridepublic void delete(Integer id) {deptMapper.deleteById(id); //根據id刪除部門int i = 1/0;empMapper.deleteByDeptId(id); //根據部門id刪除員工}
propagation
事務傳播行為:當一個方法事務被另一個方法事務方法調用時,這個事務應該如何進行事務控制
REQUIRED:大部分情況下用該傳播行為即可
REQURIRES_NEW:不希望事務之間相互影響時,使用該傳播行為。比如:下訂單前需要記錄日志,不論訂單成功保存與否,都需要保證日志記錄能夠成功記錄
AOP基礎
AOP概述
AOP:Aspect Oriented Programming(面向切面編程),其實是面向特定方法編程
場景:
實現:
動態代理是面向切面編程的主流實現。SpringAOP是Spring框架的高級技術,旨在管理bean對象的過程中,主要通過底層的動態代理機制,對特定的方法進行編程
AOP快速入門
統計業務層各個方法的執行耗時
1.導入AOP的相關依賴
2.編寫AOP程序,針對特定方法根據業務進行編程
首先再pom文件中添加AOP的依賴
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
再編寫AOP程序,新建一個aop.TimeAspect類
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
@Aspect //AOP類
public class TimeAspect {@Around("execution(* com.itheima.service.*.*(..))") //切入點表達式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() + "方法執行時間:{}ms", end - begin);return result;}
}
查詢部門信息時,日志信息如下
場景:記錄操作日志,權限控制,事務管理
優點:代碼無侵入,減少重復代碼,提高開發效率,維護方便
AOP核心概念
AOP進階
通知類型
@PointCut注解:該注解的作用是將公共的切入點表達式抽取出來,需要用到是引入該切入點表達式即可
通知順序
當多個切面的切入點都匹配到了目標方法,目標方法運行時,多個通知方法都會被運行
1.不同切面類中,默認按照不同切面類的類名字母排序
? ? 目標方法前的通知方法:字母排序靠前的先執行
? ? 目標方法后的通知方法:字母排序靠前的后執行
2.用 @Order(數字) 加在切面類上來控制順序
? ? 目標方法前的通知方法:數字小的先執行
? ? 目標方法后的通知方法:數字小的后執行
切入點表達式
即描述切入點方法的一種表達式,主要用來決定項目中的哪些方法要加入通知
execution
根據方法的返回值、包名、類名、方法名、方法參數等信息來匹配,語法為
帶?的表示是可以省略的部分
1.訪問修飾符:可省略(比如:public、protected)
2.包名.類名:可省略
3.throws異常:可省略(注意是方法上聲明拋出的異常,不是實際拋出的異常
可以使用通配符描述切入點
1. * :單個獨立的任意符號,可以通配任意返回值、包名、類名、方法名、任意類型的一個參數,也可以通配包、類、方法名的一部分
? ? ?
2. .. :多個連續的任意符號,可以通配任意層級的包,或任意類型、任意個數的參數
? ??
可以根據業務需要,使用且(&&)、或(||)、非(!)來組合比較復雜的切入點表達式
@annotation
如果想要匹配DeptServiceImpl中的list()和delete(Integer id)方法,使用execution寫切入點表達式比較復雜
@PointCut("execution(* com.itheima.service.DeptService.list())" || "execution(* com.itheima.service.DeptService.delete(java.lang.Integer))")
private void pt(){}
@annotation切入點表達式用于匹配標識有特定注解的方法
連接點
在Spring中使用JoinPoint抽象了連接點,用它可以獲得方法執行時的相關信息,如目標類名、方法名、方法參數等
對于@Around通知,獲取連接點信息只能使用ProceedingJoinPoint
對于其他四種通知,獲取連接點信息只能使用JoinPoint,他是ProceedingJoinPoint的父類
AOP案例
需求:將案例中增、刪、改相關接口的操作日志記錄到數據庫表中,日志信息包含:操作人、操作時間、執行方法的全類名、執行方法名、方法運行時參數、返回值、方法執行時長
思路:需要對所有業務類中的增、刪、改 方法添加統一功能,使用AOP技術最為方便;由于增、刪、改 方法名沒有規律,可以自定義@Log注解完成目標方法匹配
首先在pom文件中添加AOP相關依賴
<!-- AOP --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
接著新建一個操作日志表
-- 操作日志表
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 '操作日志表';
再新建一個日志表對應的實體類
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; //IDprivate Integer operateUser; //操作人IDprivate LocalDateTime operateTime; //操作時間private String className; //操作類名private String methodName; //操作方法名private String methodParams; //操作方法參數private String returnValue; //操作方法返回值private Long costTime; //操作耗時
}
編寫Log注解(@annotation切面表達式)
package com.itheima.anno;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 Log {
}
編寫通知,定義切面類
package com.itheima.aop;import com.alibaba.fastjson.JSON;
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 io.jsonwebtoken.Jwts;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
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;@Slf4j
@Component
@Aspect //切面類
public class LogAspect {@Autowiredprivate HttpServletRequest request;@Autowiredprivate OperateLogMapper operateLogMapper;@Around("@annotation(com.itheima.anno.Log)")public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {//操作人id//獲取請求頭中的JWT令牌,解析令牌String jwt = request.getHeader("token");Claims 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, costTime);operateLogMapper.insert(operateLog);log.info("AOP記錄操作日志:{}",operateLog);return result;}
}
然后要在需要在目標對象(DeptServiceImpl、EmpServiceImpl)的連接點方法(增、刪、改)上添加剛剛寫的Log注解,執行啟動類,進行一次新增部門操作和一次刪除部門操作,日志成功記錄在operate_log表中