当前位置: 首页 > news >正文

jeesite mybatis添加拦截器,推送指定表的变更数据到其他数据库

一、在mybatis-config.xml添加拦截器

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!-- 全局参数 --><settings><!-- 使全局的映射器启用或禁用缓存。 --><setting name="cacheEnabled" value="true"/><!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 --><setting name="lazyLoadingEnabled" value="true"/><!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 --><setting name="aggressiveLazyLoading" value="true"/><!-- 是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true --><setting name="multipleResultSetsEnabled" value="true"/><!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true --><setting name="useColumnLabel" value="true"/><!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false  --><setting name="useGeneratedKeys" value="false"/><!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不映射 PARTIAL:部分  FULL:全部  --><setting name="autoMappingBehavior" value="PARTIAL"/><!-- 这是默认的执行类型(SIMPLE: 简单;REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新)  --><setting name="defaultExecutorType" value="SIMPLE"/><!-- 使用驼峰命名法转换字段。 --><setting name="mapUnderscoreToCamelCase" value="true"/><!-- 设置本地缓存范围 session:就会有数据的共享 statement:语句范围 (这样就不会有数据的共享 ) defalut:session --><setting name="localCacheScope" value="SESSION"/><!-- 设置但JDBC类型为空时,某些驱动程序 要指定值,default:OTHER,插入空值时不需要指定类型 --><setting name="jdbcTypeForNull" value="NULL"/><!-- 迭代集合的时候如果空值,则忽略而不抛出异常 --><setting name="nullableOnForEach" value="true"/><!-- 返回值为Map时,当返回空值字段时,仍然需要返回这个Key --><setting name="callSettersOnNulls" value="true"/></settings><!-- 类型别名 --><typeAliases><typeAlias alias="Page" type="com.jeesite.common.entity.Page" /><!--分页  --></typeAliases><!-- 插件配置 --><plugins><plugin interceptor="com.jeesite.common.mybatis.interceptor.DataSourceInterceptor" /><plugin interceptor="com.jeesite.common.mybatis.interceptor.PaginationInterceptor" /><plugin interceptor="com.jeesite.modules.station.entity.SqlExtractInterceptor"/></plugins></configuration>

在这里插入图片描述

二、新增拦截器SqlExtractInterceptor

package com.jeesite.modules.station.entity;import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;import static com.jeesite.modules.station.entity.CrossDbExecutor.executeInAnotherDb;@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
//        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class SqlExtractInterceptor implements Interceptor {// ==== 新增:目标监听表名(可通过配置文件注入)====private Set<String> targetTables = new HashSet<>();@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement ms = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];BoundSql boundSql = ms.getBoundSql(parameter);String sql = boundSql.getSql();Object[] params = getParameters(boundSql, parameter);// ==== 新增:1. 过滤非修改操作(只处理 INSERT/UPDATE/DELETE)====SqlCommandType commandType = ms.getSqlCommandType();if (commandType != SqlCommandType.INSERT&& commandType != SqlCommandType.UPDATE&& commandType != SqlCommandType.DELETE) {return invocation.proceed(); // 非修改操作,直接放行}// ==== 新增:2. 提取 SQL 中的表名并匹配目标表 ====String tableName = extractTableName(sql, commandType);if (tableName == null || !targetTables.contains(tableName.toLowerCase())) {return invocation.proceed(); // 表名不匹配,直接放行}// ==== 原有逻辑:仅当上述条件满足时执行跨库操作 ====executeInAnotherDb(sql, params);return invocation.proceed();}// ==== 新增:从 SQL 中提取表名 ====private String extractTableName(String sql, SqlCommandType commandType) {String lowerSql = sql.toLowerCase().trim();Pattern pattern = null;// 根据操作类型匹配表名(简单处理,可根据实际 SQL 复杂度增强)switch (commandType) {case INSERT:// 新增:支持反引号包裹的表名(如 `js_sys_log`)pattern = Pattern.compile("insert\\s+into\\s+`?([a-zA-Z0-9_]+)`?");break;case UPDATE:// 保持不变(或同步增强,如支持反引号)pattern = Pattern.compile("update\\s+`?([a-zA-Z0-9_]+)`?");break;case DELETE:// 保持不变(或同步增强,如支持反引号)pattern = Pattern.compile("delete\\s+from\\s+`?([a-zA-Z0-9_]+)`?");break;default:return null;}Matcher matcher = pattern.matcher(lowerSql);if (matcher.find()) {return matcher.group(1); // 返回匹配到的表名}return null;}// ==== 新增:允许通过配置文件设置目标表(如 mybatis-config.xml 或 Spring 配置)====@Overridepublic void setProperties(Properties properties) {
//        String tables = properties.getProperty("targetTables");String tables = "jk_station_config,js_sys_log";if (tables != null && !tables.isEmpty()) {targetTables.addAll(Arrays.asList(tables.split(",")));}}private Object[] getParameters(BoundSql boundSql, Object parameter) {if (parameter == null) {return new Object[0]; // 无参数时返回空数组}// 1. 处理数组类型参数if (parameter.getClass().isArray()) {return (Object[]) parameter;}// 2. 处理集合类型参数(List/Set等)if (parameter instanceof Collection) {return ((Collection<?>) parameter).toArray(new Object[0]);}// 3. 处理MyBatis参数包装(重点修复:支持命名参数和StrictMap)if (parameter instanceof Map) {Map<?, ?> paramMap = (Map<?, ?>) parameter;// 3.1 处理MyBatis内部StrictMap(多参数场景)if (paramMap.containsKey("__frch_item_0")) { // 迭代参数标记(如foreach)List<Object> paramList = new ArrayList<>();for (Object key : paramMap.keySet()) {if (key.toString().startsWith("__frch_")) {paramList.add(paramMap.get(key));}}return paramList.toArray(new Object[0]);}// 3.2 处理命名参数(@Param注解)或param1,param2...格式List<Object> paramList = new ArrayList<>();if (paramMap.containsKey("param1")) {// 按param1,param2...顺序提取int i = 1;while (paramMap.containsKey("param" + i)) {paramList.add(paramMap.get("param" + i));i++;}} else {// 按参数名排序后提取(确保顺序一致)List<String> sortedKeys = new ArrayList<>(paramMap.keySet().stream().map(Object::toString).collect(Collectors.toList()));Collections.sort(sortedKeys);for (String key : sortedKeys) {paramList.add(paramMap.get(key));}}return paramList.toArray(new Object[0]);}// ===== 新增:处理实体对象参数 =====// 判断是否为自定义实体(排除Java内置类型)if (isCustomEntity(parameter)) {try {
//                List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();List<Object> paramValues = new ArrayList<>();// 遍历SQL参数映射,提取实体属性值for (ParameterMapping pm : parameterMappings) {String propertyName = pm.getProperty();if (propertyName == null || propertyName.isEmpty()) {continue;}// 通过反射获取实体属性值PropertyDescriptor pd = new PropertyDescriptor(propertyName, parameter.getClass());Method getter = pd.getReadMethod();Object value = getter.invoke(parameter);paramValues.add(value);}return paramValues.toArray(new Object[0]);} catch (Exception e) {// 反射失败时降级为原逻辑(避免阻断执行)System.err.println("实体参数解析失败: " + e.getMessage());}}// 4. 单个实体对象直接包装为数组return new Object[]{parameter};}// 判断是否为自定义实体(排除Java内置类型和MyBatis包装类型)private boolean isCustomEntity(Object parameter) {Class<?> clazz = parameter.getClass();// 排除基本类型、包装类型、字符串、日期等内置类型if (clazz.isPrimitive() ||clazz.getName().startsWith("java.") ||clazz.getName().startsWith("javax.") ||parameter instanceof Number ||parameter instanceof Boolean ||parameter instanceof Character) {return false;}// 排除MyBatis内部参数对象if (clazz.getName().startsWith("org.apache.ibatis")) {return false;}return true;}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}}

此处修改要推送的表:
在这里插入图片描述

三、新增JDBC连接器

package com.jeesite.modules.station.entity;import java.sql.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import lombok.extern.log4j.Log4j2;@Log4j2
public class CrossDbExecutor {// 目标数据库连接信息(建议通过配置文件读取)private static final String TARGET_JDBC_URL = "jdbc:mysql://127.0.0.1:3306/datang_znaf_jk?useSSL=false&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=CONVERT_TO_NULL&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true";private static final String TARGET_USERNAME = "root";private static final String TARGET_PASSWORD = "123456";public static void executeInAnotherDb(String sql, Object[] params) {if (sql == null || sql.trim().isEmpty()) {return;}// ==== 新增:校验参数数量与SQL占位符数量是否匹配 ====int placeholderCount = countPlaceholders(sql);int paramCount = (params == null) ? 0 : params.length;if (paramCount > 0 && placeholderCount == 0) {System.err.println("警告:SQL无参数占位符,但传入了" + paramCount + "个参数,已跳过执行");return; // 无占位符时不执行参数设置}if (paramCount != placeholderCount) {System.err.println("警告:参数数量(" + paramCount + ")与占位符数量(" + placeholderCount + ")不匹配,已跳过执行");return;}log.info("执行跨库SQL: {},参数: {}", sql, params);try (Connection conn = DriverManager.getConnection(TARGET_JDBC_URL, TARGET_USERNAME, TARGET_PASSWORD);PreparedStatement pstmt = conn.prepareStatement(sql)) {// 设置参数(仅当参数不为空且有占位符时)if (params != null && params.length > 0 && placeholderCount > 0) {for (int i = 0; i < params.length; i++) {pstmt.setObject(i + 1, params[i]); // 参数索引从1开始}}// 执行SQL(根据类型选择执行方法)if (sql.trim().toLowerCase().startsWith("select")) {try (ResultSet rs = pstmt.executeQuery()) {// 处理查询结果(按需实现)}} else {int affectedRows = pstmt.executeUpdate();}} catch (SQLException e) {
//            throw new RuntimeException("跨库执行SQL失败: " + e.getMessage(), e);log.info("跨库执行SQL失败: {}", e.getMessage());}log.info("跨库执行SQL成功: {}", sql);}// ==== 新增:统计SQL中的参数占位符数量(支持标准?占位符) ====private static int countPlaceholders(String sql) {// 正则匹配SQL中的?占位符(排除字符串中的?)Pattern pattern = Pattern.compile("(?<!')\\?(?!')");Matcher matcher = pattern.matcher(sql);int count = 0;while (matcher.find()) {count++;}return count;}
}
http://www.dtcms.com/a/479630.html

相关文章:

  • [MRCTF2020]千层套路1
  • 新网站建设的工作总结怎么在网上免费做公司网站
  • .net商城网站开发开设计公司要怎么规划
  • 在哪里可以做个人网站上海建站shwzzz
  • 项目管理的核心价值是什么?
  • 哪有做机械设计的网站无经验培训 网页设计学员
  • 常州网站建设平台带视频的网站模板
  • DrayTek Vigor路由器mainfunction.cgi接口存在命令执行漏洞
  • 企业内部网站打不开甜点的网站建设规划书
  • 网站loading动画wordpress 做app
  • STM32 DMA直接存储器访问(寄存器与HAL库实现)
  • 百度一下首页网页百度企业seo排名哪家好
  • 辽阳做网站做php网站需要什么软件开发
  • 植物大战僵尸融合版,支持手机+电脑,附修改器!
  • 做网站需要哪些工程师网站添加文章
  • 网站开发与app差距网页制作与设计属于什么专业
  • 网站代理浏览器0网络营销公司全网天下
  • 使用Reindex迁移Elasticsearch集群数据详解(下)
  • 成都百度推广账户优化网站建设关键字优化
  • 设计素材网站版权连云港网站建设sitall
  • 雪花算法生成id
  • 去哪个网站可以接单做ps等等苏州建网站公司
  • wordpress修改侧边栏wordpress优化数据库
  • 成都网站制什么网站容易收录
  • 首都医科大学网站建设做网站最少几个页面
  • 肿瘤微环境介导的免疫耐受在胃癌发生和治疗中的作用
  • Spring Cloud微服务篇面试题总结
  • 刚学完网站开发设计师学编程能自己做网站吗
  • 织梦网站建设选项卡教程asp 精品网站制作
  • 宝宝树以“奇迹2.0”重构营销范式:AI驱动母婴行业迈向全域智能