背景

在将mysql 迁移到达梦的过程中,有同事反馈达梦方法级别的插件失效。

插件实现原理

  • 在MyBatis调用Mapper方法时进行拦截

  • 自动将原方法调用重定向到对应的DM版本方法

mybatis 两种插件,一种是executor,一种是stamentHandler.

我的插件代码如下:

package cn.com.cbim.task.config;

import cn.hutool.json.JSONUtil;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


@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})
})
@Component
@Slf4j
public class MethodInterceptor implements Interceptor {
  @Value("${task.dbType:mysql}")
  private String dbType; // 数据库类型配置

  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this); // 包装目标对象
  }

  @Override
  public void setProperties(Properties properties) {} // 设置属性方法

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    System.out.println("intercept-开始执行方法-" + invocation.getMethod().getName());
    // 获取StatementHandler
    Object[] args = invocation.getArgs(); // 方法参数
    MappedStatement ms = (MappedStatement) args[0]; // 映射语句
    String methodId = ms.getId(); // 方法ID

    // 如果是DM数据库且方法名不带Dm后缀
    if ("dm".equals(dbType) && !methodId.endsWith("DM")) {
      try {
        // 尝试查找对应的Dm方法
        Configuration configuration = ms.getConfiguration();
        String dmMethodId = methodId + "DM";
        MappedStatement dmMs = configuration.getMappedStatement(dmMethodId);

        // 如果找到Dm方法则替换执行
        if (dmMs != null) {
          log.info("intercept执行的方法是-" + dmMethodId);
          args[0] = dmMs; // 替换为DM方法
        }
      } catch (Exception e) {
        log.error("intercept-没有找到对应的Dm方法-继续执行原方法-或尝试使用sql替换-" + methodId,e);
      }
    }
    Object proceed = invocation.proceed(); // 执行拦截方法
    log.info(JSONUtil.parse(proceed).toStringPretty()); // 记录执行结果
    return proceed;
  }
}

我的是执行器插件不生效,以下以这个为例。

第一步:检查插件是否注册

package org.apache.ibatis.plugin;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @author Clinton Begin
 */
public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }

}

如果未生效,则检查自己的插件方法是否遗漏重要的@Intercepts 注解或签名定义。生效则进行下一步。

@Intercepts({
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class YourInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 实现逻辑
        return invocation.proceed();
    }
}

第二步  在插件执行的方法中进行断点调试

org.apache.ibatis.plugin.Plugin

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
            return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
        } catch (Exception var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

经过断点调试后发现,项目配置了MybatisPlusInterceptor 的拦截器,在这个拦截器中变更了默认方法。

 是因为这个插件将默认方法进行扩展两个参数。 所以需要在我们拦截器的放上再添加上签名方法即可。如下图所示:

完整代码如下:

import cn.hutool.json.JSONUtil;
import java.util.Properties;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

// 定义拦截器签名
@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}),
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})
})
@Component
@Slf4j
public class MethodInterceptor implements Interceptor {
  @Value("${task.dbType:mysql}")
  private String dbType; // 数据库类型配置

  @Override
  public Object plugin(Object target) {
    return Plugin.wrap(target, this); // 包装目标对象
  }

  @Override
  public void setProperties(Properties properties) {} // 设置属性方法

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
    System.out.println("intercept-开始执行方法-" + invocation.getMethod().getName());
    // 获取StatementHandler
    Object[] args = invocation.getArgs(); // 方法参数
    MappedStatement ms = (MappedStatement) args[0]; // 映射语句
    String methodId = ms.getId(); // 方法ID

    // 如果是DM数据库且方法名不带Dm后缀
    if ("dm".equals(dbType) && !methodId.endsWith("DM")) {
      try {
        // 尝试查找对应的Dm方法
        Configuration configuration = ms.getConfiguration();
        String dmMethodId = methodId + "DM";
        MappedStatement dmMs = configuration.getMappedStatement(dmMethodId);

        // 如果找到Dm方法则替换执行
        if (dmMs != null) {
          log.info("intercept执行的方法是-" + dmMethodId);
          args[0] = dmMs; // 替换为DM方法
        }
      } catch (Exception e) {
        log.error("intercept-没有找到对应的Dm方法-继续执行原方法-或尝试使用sql替换-" + methodId,e);
      }
    }
    Object proceed = invocation.proceed(); // 执行拦截方法
    log.info(JSONUtil.parse(proceed).toStringPretty()); // 记录执行结果
    return proceed;
  }
}

运行日志如下则说明生效了:

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐