详解MyBatis自定义SQL拦截器

 

前言

本文主要是讲通过 MyBaits 的 Interceptor 的拓展点进行对 MyBatis 执行 SQL 之前做一个逻辑拦截实现自定义逻辑的插入执行。

适合场景:1. 比如限制数据库查询最大访问条数;2. 限制登录用户只能访问当前机构数据。

 

定义是否开启注解

定义是否开启注解, 主要做的一件事情就是是否添加 SQL 拦截器。

// 全局开启
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyBatisSqlInterceptorConfiguration.class)
public @interface EnableSqlInterceptor {

}

// 自定义注解
@Target({ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DataScope {

}

 

注册SQL 拦截器

注册一个 SQL 拦截器,会对符合条件的 SQL 查询操作进行拦截。

public class MyBatisSqlInterceptorConfiguration implements ApplicationContextAware {

  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
      SqlSessionFactory sqlSessionFactory = applicationContext.getBean(SqlSessionFactory.class);
      sqlSessionFactory.getConfiguration().addInterceptor(new MyBatisInterceptor());
  }
}


 

处理逻辑

在处理逻辑中,我主要是做一个简单的 limit 1 案例,如果是自己需要做其他的逻辑需要修改

@Slf4j
@Intercepts(
      {
              @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}),
      })
public class MyBatisInterceptor implements Interceptor {

  private static final Logger LOGGER = LoggerFactory.getLogger(MyBatisInterceptor.class);

  @Override
  public Object intercept(Invocation invocation) throws Throwable {
      // TODO Auto-generated method stub
      Object[] args = invocation.getArgs();
      MappedStatement ms = (MappedStatement) args[0];
      Object parameter = args[1];
      RowBounds rowBounds = (RowBounds) args[2];
      ResultHandler resultHandler = (ResultHandler) args[3];
      Executor executor = (Executor) invocation.getTarget();
      CacheKey cacheKey;
      BoundSql boundSql;
      //由于逻辑关系,只会进入一次
      if (args.length == 4) {
          //4 个参数时
          boundSql = ms.getBoundSql(parameter);
          cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
      } else {
          //6 个参数时
          cacheKey = (CacheKey) args[4];
          boundSql = (BoundSql) args[5];
      }
      DataScope dataScope = getDataScope(ms);
      if (Objects.nonNull(dataScope)) {
          String origSql = boundSql.getSql();
          log.info("origSql : {}", origSql);
          // 组装新的 sql
          // todo you weaving business
          String newSql = origSql + " limit 1";

          // 重新new一个查询语句对象
          BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), newSql,
                  boundSql.getParameterMappings(), boundSql.getParameterObject());

          // 把新的查询放到statement里
          MappedStatement newMs = newMappedStatement(ms, new BoundSqlSource(newBoundSql));
          for (ParameterMapping mapping : boundSql.getParameterMappings()) {
              String prop = mapping.getProperty();
              if (boundSql.hasAdditionalParameter(prop)) {
                  newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
              }
          }

          args[0] = newMs;
          if (args.length == 6) {
              args[5] = newMs.getBoundSql(parameter);
          }
      }
      LOGGER.info("mybatis intercept sql:{},Mapper方法是:{}", boundSql.getSql(), ms.getId());


      return invocation.proceed();
  }

  private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
      MappedStatement.Builder builder = new
              MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
      builder.resource(ms.getResource());
      builder.fetchSize(ms.getFetchSize());
      builder.statementType(ms.getStatementType());
      builder.keyGenerator(ms.getKeyGenerator());
      if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
          builder.keyProperty(ms.getKeyProperties()[0]);
      }
      builder.timeout(ms.getTimeout());
      builder.parameterMap(ms.getParameterMap());
      builder.resultMaps(ms.getResultMaps());
      builder.resultSetType(ms.getResultSetType());
      builder.cache(ms.getCache());
      builder.flushCacheRequired(ms.isFlushCacheRequired());
      builder.useCache(ms.isUseCache());
      return builder.build();
  }


  private DataScope getDataScope(MappedStatement mappedStatement) {
      String id = mappedStatement.getId();
      // 获取 Class Method
      String clazzName = id.substring(0, id.lastIndexOf('.'));
      String mapperMethod = id.substring(id.lastIndexOf('.') + 1);

      Class<?> clazz;
      try {
          clazz = Class.forName(clazzName);
      } catch (ClassNotFoundException e) {
          return null;
      }
      Method[] methods = clazz.getMethods();

      DataScope dataScope = null;
      for (Method method : methods) {
          if (method.getName().equals(mapperMethod)) {
              dataScope = method.getAnnotation(DataScope.class);
              break;
          }
      }
      return dataScope;
  }

  @Override
  public Object plugin(Object target) {
      // TODO Auto-generated method stub
      LOGGER.info("MysqlInterCeptor plugin>>>>>>>{}", target);
      return Plugin.wrap(target, this);
  }

  @Override
  public void setProperties(Properties properties) {
      // TODO Auto-generated method stub
      String dialect = properties.getProperty("dialect");
      LOGGER.info("mybatis intercept dialect:>>>>>>>{}", dialect);
  }

  /**
   * 定义一个内部辅助类,作用是包装 SQL
   */
  class BoundSqlSource implements SqlSource {
      private BoundSql boundSql;

      public BoundSqlSource(BoundSql boundSql) {
          this.boundSql = boundSql;
      }

      public BoundSql getBoundSql(Object parameterObject) {
          return boundSql;
      }

  }

}

 

如何使用

我们在 XXXMapper 中对应的数据操作方法只要加入 @DataScope 注解即可。

@Mapper
public interface OrderMapper {

 @Select("select 1 ")
 @DataScope
 Intger selectOne();

}

参考资料

mybatis.org/mybatis-3/z…

 

总结

关于MyBatis自定义SQL拦截器的文章就介绍至此,更多相关MyBatis自定义SQL拦截器内容请搜索编程宝库以前的文章,希望大家多多支持编程宝库

 什么是字符串字符串或串(String)是由数字、字母、下划线组成的一串字符。一般记为 s=“a1a2・・・an”(n>=0)。它是编程语言中表示文本的数据类型。在程序设计中,字符串(stri ...