目录
一、事务管理
1、事务回顾
事务的概念:事务必须服从ACID原则。ACID指的是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。通俗理解,事务其实就是一系列指令的集合。
事务的特性:
原子性:操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态。
一致性:事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
隔离性:在该事务执行的过程中,无论发生的任何数据的改变都应该只存在于该事务之中,对外界不存在任何影响。只有在事务确定正确提交之后,才会显示该事务对数据的改变。其他事务才能获取到这些改变后的数据。
持久性:当事务正确完成后,它对于数据的改变是永久性的。
下面提供一个事务操作发生的图进行述说:
感想:其实联系我之前学习的MySql知识,可以发现此事务就是当时学习到的事务概念,主要的是对数据库操作进行一个集合类的统合,实现数据的同步修改性。
接下来举一个案例来说明使用事务的原因:
分析:上图是一个对部门表实现删除部门的代码案例,首先它能实现基础的连带部门信息和部门人员信息的删除,但当代码执行过程中出现异常,如上图设置的1/0,就会中断代码执行,在前端表现就是只删除了部门信息,数据库里也是如此,它们不能一致的发生删除或不删除,由此我们就要利用到事务来进行管理,实现同步。
使用注释,进行事务集合使用(@Transactional):
分析:由图可知该注释可以使用在方法、类、接口上,自由度很高,而要解决上面提到的问题,只要在方法上实现就行。
补充:spring事务管理日志依赖添加
添加原因:Spring 事务管理是 Spring 框架中一个重要的模块,它提供了强大的事务管理能力,使得我们可以对事务操作进行细粒度的控制。
添加代码:
#spring事务管理日志 logging: level: org.springframework.jdbc.support.JdbcTransactionManager: debug
2、事务进阶
a、rollbackFor属性
作用:在 Spring 数据访问操作中,默认情况下只有未检查(unchecked)异常(继承自 RuntimeException 的异常)或者 Error 会导致事务回滚,而受检查的异常(即非 RuntimeException 异常)则不会导致事务回滚。rollbackFor 属性就是用来解决这个问题的,它可以指定受检查的异常也可以导致事务回滚。你可以设置任何你希望导致此行为的异常类型。
代码示例:
@Transactional(rollbackFor = Exception.class) //spring事务管理
//@Transactional
@Override
public void delete(Integer id) throws Exception {
try {
deptMapper.deleteById(id); //根据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);
}
}
分析:上图代码的效果仍是删除部门相关信息,只是最后的完成版,在这我们不用太在意另外的代码,只用看@Transactional后面括号里的rollbackFor使用,这里是指定所有的异常,我们也可以换另外的指定异常类型。
b、propagation属性
作用:propagation
是 Spring 的 @Transactional
注解的一个属性,它用于指定事务的传播行为。事务的传播行为决定了在方法被调用时,事务是如何沿着调用链传播的。
使用类型:
-
REQUIRED(Propagation.REQUIRED)
:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认的设置。 -
SUPPORTS(Propagation.SUPPORTS)
:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 -
MANDATORY(Propagation.MANDATORY)
:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 -
REQUIRES_NEW(Propagation.REQUIRES_NEW)
:创建一个新的事务,如果当前存在事务,则把当前事务挂起。 -
NOT_SUPPORTED(Propagation.NOT_SUPPORTED)
:以非事务方式运行操作,如果当前存在事务,就把当前事务挂起。 -
NEVER(Propagation.NEVER)
:以非事务方式运行,如果当前存在事务,则抛出异常。 -
NESTED(Propagation.NESTED)
:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于REQUIRED
。
使用视图:
感想:这一属性的使用就是可以自由设置当多重事务关联触发时,我们来决定它们的运行顺序和存在周期。
二、AOP学习
1、基础了解
AOP的概述
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,旨在促进更高效、组织性更好的代码。这种模式主要聚焦于程序逻辑的横向关注点,也就是那些几乎出现在所有层级代码中的公共功能(例如日志、事务管理等),而这种功能在没有 AOP 的情况下,往往需要重复编写,这会导致代码冗余。
以下是 AOP 的主要概念:
-
Aspect(切面):一个模块,具有一组提供公共功能的 APIs。在 AOP 中,切面用于封装每个横切关注点的逻辑,这些逻辑可以在运行时动态地应用到不同的对象和函数上。
-
Join Point(连接点):程序执行过程中的某个特定点,如方法被调用时、异常被抛出时等。在 Spring AOP 中,一个连接点总是代表一个方法的执行。
-
Advice(通知):是切面必须完成的工作,也就是切面的具体行为。根据其所在位置,通知的类型有 before、after、around、afterThrowing、afterReturning 等。
-
Pointcut(切入点):匹配连接点的规则或概述。在 Spring AOP 中,切入点可以使用 AspectJ 的切入点表达式语言来指定。
-
Target Object(目标对象):被一个或多个切面所通知的对象。
-
Weaving(织入):将切面和其它应用类型或者对象连接起来,创建一个被通知的对象。这可以在编译时(使用 AspectJ 编译器),类加载时,或在运行时完成。
Spring AOP 框架将这些概念与 Spring 的 IoC(Inversion of Control,控制反转)容器和 Spring 提供的其它功能无缝集成,从而提供了一个完整、易用,而且与 Java 语言和 Spring 框架高度集成的 AOP 实现。
应用场景
AOP(面向切面编程)在许多编程场景中都能发挥重要作用,主要因为它有助于代码的解耦和重复代码的减少。以下是一些 AOP 常见的应用场景:
-
日志记录:通过把日志记录的功能放到切面中处理,可以简化主要业务逻辑代码的清晰度,并且可以非常方便地控制日志记录的粒度和范围。
-
事务管理:在许多企业级应用中,事务管理是一项关键任务。AOP 可以简化事务操作,通过预定义的策略,使开发者可以透明地管理和配置事务。
-
性能监控:AOP 可以用于收集方法调用的统计信息,例如:方法调用的次数以及每次调用的持续时间等。这些信息可以用于后续的性能分析和优化。
-
安全检查:可以用AOP实现统一的安全检查,避免在每个需要进行安全检查的方法中单独实现。
-
错误处理:可以通过 AOP 对所有方法进行统一的错误捕获和处理,减少重复的错误处理代码。
-
缓存:可以通过 AOP 实现方法结果的缓存,提高系统的性能。
-
验证和格式化:AOP 可以用于验证对象的状态或格式化对象的输出,将这些横切关注点从主要任务中分离出来。
以上是 AOP 最常见的应用场景,但是并不受此限。
Spring AOP与动态代理
Spring AOP(面向切面编程)在它的底层实现上主要使用了动态代理。
动态代理是一种设计模式,主要是用于接口的非入侵式代理,不改变类文件和原有业务逻辑的情况下,增加额外的功能。Java的java.lang.reflect包中提供的Proxy类和InvocationHandler接口,就是用于生成动态代理类和处理代理方法调用的。
Spring AOP 使用了两种类型的动态代理:
1. **JDK动态代理**:基于接口的动态代理,JDK动态代理通过实现InvocationHandler接口,并重写invoke方法来处理代理逻辑。如果目标对象有接口,Spring AOP会选择使用JDK动态代理。
2. **CGLIB代理**:基于类的动态代理,CGLIB通过生成目标类的子类来处理代理逻辑。如果目标对象没有实现任何接口,Spring AOP会选择使用CGLIB代理。
在Spring AOP中,切面的逻辑(例如事务管理、日志记录等)通常会写在Advice中,然后这些通知会被动态织入到你指定的方法上,这样在调用这些方法时,就会执行对应的通知逻辑。
总的来说,动态代理构成了Spring AOP的核心,Spring通过动态代理技术实现了其强大的面向切面编程功能。
举例说明(统计各个业务层方法执行耗时)
在实现AOP的使用前,我们要加入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
编写的AOP使用案例代码(实现统计执行耗时):
package com.sunny.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.sunny.service.impl.DeptServiceImpl.*(..))") //切入点表达式
//@Around("com.sunny.aop.MyAspect1.pt()")
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操作中如何协同工作的示例:
-
定义一个Aspect,例如日志切面,将日志记录通知(Advice)与切入点(Pointcut,例如全部的服务操作方法)关联起来。
-
当程序执行到切入点的方法时,即触发了连接点(Join point),例如调用服务操作。
-
由于切面的存在,切入点的方法会被切面中的通知Advice捕获,这时候,日志切面的日志记录代码就在目标方法执行之前或之后执行,这个过程就是织入(Weaving)。
总结起来,AOP的核心概念关联使用,可以使系统的服务模块关注于核心业务,而像日志,事务,安全等服务可以通过AOP的方式进行模块化和集中管理,提高了系统的可维护性和可重用性。
补充:Advice的使用类型
有以下几种类型的advice:
- Before advice:在切入点选择的连接点之前执行的通知(比如,一个特定的方法调用之前)。
- After returning advice:在切入点选择的连接点成功完成后,例如,方法成功返回结果后,执行的通知。
- After throwing advice:在方法抛出异常退出时执行的通知。
- After (finally) advice:当切入点结束后执行的通知,无论方法退出是正常还是异常返回。
- Around advice:包围着被通知的方法,可以在被通知的方法前后自定义行为。
这些advice中,我们自定义的代码就是那些横切关注点的实现, 例如在方法调用前记录日志(Before advice),在方法异常时发送通知(After throwing advice)等。
下面给些代码案例:
package com.sunny.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Slf4j
@Component
//@Aspect
public class MyAspect1 {
@Pointcut("execution(* com.sunny.service.impl.DeptServiceImpl.*(..))")
//使用切入,指定表达式
public void pt(){}
@Before("pt()")//Advice使用
public void before(){
log.info("before ...");
}
@Around("pt()")//Advice使用
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before ...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
log.info("around after ...");
return result;
}
@After("pt()")//Advice使用
public void after(){
log.info("after ...");
}
@AfterReturning("pt()")//Advice使用
public void afterReturning(){
log.info("afterReturning ...");
}
@AfterThrowing("pt()")//Advice使用
public void afterThrowing(){
log.info("afterThrowing ...");
}
}
2、AOP进阶
@Around的使用补充
@Pointcut的使用图示:
多个切点的通知顺序分析(多个切面的切入点匹配到了相同的目标方法)
切入点表达式学习
常见形式
execution详细使用
代码示例:
@Pointcut("execution(public void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
@Pointcut("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
@Pointcut("execution(void delete(java.lang.Integer))") //包名.类名不建议省略
@Pointcut("execution(void com.itheima.service.DeptService.delete(java.lang.Integer))")
@Pointcut("execution(void com.itheima.service.DeptService.*(java.lang.Integer))")
@Pointcut("execution(* com.*.service.DeptService.*(*))")
@Pointcut("execution(* com.itheima.service.*Service.delete*(*))")
@Pointcut("execution(* com.itheima.service.DeptService.*(..))")
@Pointcut("execution(* com..DeptService.*(..))")
@Pointcut("execution(* com..*.*(..))")
@Pointcut("execution(* *(..))") //慎用
annotation详细使用
代码示例:
package com.sunny.aop;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 创建名为MyLog的切入点
*/
@Retention(RetentionPolicy.RUNTIME) //描述注解什么时候使用到
@Target(ElementType.METHOD) //描述注解的使用对象——方法
public @interface MyLog {
}
@MyLog //加入设定的annotation注解
@Override
public List<Dept> list() {
List<Dept> deptList = deptMapper.list();
return deptList;
}
@MyLog //加入设定的annotation注解
@Override
public void delete(Integer id) {
//1. 删除部门
deptMapper.delete(id);
}
连接点使用
代码案例:
package com.sunny.aop;
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.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;
//切面类
@Slf4j
@Aspect
@Component
public class MyAspect8 {
@Pointcut("execution(* com.sunny.service.DeptService.*(..))")
private void pt(){}
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info("MyAspect8 ... 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. 获取 目标方法的方法名 .
String methodName = joinPoint.getSignature().getName();
log.info("目标方法的方法名: {}",methodName);
//3. 获取 目标方法运行时传入的参数 .
Object[] args = joinPoint.getArgs();
log.info("目标方法运行时传入的参数: {}", Arrays.toString(args));
//4. 放行 目标方法执行 .
Object result = joinPoint.proceed();
//5. 获取 目标方法运行的返回值 .
log.info("目标方法运行的返回值: {}",result);
log.info("MyAspect8 around after ...");
return result;
}
}