Mybatis 插件兼容动态SQL

作者: 晴天哥_王志 | 来源:发表于2021-10-24 21:36 被阅读0次

系列

开篇

  • 在mybatis的语法当中支持一种动态SQL的语法,比较常见的是<if>条件判断,这类语句会结合实际传入的参数拼接完整的待执行的SQL 语句。

  • 在某种场景下我们会需要在 mybatis的拦截器中动态修改入参并期望生成新的SQL语句。

  • 一般场景下上述的情况应该在 Executor 的拦截插件中,但是事实上可以在ParameterHandler或StatementHandler 插件当中进行调整。

  • 建议通过实际实践可以发现真正的逻辑,如果能够能完整的调试会更加印象深刻。

例子

<insert id="addOneUser" parameterType="com.tuacy.mybatis.interceptor.entity.vo.UserInfoVo">
  INSERT INTO user
  <trim prefix="(" suffix=")" suffixOverrides=",">
    <if test="null != pkId">
      pkid,
    </if>
    <if test="null != userName">
      `name`,
    </if>
    <if test="null != password">
      password,
    </if>
    <if test="null != phone">
      phone
    </if>
  </trim>
  <trim prefix="VALUES (" suffix=")" suffixOverrides=",">
    <if test="null != pkId">
        #{pkId},
    </if>
    <if test="null != userName">
        #{userName},
    </if>
    <if test="null != password">
      #{password},
    </if>
    <if test="null != phone">
      #{phone},
    </if>
  </trim>
</insert>
  • 上述SQL当中我们通过修改入参对象的值来动态修改生成的INSERT语句,如修改 phone字段为 null,那么整个INSERT语句就不包含phone字段。

ParameterHandler拦截

image.png
  • mybatis真正执行的SQL对象是 BoundSql 对象,而 BoundSql 语句是通过MappedStatement来返回的。
  • MappedStatement.getBoundSql是通过sqlSource.getBoundSql 语句来生成 BoundSql对象。
  • 通过动态修改 SqlSource 对象 或 修改MappedStatement来达到目的
@Intercepts({
        @Signature(type = ParameterHandler.class, method = "setParameters", args = PreparedStatement.class)
})
public class ParamInterceptor implements Interceptor {

    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
    private static final ReflectorFactory REFLECTOR_FACTORY = new DefaultReflectorFactory();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {

        // 获取拦截器拦截的设置参数对象DefaultParameterHandler
        ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();

        // 通过mybatis的反射来获取对应的值
        MetaObject metaParameterHandler = MetaObject.forObject(parameterHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
        MappedStatement mappedStatement = (MappedStatement) metaParameterHandler.getValue("mappedStatement");
        Object parameterObject = metaParameterHandler.getValue("parameterObject");

        // id字段对应执行的SQL的方法的全路径,包含类名和方法名
        String id = mappedStatement.getId();
        String className = id.substring(0, id.lastIndexOf("."));
        String methodName = id.substring(id.lastIndexOf(".") + 1);

        // 动态加载类并获取类中的方法
        final Method[] methods = Class.forName(className).getMethods();

        // 遍历类的所有方法并找到此次调用的方法
        for (Method method : methods) {
            if (method.getName().equalsIgnoreCase(methodName)
                    && method.isAnnotationPresent(ParamAnnotation.class)) {

                // 获取方法上的注解以及注解对应的参数
                ParamAnnotation paramAnnotation = method.getAnnotation(ParamAnnotation.class);
                String srcKey = paramAnnotation.srcKey()[0];
                String destKey = paramAnnotation.destKey()[0];

                // 反射获取参数对象
                MetaObject param = MetaObject.forObject(parameterObject, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
                Object srcValue = param.getValue(srcKey);

                // 动态加工指定参数
                String destValue = String.valueOf(srcValue) + "fix";

                // 将修改后的动态参数添加到请求参数当中
                param.setValue(destKey, destValue);
                param.setValue(destKey, null);
                break;
            }
        }

        // 修改入参后动态生成的BoundSql对象
        BoundSql boundSql = mappedStatement.getBoundSql(parameterObject);
        String newSql = boundSql.getSql();

        // 生成新的BoundSql
        BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), newSql,
                boundSql.getParameterMappings(), boundSql.getParameterObject());

        // 1、通过生成新的MappedStatement对象
        MappedStatement newMappedStatement = newMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            String prop = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(prop)) {
                newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
            }
        }
        metaParameterHandler.setValue("mappedStatement", newMappedStatement);

        // 2、通过生成新的SqlSource对象
        MetaObject metaMappedStatement = MetaObject.forObject(mappedStatement, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, REFLECTOR_FACTORY);
        metaMappedStatement.setValue("sqlSource", new BoundSqlSqlSource(newBoundSql));

        // 回写parameterObject对象
        metaParameterHandler.setValue("parameterObject", parameterObject);
        return invocation.proceed();
    }


    class BoundSqlSqlSource implements SqlSource {
        private BoundSql boundSql;
        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

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

    }

    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) {
            StringBuilder keyProperties = new StringBuilder();
            for (String keyProperty : ms.getKeyProperties()) {
                keyProperties.append(keyProperty).append(",");
            }
            keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
            builder.keyProperty(keyProperties.toString());
        }

        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();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}
  • 1、通过BoundSql boundSql = mappedStatement.getBoundSql(parameterObject)来获取新的BoundSql对象,进而获取文本的SQL语句。
  • 2、通过BoundSql的构造函数构建新的BoundSql 对象。
  • 3、BoundSqlSqlSource继承SqlSource来生成 SqlSource 对象。
  • 4、通过newMappedStatement生成新的MappedStatement对象。
  • 步骤3和步骤4只要选择其中一个就可以了,需要实际实践一下。

参考

相关文章

  • Mybatis 插件兼容动态SQL

    系列 MyBatis拦截器原理介绍[https://www.jianshu.com/p/345fcbb47a7f]...

  • MyBatis动态SQL

    在讲MyBatis动态SQL之前,先教大家怎么通过MyBatis插件自动生成mapper对应的文件和对应的b...

  • MyBatis动态SQL

    MyBatis 动态SQL 内容 Mybatis动态SQL在XML中支持的几种标签: if chose trim、...

  • MyBatis核心知识点

    (1)Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不? Mybatis动...

  • MyBatis 动态SQL(*.xml)

    原文参考MyBatis 动态SQL MyBatis的动态SQL大大减少了拼接SQL语句时候的各种格式问题,这里摘录...

  • Mybatis动态SQL

    MyBatis Mybatis笔记连载上篇连接MyBatis缓存Mybatis笔记连载下篇连接 动态SQL 动态S...

  • MyBatis的动态SQL与日志Log4J、SQL语句构造器

    一、MyBatis动态SQL 动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似...

  • MyBatis学习:动态sql

    1.动态sql 动态sql是mybatis中的一个核心,什么是动态sql?动态sql即对sql语句进行灵活操作,通...

  • mybatis的xml文件的标签详解

    Mybatis #{}和${}和区别 mybatis获取方法参数 动态SQL

  • 第八章 动态SQL

    动态SQL中的元素介绍 动态SQL有什么作用 MyBatis提供了对SQL语句动态组装的功能 动态SQL中的元素 ...

网友评论

    本文标题:Mybatis 插件兼容动态SQL

    本文链接:https://www.haomeiwen.com/subject/eouraltx.html