做网站前端实战视频,品牌营销策划英文,wordpress博客侧边栏上如何添加图片或者博主的简介等,django 做的网站在本篇文章中#xff0c;我们将深入了解如何编写一个 MyBatis 拦截器#xff0c;并通过一个示例来展示如何在执行数据库操作#xff08;如插入或更新#xff09;时#xff0c;自动填充某些字段#xff08;例如 createdBy 和 updatedBy#xff09;信息。本文将详细讲解拦…在本篇文章中我们将深入了解如何编写一个 MyBatis 拦截器并通过一个示例来展示如何在执行数据库操作如插入或更新时自动填充某些字段例如 createdBy 和 updatedBy信息。本文将详细讲解拦截器的工作原理、代码示例及其在 MyBatis 项目中的应用。
一、为什么需要数据库操作拦截器
在典型的企业级应用开发中我们经常会遇到这样的需求 需要自动记录数据行的创建时间和修改时间 需要跟踪记录数据操作人 需要实现全局的软删除逻辑 需要对敏感数据进行自动加密 需要实现SQL执行监控和慢查询统计
传统做法是在每个Mapper方法中手动添加这些逻辑但这样会导致大量重复代码。MyBatis拦截器Interceptor正是为了解决这类问题而生它可以在SQL执行的各个阶段插入自定义逻辑实现横切关注点的统一管理。
二、MyBatis拦截器核心原理 如果对于MyBatis核心组件功能还不了解的小伙伴们建议先跳到最后一章节:第五节去了解MyBatis核心组件功能再倒回来继续学习 2.1 拦截器架构
MyBatis采用责任链模式实现拦截器机制主要拦截点包括
拦截接口拦截时机典型应用场景ExecutorSQL执行前后增删改查操作事务管理、分页处理StatementHandlerSQL语句构建时SQL改写、危险操作拦截ParameterHandler参数处理时参数加密、参数校验ResultSetHandler结果集处理时结果解密、数据脱敏
2.2 拦截器生命周期
MyBatis 中的拦截器生命周期较为简单。它的生命周期由 Plugin.wrap() 方法控制首先会创建代理对象并包装目标对象。拦截器的 intercept 方法在执行目标方法前被调用拦截器的 plugin 方法用于对目标方法的代理setProperties 方法用于注入拦截器所需的配置属性。
拦截目标方法调用的顺序当拦截器装载到 MyBatis 配置中后每次执行目标方法如数据库操作的 insert、update时都会经过拦截器链。如果多个拦截器存在它们会按照配置顺序依次执行。可以通过实现Ordered接口或使用Order注解控制执行顺序
Intercepts(...)
Order(Ordered.HIGHEST_PRECEDENCE)
public class FirstInterceptor implements Interceptor {}Intercepts(...)
Order(Ordered.LOWEST_PRECEDENCE)
public class LastInterceptor implements Interceptor {}
关键接口解析 初始化阶段通过Intercepts注解声明拦截目标 代理阶段通过plugin()方法创建代理对象 执行阶段intercept()方法处理拦截逻辑 配置阶段通过XML或Java Config注册拦截器
// 典型拦截器声明
Intercepts({Signature(type Executor.class, method update,args {MappedStatement.class, Object.class})
})
public class CustomInterceptor implements Interceptor {// 实现方法...
}
2.3 代理模式与责任链模式
代理模式Proxy PatternMyBatis 拦截器本质上是通过代理模式对目标对象进行包装通过 Plugin.wrap() 方法将拦截器包装在目标对象上形成一个代理对象。这个代理对象会拦截对目标方法的调用执行预定的增强逻辑再将控制权交给目标方法。责任链模式Chain of Responsibility PatternMyBatis 的拦截器是按顺序进行链式调用的多个拦截器会组成一个链按照声明顺序依次执行直到所有拦截器都被调用。
三、实战实现自动化字段填充
3.1 需求分析
实现以下字段的自动填充
字段名插入时自动填充更新时自动填充数据类型created_by✔️❌Stringcreated_time✔️❌Dateupdated_by❌✔️Stringupdated_time❌✔️Dateis_deleted✔️默认0❌Integer 3.2 完整实现代码解析
package com.wanren.subject.application.interceptor;import com.wanren.subject.common.util.LoginUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.Executor;/***ClassName MybatisInterceptor*Description 填充createBy,createTime等公共字段的拦截器*Author 彭于晏*Date 2025/2/14 0:31*Version 1.0**/
Component
Slf4j
//Signature指明该拦截器需要拦截哪一个接口的哪一个方法,包括如下
// 接口类型Executor拦截执行器方法负责调用StatementHandler操作数据库
// StatementHandler//拦截SQL语法构建处理直接在数据库执行SQL脚本的对象、
// ParameterHandler//拦截参数处理和ResultSetHandler//拦截结果集处理ResultSet结果集对象转换成List类型的集合
//对应接口中的某一个方法的参数比如Executor中query方法因为重载原因有多个args就是指明参数类型从而确定是具体哪一个方法。
Intercepts(Signature(type Executor.class,method update,args {MappedStatement.class,Object.class
}))
public class MybatisInterceptor implements Interceptor {//当被拦截的数据库操作如查询、插入、更新发生时intercept 方法会被调用。//invocation的三个方法Object target invocation.getTarget();//被代理对象//Method method invocation.getMethod();//代理方法//Object[] args invocation.getArgs();//方法参数Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement mappedStatement (MappedStatement) invocation.getArgs()[0];//MyBatis 中封装 SQL 语句信息的类表示一个 SQL 映射。MappedStatement 对象包含 SQL 执行所需的所有信息比如 SQL 命令类型、SQL 语句、参数类型等SqlCommandType sqlCommandType mappedStatement.getSqlCommandType();Object parameter invocation.getArgs()[1];//parameter 是一个 Object 类型通常是实体类对象if(parameter null){//执行目标方法invocation.proceed()return invocation.proceed();}//获取当前登录用户的idString loginId LoginUtil.getLoginId();if(StringUtils.isBlank(loginId)){return invocation.proceed();}if(sqlCommandType.INSERT sqlCommandType || sqlCommandType.UPDATE sqlCommandType){replaceEntityProperty(parameter,loginId,sqlCommandType);}return invocation.proceed();}private void replaceEntityProperty(Object parameter, String loginId, SqlCommandType sqlCommandType) {if(parameter instanceof Map){replaceMap((Map) parameter,loginId,sqlCommandType);}else{replace(parameter,loginId,sqlCommandType);}}private void replace(Object parameter, String loginId, SqlCommandType sqlCommandType) {if(SqlCommandType.INSERT sqlCommandType){dealInsert(parameter,loginId);}else{dealUpdate(parameter,loginId);}}private void dealUpdate(Object parameter, String loginId) {Field[] fields getAllFields(parameter);for (Field field : fields) {try {field.setAccessible(true);//parameter是字段名field是字段的值Object o field.get(parameter);//如果该字段不为null则跳过if (Objects.nonNull(o)) {field.setAccessible(false);continue;}if (updateBy.equals(field.getName())) {field.set(parameter, loginId);field.setAccessible(false);} else if (updateTime.equals(field.getName())) {field.set(parameter, new Date());field.setAccessible(false);} else {field.setAccessible(false);}}catch (Exception e){log.error(dealUpdate.error:{}, e.getMessage(), e);}}}private void dealInsert(Object parameter, String loginId) {Field[] fields getAllFields(parameter);for(Field field : fields){try{//Java 语言的私有字段是不可访问的。如果你想访问私有字段即在类外部访问 private 字段// 你必须调用 setAccessible(true) 来打破 Java 的访问控制允许访问这些字段。field.setAccessible(true);Object o field.get(parameter);if (Objects.nonNull(o)) {field.setAccessible(false);continue;}if(isDeleted.equals(field.getName())){field.set(parameter,0);field.setAccessible(false);}else if (createdBy.equals(field.getName())) {field.set(parameter, loginId);field.setAccessible(false);} else if (createdTime.equals(field.getName())) {field.set(parameter, new Date());field.setAccessible(false);}else {field.setAccessible(false);}}catch (Exception e){log.error(dealInsert.error:{}, e.getMessage(), e);}}}private Field[] getAllFields(Object parameter) {Class? clazz parameter.getClass();ListField fieldList new ArrayList();while (clazz ! null){fieldList.addAll(new ArrayList(Arrays.asList(clazz.getDeclaredFields())));clazz clazz.getSuperclass();}Field[] fields new Field[fieldList.size()];fieldList.toArray(fields);return fields;}private void replaceMap(Map parameter, String loginId, SqlCommandType sqlCommandType) {for(Object val : parameter.values()){replace(val,loginId,sqlCommandType);}}//插件用于封装目标对象,通过这个方法可以返回目标对象本身也可以返回一个他的代理决定//是否要进行拦截从而进一步决定要返回一个怎样的对象Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}//如果我们拦截器需要用到一些变量参数而且这个参数是支持可配置的类似Spring中的Value(${})从application.properties文件获取自定义变量属性这个时候我们就可以使用这个方法。//在拦截器插件的setProperties方法中进行。这些自定义属性参数会在项目启动的时候被加载。Overridepublic void setProperties(Properties properties) {}
}四、扩展应用场景
4.1 敏感数据加密
public Object intercept(Invocation invocation) {Object parameter invocation.getArgs()[1];if (parameter instanceof User) {User user (User) parameter;user.setPassword(encrypt(user.getPassword()));}return invocation.proceed();
}
4.2 SQL执行监控
public Object intercept(Invocation invocation) {long start System.nanoTime();try {return invocation.proceed();} finally {long cost (System.nanoTime() - start)/1000000;if(cost 1000){log.warn(慢查询警告: {}ms - {}, cost, getSql(invocation));}Metrics.counter(sql.total.count).increment();Metrics.timer(sql.execute.time).record(cost, MILLISECONDS);}
}
4.3 多租户数据隔离
public void replaceEntityProperty(Object parameter, String tenantId) {if (parameter instanceof TenantAware) {((TenantAware) parameter).setTenantId(tenantId);}
} 五、MyBatis核心组件功能详解
从MyBatis代码实现的角度来看MyBatis的核心组成部分涉及到多个组件和接口它们共同协作完成了数据库操作的管理、映射及优化等工作。以下是MyBatis的核心部件及其功能的详细介绍
1. Configuration配置对象
功能概述 Configuration 是 MyBatis 的全局配置中心负责管理所有配置信息包括数据库连接、映射器、类型处理器、插件等。它是 MyBatis 初始化的核心类。
核心职责 加载并解析 MyBatis 配置文件如 mybatis-config.xml。 管理 Mapper 文件和接口的配置信息。 注册类型处理器TypeHandler和对象工厂ObjectFactory。 维护拦截器链InterceptorChain用于支持插件扩展。
2. SqlSessionFactory会话工厂
功能概述 SqlSessionFactory 是 MyBatis 的核心工厂类用于创建 SqlSession 实例。它是线程安全的通常在应用启动时初始化。
核心职责 根据 Configuration 创建 SqlSession。 管理数据库连接池和事务工厂。 加载 Mapper 文件和对应的映射语句。
3. SqlSession会话
功能概述 SqlSession 是 MyBatis 与数据库交互的核心接口提供了执行 SQL、管理事务、获取 Mapper 接口等功能。
核心职责 执行 CRUD 操作select、insert、update、delete。 提供事务管理、批量操作、缓存等功能。 获取 Mapper 接口的代理对象。
4. Executor执行器
功能概述 Executor 是 MyBatis 的内部执行器负责执行 SQL 语句并处理结果。它是 MyBatis 中的核心对象之一负责查询、插入、更新等数据库操作。
核心职责 调用 StatementHandler 执行 SQL 语句。 管理一级缓存和二级缓存。 触发拦截器链支持插件扩展。
5. StatementHandler语句处理器
功能概述
StatementHandler 负责将 SQL 语句发送到数据库执行主要完成 SQL 的解析、生成和执行。
核心职责 创建 PreparedStatement 并设置参数。 执行 SQL 并返回结果。 支持一级缓存避免重复查询。
6. ParameterHandler参数处理器
功能概述 ParameterHandler 负责将 Java 对象转换为 JDBC 参数并设置到 PreparedStatement 中。
核心职责 处理 SQL 参数的映射。 将 Java 对象转换为 JDBC 所需的参数格式。
7. ResultSetHandler结果集处理器
功能概述 ResultSetHandler 负责将 JDBC 返回的 ResultSet 转换为 Java 对象集合。
核心职责 解析 ResultSet 结果集。 将数据库查询结果映射为 Java 对象。
8. TypeHandler类型处理器
功能概述 TypeHandler 用于 Java 类型和数据库类型之间的转换。负责将 Java 对象的属性值转换为数据库支持的数据类型或者将数据库查询结果转化为 Java 对象。
核心职责 处理 Java 类型与 JDBC 类型的映射。 支持自定义类型转换。
9. MappedStatement映射语句
功能概述 MappedStatement负责封装单个 select、insert、update、delete 等 SQL 语句的配置信息。包括 SQL 类型、参数映射、结果映射等。
核心职责 存储 SQL 语句、参数映射、返回值映射等信息。 提供 SQL 执行的上下文信息。
10. SqlSourceSQL 源
功能概述 SqlSource 负责根据传入的 parameterObject 动态生成 SQL 语句。它通过 BoundSql 对象封装 SQL 和参数并返回给 StatementHandler 执行。
核心职责 动态生成 SQL 语句。 根据传入的参数对象构建 SQL 语句并封装到 BoundSql 中。
11. BoundSql封装的 SQL
功能概述
BoundSql 负责封装动态生成的 SQL 语句和参数信息。它是 SqlSource 生成 SQL 后的载体存储了 SQL 语句及其相关参数信息。
核心职责 存储动态生成的 SQL 语句。 提供 SQL 执行所需的参数信息。 优秀的框架设计应该像电路板一样允许开发者在不修改核心逻辑的情况下通过插件机制扩展功能。 —— MyBatis设计哲学