Spring事务和AOP
事务四大特性
原子性:事务不可再分,一组事务只能同时成功或失败
持久性:事务一旦提交就一定会持久化到数据库
隔离性:事务之前不会互相干扰
一致性:数据操作前后,总量保持不变
事务管理
- 事务管理是应用系统开发中必不可少的一部分,Spring 为事务管理提供了丰富的功能支持
- Spring 事务管理分为编程式和声明式的两种方式
-
- 编程式:是指在写业务代码中将事务代码也写进去,这是很古老的做法了,在现在看起来可能不可思议
- 声明式:基于AOP将具体业务逻辑与事务处理解耦,声明式事务管理使业务代码逻辑不受污染,因此在实际使用中声明式事务用的比较多
- 声明式事务有两种方式,一种是在配置文件中做相关的事务规则声明,另一种是基于@Transactional 注解的方式,注解方式是目前主流方式
Transactional注解
- @Transactional:此注解是 Spring 提供用来控制事务回滚/提交的一个注解,属于声明式事务的实现
- 作用域:@Transactional可以写在类、接口、方法上
-
- 当标注在类上的时候,表示给该类所有的 public 方法添加上 @Transactional注解
- Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。像 CGLib 动态代理采用继承的方式将会导致 @Transactional 注解失效
- 当标注在方法上的时候,事务的作用域就只在该方法上生效,并且如果类及方法上都配置 @Transactional注解时,方法的注解会覆盖类上的注解
/**
* readOnly:只读模式,默认false
* SUPPORTS:支持模式,被调用时当前方法有事务就加入,没有就不使用
* REQUIRED(默认):必须要有事务,当前方法有事务就加入,没有就建一个事务
* NEVER:以非事务方式执行,如果存在事务则抛出异常。
* REQUIRES_NEW:创建一个新事务,如果存在当前事务,则暂停当前事务
*/
@Service("studentServiceImpl")
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class StudentServiceImpl implements IStudentService {
@Autowired
private StudentMapper studentMapper;
@Override
public Student findStudentById(Long id) {
System.out.println("开始查询");
return studentMapper.selectById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
//指定发生异常类型回归
public void saveStudent(Student student) {
studentMapper.insert(student);
}
事务传播机制
- Propagation.REQUIRED:默认,支持当前事务,如果当前没有事务,就创建一个事务,保证一定有事务 -- 增删改方法使用
- Propagation.SUPPORTS:支持当前事务,如果当前没有事务,就不使用事务 -- 查询方法使用
- Propagation.REQUIRES_NEW:新建事务,如果当前有事务,就挂起 -- 不常用
- Propagation.NAVEN:不支持事务,如果当前有事务,就抛出异常 -- 不常用
AOP
概述
Aspect Oriented Programming:简称Aop,意思是面向切面编程,是Spring中的第二大核心,第一大核心是IOC。Aop 是一种编程思想,是面向对象编程(OOP)的一种补充
应用场景
- 日志记录:自动记录用户的所有操作到数据库中
- 事务管理:自动在Service层方法前后开启提交事务
- 权限验证:自动在执行业务代码前执行权限校验
- 性能监控:自动记录Service层方法执行耗时
- AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离
代理模式
AOP底层由动态代理实现,分别有JDK动态代理和CGLIB动态代理
静态代理
- 静态代理是一种代理模式的实现方式,它在编译期间就已经确定了代理对象,需要为每个被代理对象创建一个代理类
- 静态代理的实现比较简单,但是每个被代理的对象都需要创建一个代理类,因此在代理对象比较多时,会导致代码冗余和维护成本增加
动态代理
- 动态代理是一种代理模式的实现方式,它在运行期间根据需要动态生成代理对象,无需手动编写代理类,可以减少代码冗余和维护成本
- 动态代理适用于需要代理的对象数量较多,代理类实现相对灵活的场景,例如Spring中的Aop就是使用的动态代理
AOP实战
1.核心概念
名称 | 说明 |
Joinpoint:连接点 | 连接点指的是可以被Aop控制的方法,例如:入门程序当中所有的Service层方法都是可以被Aop控制的 |
Pointcut:切入点 | 切入点指的是哪些类、方法要被拦截,也就是哪些连接点要被拦截,例如:入门程序中切入点就是Service层所有方法 |
Advice:通知 | 通知指的是要作用到连接点的功能,例如:入门程序中的四种通知 |
Target:目标 | 目标指的是被代理的对象,例如:入门案例中的UserServiceImpl就是目标对象 |
Aspect:切面 | 切面指的是切入点和通知的结合,例如:入门案例中的TxManager就被定义为切面 |
Weaving:织入 | 织入指的是将增强处理运用到目标对象的过程,例如:在入门案例中事务增强到Service层方法的过程就是织入 |
Proxy:代理 | 代理指的是被增强后的对象,也就是织入了增强处理的类,在程序运行时看的到 |
2.通知分类
通知 | 说明 |
before:前置通知 | 通知方法在目标方法调用之前执行 |
after:后置通知 | 通知方法在目标方法执行后执行,核心方法是否异常都会执行 |
after-returning:返回后通知 | 通知方法会在目标方法执行后执行,核心方法异常后不执行 |
after-throwing:抛出异常通知 | 通知方法会在目标方法抛出异常后执行 |
around:环绕通知 | 通知方法会将目标方法封装起来 |
@Component
@Aspect//定义切面
public class AopConfig {
@Autowired
private TxManger txManger;
* 定义切入点
*//*
@Pointcut("execution(* cn.lgc.service.IStudentService.*(..))")
public void pointcut() {
}
/**
* 前置通知
*//*
*/
@Before("pointcut()")
public void before() {
txManger.begin();
}
/**
* 后置通知
*//*
*/
@AfterReturning("pointcut()")
public void afterReturning() {
txManger.commit();
}
/**
* 最终通知
*//*
*/
@After("pointcut()")
public void after() {
txManger.close();
}
/**
* 异常通知
*//*
*/
@AfterThrowing(value = "pointcut()", throwing = "e")
public void afterThrowing(Exception e) {
txManger.rollback();
System.out.println(e.getMessage());
}
/**
* 环绕通知
*
* @param pjp
* @return {@link Object}
*/
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) {
try {
//获取目标方法名
Signature signature = pjp.getSignature();
String name = signature.getName();
if (name.startsWith("find") || name.startsWith("get")) {
//查询,不开启事务
Object proceed = pjp.proceed();
txManger.close();
return proceed;
} else {
//增删改
txManger.begin();
Object proceed = pjp.proceed();
txManger.commit();
txManger.close();
return proceed;
}
} catch (Throwable e) {
e.printStackTrace();
txManger.rollback();
}
return null;
}
}
3.aop实现日志打印
@Component
@Aspect
public class LogConfig {
//日志持久化对象
@Autowired
private LogMapper logMapper;
//获取当前请求ip
@Autowired
private HttpServletRequest request;
//切入点
@Pointcut("execution(* cn.lgc.service.IStudentService.*(..))")
public void pointcut() {
}
//通知
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) {
Log log = new Log();
try {
Object result = resultWrapper(joinPoint, log);
logMapper.insert(log);
//结果传递
return result;
} catch (Throwable e) {
e.printStackTrace();
System.err.println("日志添加失败");
} finally {
}
return null;
}
/**
* 结果包装器
*
* @param joinPoint 加入点
* @param log 日志
* @return {@link Object}
* @throws Throwable
*/
private Object resultWrapper(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
//获取目标类完全限定名
String className = joinPoint.getTarget().getClass().getName();
//获取方法名
String methodName = joinPoint.getSignature().getName();
//获取参数类型
String args = Arrays.stream(joinPoint.getArgs()).toString();
long begin = System.currentTimeMillis();
//执行目标方法
Object result = joinPoint.proceed();
long costTime = System.currentTimeMillis() - begin;
String ip = request.getRemoteAddr();
//创建持久化对象
log.setClassName(className);
log.setMethodName(methodName);
log.setMethodParams(args);
log.setUserId(9527L);
log.setUserName("admin");
log.setReturnValue(Optional.ofNullable(result).map(m -> m.getClass().getName()).orElse(null));
log.setCostTime(costTime);
log.setCreateTime(new Date());
log.setIp(ip);
return result;
}
}