第15篇:Mybatis中打印Sql信息

本文涉及的产品
RDS MySQL DuckDB 分析主实例,基础系列 4核8GB
RDSClaw,2核4GB
RDS DuckDB + QuickBI 企业套餐,8核32GB + QuickBI 专业版
简介: 在Mybatis中如果我们要对我们的sql信息进行检查, 只能启动Spring容器, 去执行根据成功和失败来判断我们的逻辑是否有问题。此时会比较耗时,因为要启动容器。基于这个痛点, 本文要设计一个工具。使我们不依赖Spring容器,也不依赖任何外部插件,直接就把Sql信息的打印出来。
在Mybatis中如果我们要对我们的sql信息进行检查, 只能启动Spring容器, 去执行根据成功和失败来判断我们的逻辑是否有问题。
此时会比较耗时,因为要启动容器。基于这个痛点, 本文要设计一个工具。使我们不依赖Spring容器,也不依赖任何外部插件,直接就把
Sql信息的打印出来。

仓库地址: https://github.com/lxchinesszz/mybatis-sql-helper

使用方法

OrderBatchEntityQuery query = JMockData.mock(OrderBatchEntityQuery.class);
// 如果需要绑定xml就使用bindMapper
QuickMapperChecker.analyse(QuickMapperChecker.mock(IOrderMapper.class).list(query))
   .bindMapper("mapper/center/ReplenishOrderMapper.xml").printSql();   
// 如果完全依赖注解跟简单
QuickMapperChecker.analyse(QuickMapperChecker.mock(IOrderMapper.class).list(query))
   .printSql();   

一、设计思路

基于前面我们对Mybatis的学习,我们知道所有的sql信息,都会被解析成MappedStatement,并保存在 Configuration。
那么我们要做的

第一步就是解析sql信息成MappedStatement。而在Mybatis中的sql是可以写在Mapper.xml也可以使用注解形式,
直接写到接口类中的。

第二个知识点,Mybatis中是可以使用很多标签的如 <where/> <if/> <foreach/> <include/> 这些标签要先处理成sql信息。

第三步组装sql信息, 前面的学习我们知道sql信息如果是$变量符,那么会在直接会编译成sql信息。而动态sql是由DynamicSqlSource来直接解析参数
生成sql的。那么我们就需要将#占位符都调换成变量符,然后利用DynamicSqlSource给直接生成sql信息的。

第四步sql信息格式化。

第五步使用方法设计。

二、思路实现

2.1 MappedStatement解析

2.1.1 xml参数解析

private void loadMappedStatementByMapperFile(String mapperXmlFile) throws Exception {
    InputStream resourceAsStream = Resources.getResourceAsStream(mapperXmlFile);
    Map<String, XNode> sqlFragments = configuration.getSqlFragments();
    new XMLMapperBuilder(resourceAsStream, configuration, mapperXmlFile, sqlFragments).parse();
}

2.1.2 注解sql解析

private void loadMappedStatementByAnnotation() {
    MapperAnnotationBuilder mapperAnnotationBuilder =
        new MapperAnnotationBuilder(configuration, quickMapperChecker.mapper);
    mapperAnnotationBuilder.parse();
}

当执行完上面的代码,所有MappedStatement就生成了并保存到你指定的Configuration中了。

2.2 Sql中标签解析

2.2.1 Include 标签解析

拿到所有的sql执行标签"select|insert|update|delete",去执行include参数替换。
includeParser.applyIncludes(child.getNode());执行后 include 标签就替换成真正的sql片段了。

 private XNode findNode() throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream(this.mapperFile);
        XPathParser xPathParser = new XPathParser(resourceAsStream);
        XNode mapperNode = xPathParser.evalNode("/mapper");
        List<XNode> children = mapperNode.getChildren();
        for (XNode child : children) {
            if (child.getStringAttribute("id").equals(quickMapperChecker.methodName)) {
                MapperBuilderAssistant mapperBuilderAssistant =
                    new MapperBuilderAssistant(configuration, quickMapperChecker.mapperFile);
                mapperBuilderAssistant.setCurrentNamespace(mapper.getName());
                XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, mapperBuilderAssistant);
                includeParser.applyIncludes(child.getNode());
                return child;
            }
        }
        // "select|insert|update|delete"
        return null;
    };

2.2.1 其他标签解析

 private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
  }

这里我们要使用XMLScriptBuilder#parseDynamicTags。很可惜这个方法是受到保护的。
我们只能使用反射来对参数进行解析。

 // 解析xml中的标签信息
 Method parseDynamicTags = XMLScriptBuilder.class.getDeclaredMethod("parseDynamicTags", XNode.class);
 parseDynamicTags.setAccessible(true);

 XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(configuration, node);
 MixedSqlNode rootSqlNode = (MixedSqlNode)parseDynamicTags.invoke(xmlScriptBuilder, node);

2.2.2 bind参数生成

这里要说明下,我们举一个列子。以下面例子,我们拿到的参数是query。

List<OrderDO> list(@Param("query") OrderBatchEntityQuery query);

而他的xml比较复杂的。

<select id="list" resultType="com.center.dal.entity.OrderDO">
        select *
        from order as ro
                 left join order_detail rod on ro.id = rod.replenish_order_id
        <where>
            <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.ids)">
                and ro.id in
                <foreach collection="query.ids" open="(" separator="," index="index" item="id"
                         close=")">
                    #{id}
                </foreach>
            </if>
            <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.orderCode)">
                and ro.order_code = #{query.orderCode}
            </if>
            <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.statusList)">
                and ro.status in
                <foreach collection="query.statusList" open="(" separator="," index="index" item="status"
                         close=")">
                    #{status}
                </foreach>
            </if>
            <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.title)">
                and ro.title = #{query.title}
            </if>
            <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.salesWarehouseId)">
                and ro.sales_warehouse_id = #{query.salesWarehouseId}
            </if>
            <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.brandCode)">
                and ro.brand_code = #{query.brandCode}
            </if>
            <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.businessLineId)">
                and ro.business_line_id = #{query.businessLineId}
            </if>
            <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.signOwnerCode)">
                and ro.sign_owner_code = #{query.signOwnerCode}
            </if>
            <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.storageOwnerCode)">
                and ro.storage_owner_code = #{query.storageOwnerCode}
            </if>
            <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.goodsBarcodes)">
                and rod.goods_barcode in
                <foreach collection="query.goodsBarcodes" open="(" separator="," index="index" item="goods_barcode"
                         close=")">
                    #{goods_barcode}
                </foreach>
            </if>
        </where>
    </select>

以上参数分为2部分,一部分是原始方法参数的解析。

而BoundsSql中ParameterMapping是这样的。

需要拿到参数中每个的数据信息。

 // 解析xml中的标签信息
 Method parseDynamicTags = XMLScriptBuilder.class.getDeclaredMethod("parseDynamicTags", XNode.class);
 parseDynamicTags.setAccessible(true);

 XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(configuration, node);
 MixedSqlNode rootSqlNode = (MixedSqlNode)parseDynamicTags.invoke(xmlScriptBuilder, node);
 DynamicContext context = new DynamicContext(configuration, namedParams);
 rootSqlNode.apply(context);
 // 标签信息参数解析
 Map<String, Object> bindings = context.getBindings();

到这里复杂标签中的参数就获取到了。

2.3 占位符替换成变量符

2.3.1 占位符替换变量符

因为#占位符都会先调换成?。而参数都会按照顺序放在ParameterMapping中。

这里我们要写代码将?替换成${ParameterMapping#getProperty}。

    /**
     * 处理占位符已经被替换成?的时候,用于将占位符重新替换成变量符
     *
     * @param sql
     *            占位符sql
     * @param index
     *            占位符当前处理的索引
     * @param parameterMappings
     *            占位符参数信息
     * @return String 变量符sql
     */
    private String resetSql(String sql, int index, List<ParameterMapping> parameterMappings, MetaObject metaObject) {
        int i = sql.indexOf("?");
        if (i > -1) {
            ParameterMapping parameterMapping = parameterMappings.get(index);
            String property = parameterMapping.getProperty();
            Class<?> javaType = parameterMapping.getJavaType();
            Object value = metaObject.getValue(parameterMapping.getProperty());
            String s;
            if (javaType.equals(String.class) || value instanceof String) {
                s = sql.replaceFirst("\\?", "\"\\${" + property + "}\"");
            } else {
                s = sql.replaceFirst("\\?", "\\${" + property + "}");
            }
            sql = resetSql(s, ++index, parameterMappings, metaObject);
        }
        return sql;
    }

2.3.2 生成Sql

利用变量符能直接生成sql的能力,我们直接将参数准备好,使用就好了。

 // 获取原始参数信息
 Object namedParams = paramNameResolver.getNamedParams(quickMapperChecker.args);
 // 复杂参数解析
 Map<String, Object> bindings = context.getBindings();
 // 标签参数 + 原始参数
 ((Map)namedParams).putAll(bindings);
 TextSqlNode textSqlNode = new TextSqlNode(resetSql(sql, 0, parameterMappings, metaObject));
 new DynamicSqlSource(configuration, textSqlNode).getBoundSql(namedParams).getSql());

2.4 sql格式化

这里我们就直接使用druid库中的sql格式化工具

       <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>

这里因为我们知道是mysql数据库所以执行使用mysql格式化

SQLUtils.formatMySql(boundSql.getSql());

2.5 使用方法设计

基于上门的代码,首先我们已经不依赖Spring容器了,所以要想分析sql就不用启动整个项目了。
直接将要分析的类和方法进行执行就行了。

OrderBatchEntityQuery query = JMockData.mock(OrderBatchEntityQuery.class);
// 如果需要绑定xml就使用bindMapper
QuickMapperChecker.analyse(QuickMapperChecker.mock(IOrderMapper.class).list(query))
   .bindMapper("mapper/center/ReplenishOrderMapper.xml").printSql();   
// 如果完全依赖注解跟简单
QuickMapperChecker.analyse(QuickMapperChecker.mock(IOrderMapper.class).list(query))
   .printSql();   

三、完整代码

代码较为简单这里附带源码

3.1 Mybatis 使用

@NoArgsConstructor
public class QuickMapperChecker {

    /**
     * 方法签名id
     */
    @Getter
    public String mapperId;

    @Setter
    public String methodName;

    /**
     * 方法参数
     */
    @Getter
    private Object[] args;

    /**
     * 参数解析器
     */
    @Getter
    private ParamNameResolver paramNameResolver;

    /**
     * mapper类型
     */
    private Class<?> mapper;

    /**
     * mybatis配置
     */
    @Getter
    private Configuration configuration;

    @Getter
    @Setter
    private String mapperFile;

    private boolean simple;

    public QuickMapperChecker(String mapperId, Object[] args, ParamNameResolver paramNameResolver, Class<?> mapper,
                              Configuration configuration) {
        this.mapperId = mapperId;
        this.args = args;
        this.paramNameResolver = paramNameResolver;
        this.mapper = mapper;
        this.configuration = configuration;
    }

    public static QuickMapperChecker proxy() {
        if (Objects.isNull(quickMapperChecker)) {
            quickMapperChecker = new QuickMapperChecker();
            quickMapperChecker.simple = true;
        }
        return quickMapperChecker;
    }

    private static QuickMapperChecker quickMapperChecker;

    private static final Map<Class<?>, Object> PRIMITIVE_WRAPPER_TYPE_MAP = new IdentityHashMap<>(8);

    static {
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Boolean.class, false);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Byte.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Character.class, "");
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Double.class, 0D);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Float.class, 0L);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Integer.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Long.class, 0L);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Short.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Void.class, Void.TYPE);

        PRIMITIVE_WRAPPER_TYPE_MAP.put(boolean.class, false);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(byte.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(char.class, "");
        PRIMITIVE_WRAPPER_TYPE_MAP.put(double.class, 0D);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(float.class, 0L);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(int.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(long.class, 0L);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(short.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(void.class, null);
    }

    private static Class<?>[] interfacesFromMapper(Class<?> mapper) {
        Class<?>[] interfaces = mapper.getInterfaces();
        List<Class<?>> interfacesClass = new ArrayList<>();
        if (interfaces.length > 0) {
            interfacesClass.addAll(Arrays.asList(interfaces));
        }
        if (mapper.isInterface()) {
            interfacesClass.add(mapper);
        }
        return interfacesClass.toArray(new Class[]{});
    }

    public static <T> T mock(Class<T> mapper) throws Exception {
        return mock(mapper, new Configuration());
    }

    @SuppressWarnings("unchecked")
    public static <T> T mock(Class<T> mapper, Configuration configuration) throws Exception {
        return (T) Proxy.newProxyInstance(mapper.getClassLoader(), interfacesFromMapper(mapper),
                (proxy, method, args) -> {
                    String mapperId = method.getDeclaringClass().getName() + "." + method.getName();
                    if (Objects.isNull(quickMapperChecker)) {
                        quickMapperChecker = new QuickMapperChecker(mapperId, args,
                                new ParamNameResolver(configuration, method), mapper, configuration);
                        quickMapperChecker.setMethodName(method.getName());
                    } else {
                        boolean simple = quickMapperChecker.simple;
                        quickMapperChecker = new QuickMapperChecker(mapperId, args,
                                new ParamNameResolver(configuration, method), mapper, configuration);
                        quickMapperChecker.simple = simple;
                        quickMapperChecker.setMethodName(method.getName());
                    }
                    Class<?> returnType = method.getReturnType();
                    Object result = PRIMITIVE_WRAPPER_TYPE_MAP.get(returnType);
                    if (quickMapperChecker.simple) {
                        quickMapperChecker.printSql();
                    }
                    return Objects.nonNull(result) ? result : new DefaultObjectFactory().create(returnType);
                });
    }

    /**
     * 处理占位符已经被替换成?的时候,用于将占位符重新替换成变量符
     *
     * @param sql               占位符sql
     * @param index             占位符当前处理的索引
     * @param parameterMappings 占位符参数信息
     * @return String 变量符sql
     */
    private String resetSql(String sql, int index, List<ParameterMapping> parameterMappings, MetaObject metaObject) {
        int i = sql.indexOf("?");
        if (i > -1) {
            ParameterMapping parameterMapping = parameterMappings.get(index);
            String property = parameterMapping.getProperty();
            Class<?> javaType = parameterMapping.getJavaType();
            Object value = metaObject.getValue(parameterMapping.getProperty());
            String s;
            if (javaType.equals(String.class) || value instanceof String) {
                s = sql.replaceFirst("\\?", "\"\\${" + property + "}\"");
            } else {
                s = sql.replaceFirst("\\?", "\\${" + property + "}");
            }
            sql = resetSql(s, ++index, parameterMappings, metaObject);
        }
        return sql;
    }

    /**
     * sql打印
     *
     * @return String
     * @throws Exception 未知异常
     */
    public String getSql() throws Exception {
        if (!StringUtils.isBlank(this.mapperFile)) {
            loadMappedStatementByMapperFile(this.mapperFile);
        }
        loadMappedStatementByAnnotation();
        boolean hasMapped = configuration.hasStatement(quickMapperChecker.mapperId);
        if (!hasMapped) {
            throw new RuntimeException(
                    "未找到MappedStatement,请检查是否需要绑定mapper xml文件:[" + quickMapperChecker.mapperId + "]");
        }
        MappedStatement mappedStatement = configuration.getMappedStatement(quickMapperChecker.mapperId);
        SqlSource sqlSource = mappedStatement.getSqlSource();
        Object namedParams = paramNameResolver.getNamedParams(quickMapperChecker.args);
        BoundSql boundSql = mappedStatement.getBoundSql(namedParams);
        // 占位符
        if (sqlSource instanceof RawSqlSource || sqlSource instanceof DynamicSqlSource) {
            // 占位sql,将#替换成$
            String sql = boundSql.getSql();
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
            XNode node = findNode();
            if (Objects.nonNull(node)) {
                // 解析xml中的标签信息
                Method parseDynamicTags = XMLScriptBuilder.class.getDeclaredMethod("parseDynamicTags", XNode.class);
                parseDynamicTags.setAccessible(true);

                XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(configuration, node);
                MixedSqlNode rootSqlNode = (MixedSqlNode) parseDynamicTags.invoke(xmlScriptBuilder, node);
                DynamicContext context = new DynamicContext(configuration, namedParams);
                rootSqlNode.apply(context);
                // 标签信息参数解析
                Map<String, Object> bindings = context.getBindings();
                // 标签参数 + 原始参数
                ((Map) namedParams).putAll(bindings);
            }
            MetaObject metaObject = configuration.newMetaObject(namedParams);
            processDate(parameterMappings, metaObject);
            TextSqlNode textSqlNode = new TextSqlNode(resetSql(sql, 0, parameterMappings, metaObject));
            return SQLUtils
                    .formatMySql((new DynamicSqlSource(configuration, textSqlNode).getBoundSql(namedParams).getSql()));
        } else {
            return SQLUtils.formatMySql(boundSql.getSql());
        }
    }

    private void processDate(List<ParameterMapping> parameterMappings, MetaObject metaObject) {
        for (ParameterMapping parameterMapping : parameterMappings) {
            String property = parameterMapping.getProperty();
            Object value = metaObject.getValue(property);
            if (value instanceof Date) {
                metaObject.setValue(property, DatePatternEnum.DATE_TIME_PATTERN.format((Date) value));
            }
        }
    }

    private XNode findNode() throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream(this.mapperFile);
        XPathParser xPathParser = new XPathParser(resourceAsStream);
        XNode mapperNode = xPathParser.evalNode("/mapper");
        List<XNode> children = mapperNode.getChildren();
        for (XNode child : children) {
            if (child.getStringAttribute("id").equals(quickMapperChecker.methodName)) {
                MapperBuilderAssistant mapperBuilderAssistant =
                        new MapperBuilderAssistant(configuration, quickMapperChecker.mapperFile);
                mapperBuilderAssistant.setCurrentNamespace(mapper.getName());
                XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, mapperBuilderAssistant);
                includeParser.applyIncludes(child.getNode());
                return child;
            }
        }
        // "select|insert|update|delete"
        return null;
    }

    ;

    private void loadMappedStatementByAnnotation() {
        MapperAnnotationBuilder mapperAnnotationBuilder =
                new MapperAnnotationBuilder(configuration, quickMapperChecker.mapper);
        mapperAnnotationBuilder.parse();
    }

    private void loadMappedStatementByMapperFile(String mapperXmlFile) throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream(mapperXmlFile);
        Map<String, XNode> sqlFragments = configuration.getSqlFragments();
        new XMLMapperBuilder(resourceAsStream, configuration, mapperXmlFile, sqlFragments).parse();
    }

    public void printSql() throws Exception {
        ColorConsole.colorPrintln("🚀 格式化SQL:");
        ColorConsole.colorPrintln(AnsiColor.BRIGHT_MAGENTA, "{}", getSql());
    }

    /**
     * sql信息进行检查
     *
     * @param t   泛型
     * @param <T> 泛型
     * @return QuickMapperChecker
     */
    public static <T> QuickMapperChecker analyse(T t) {
        // 1. 调用方法
        return quickMapperChecker;
    }

    /**
     * 绑定mapper文件
     *
     * @param mapperFile mapper文件地址
     * @return QuickMapperChecker
     */
    public QuickMapperChecker bindMapper(String mapperFile) {
        quickMapperChecker.setMapperFile(mapperFile);
        return quickMapperChecker;
    }
}

3.2 Mybatis Plus 使用

/**
 * 无需启动容器对sql信息进行检查
 *
 * @author liuxin 2022/4/27 17:48
 */
@NoArgsConstructor
public class QuickMapperPlusChecker {

    /**
     * 方法签名id
     */
    @Getter
    public String mapperId;

    @Setter
    public String methodName;

    /**
     * 方法参数
     */
    @Getter
    private Object[] args;

    /**
     * 参数解析器
     */
    @Getter
    private ParamNameResolver paramNameResolver;

    /**
     * mapper类型
     */
    private Class<?> mapper;

    /**
     * mybatis配置
     */
    @Getter
    private MybatisConfiguration configuration;

    @Getter
    @Setter
    private String mapperFile;

    private boolean simple;

    public QuickMapperPlusChecker(String mapperId, Object[] args, ParamNameResolver paramNameResolver, Class<?> mapper,
        MybatisConfiguration configuration) {
        this.mapperId = mapperId;
        this.args = args;
        this.paramNameResolver = paramNameResolver;
        this.mapper = mapper;
        this.configuration = configuration;
    }

    public static QuickMapperPlusChecker proxy() {
        if (Objects.isNull(quickMapperChecker)) {
            quickMapperChecker = new QuickMapperPlusChecker();
            quickMapperChecker.simple = true;
        }
        return quickMapperChecker;
    }

    private static QuickMapperPlusChecker quickMapperChecker;

    private static final Map<Class<?>, Object> PRIMITIVE_WRAPPER_TYPE_MAP = new IdentityHashMap<>(8);

    static {
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Boolean.class, false);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Byte.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Character.class, "");
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Double.class, 0D);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Float.class, 0L);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Integer.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Long.class, 0L);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Short.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(Void.class, Void.TYPE);

        PRIMITIVE_WRAPPER_TYPE_MAP.put(boolean.class, false);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(byte.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(char.class, "");
        PRIMITIVE_WRAPPER_TYPE_MAP.put(double.class, 0D);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(float.class, 0L);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(int.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(long.class, 0L);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(short.class, 0);
        PRIMITIVE_WRAPPER_TYPE_MAP.put(void.class, null);
    }

    private static Class<?>[] interfacesFromMapper(Class<?> mapper) {
        Class<?>[] interfaces = mapper.getInterfaces();
        List<Class<?>> interfacesClass = new ArrayList<>();
        if (interfaces.length > 0) {
            interfacesClass.addAll(Arrays.asList(interfaces));
        }
        if (mapper.isInterface()) {
            interfacesClass.add(mapper);
        }
        return interfacesClass.toArray(new Class[] {});
    }

    public static <T> T mock(Class<T> mapper) throws Exception {
        return mock(mapper, new MybatisConfiguration());
    }

    @SuppressWarnings("unchecked")
    public static <T> T mock(Class<T> mapper, MybatisConfiguration configuration) throws Exception {
        return (T)Proxy.newProxyInstance(mapper.getClassLoader(), interfacesFromMapper(mapper),
            (proxy, method, args) -> {
                String mapperId = mapper.getName() + "." + method.getName();
                if (Objects.isNull(quickMapperChecker)) {
                    quickMapperChecker = new QuickMapperPlusChecker(mapperId, args,
                        new ParamNameResolver(configuration, method), mapper, configuration);
                    quickMapperChecker.setMethodName(method.getName());
                } else {
                    boolean simple = quickMapperChecker.simple;
                    quickMapperChecker = new QuickMapperPlusChecker(mapperId, args,
                        new ParamNameResolver(configuration, method), mapper, configuration);
                    quickMapperChecker.simple = simple;
                    quickMapperChecker.setMethodName(method.getName());
                }
                Class<?> returnType = method.getReturnType();
                Object result = PRIMITIVE_WRAPPER_TYPE_MAP.get(returnType);
                if (quickMapperChecker.simple) {
                    quickMapperChecker.printSql();
                }
                return Objects.nonNull(result) ? result : new DefaultObjectFactory().create(returnType);
            });
    }

    /**
     * 处理占位符已经被替换成?的时候,用于将占位符重新替换成变量符
     *
     * @param sql
     *            占位符sql
     * @param index
     *            占位符当前处理的索引
     * @param parameterMappings
     *            占位符参数信息
     * @return String 变量符sql
     */
    private String resetSql(String sql, int index, List<ParameterMapping> parameterMappings, MetaObject metaObject) {
        int i = sql.indexOf("?");
        if (i > -1) {
            ParameterMapping parameterMapping = parameterMappings.get(index);
            String property = parameterMapping.getProperty();
            Class<?> javaType = parameterMapping.getJavaType();
            Object value = metaObject.getValue(parameterMapping.getProperty());
            String s;
            if (javaType.equals(String.class) || value instanceof String) {
                s = sql.replaceFirst("\\?", "\"\\${" + property + "}\"");
            } else {
                s = sql.replaceFirst("\\?", "\\${" + property + "}");
            }
            sql = resetSql(s, ++index, parameterMappings, metaObject);
        }
        return sql;
    }

    /**
     * sql打印
     * 
     * @return String
     * @throws Exception
     *             未知异常
     */
    public String getSql() throws Exception {
        if (!StringUtils.isBlank(this.mapperFile)) {
            loadMappedStatementByMapperFile(this.mapperFile);
        }
        loadMappedStatementByAnnotation();
        new SqlRunnerInjector().inject(configuration);
        boolean hasMapped = configuration.hasStatement(quickMapperChecker.mapperId);
        if (!hasMapped) {
            throw new RuntimeException(
                "未找到MappedStatement,请检查是否需要绑定mapper xml文件:[" + quickMapperChecker.mapperId + "]");
        }
        MappedStatement mappedStatement = configuration.getMappedStatement(quickMapperChecker.mapperId);
        SqlSource sqlSource = mappedStatement.getSqlSource();
        Object namedParams = paramNameResolver.getNamedParams(quickMapperChecker.args);
        BoundSql boundSql = mappedStatement.getBoundSql(namedParams);
        // 占位符
        if (sqlSource instanceof RawSqlSource || sqlSource instanceof DynamicSqlSource) {
            // 占位sql,将#替换成$
            String sql = boundSql.getSql();
            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

            XNode node = findNode();
            if (Objects.nonNull(node)) {
                XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(configuration, node);
                // 解析xml中的标签信息
                Method parseDynamicTags = XMLScriptBuilder.class.getDeclaredMethod("parseDynamicTags", XNode.class);
                parseDynamicTags.setAccessible(true);
                MixedSqlNode rootSqlNode = (MixedSqlNode)parseDynamicTags.invoke(xmlScriptBuilder, node);
                DynamicContext context = new DynamicContext(configuration, namedParams);
                rootSqlNode.apply(context);
                // 标签信息参数解析
                Map<String, Object> bindings = context.getBindings();
                // 标签参数 + 原始参数
                ((Map)namedParams).putAll(bindings);
            }
            MetaObject metaObject = configuration.newMetaObject(namedParams);
            processDate(parameterMappings, metaObject);
            TextSqlNode textSqlNode = new TextSqlNode(resetSql(sql, 0, parameterMappings, metaObject));
            return SQLUtils
                .formatMySql((new DynamicSqlSource(configuration, textSqlNode).getBoundSql(namedParams).getSql()));
        } else {
            return SQLUtils.formatMySql(boundSql.getSql());
        }
    }

    private void processDate(List<ParameterMapping> parameterMappings, MetaObject metaObject) {
        for (ParameterMapping parameterMapping : parameterMappings) {
            String property = parameterMapping.getProperty();
            Object value = metaObject.getValue(property);
            if (value instanceof Date) {
                metaObject.setValue(property, DatePatternEnum.DATE_TIME_PATTERN.format((Date)value));
            }
        }
    }

    private XNode findNode() throws Exception {
        if (StringUtils.isNotBlank(this.mapperFile)) {
            InputStream resourceAsStream = Resources.getResourceAsStream(this.mapperFile);
            XPathParser xPathParser = new XPathParser(resourceAsStream);
            XNode mapperNode = xPathParser.evalNode("/mapper");
            List<XNode> children = mapperNode.getChildren();
            for (XNode child : children) {
                if (child.getStringAttribute("id").equals(quickMapperChecker.methodName)) {
                    MapperBuilderAssistant mapperBuilderAssistant =
                        new MapperBuilderAssistant(configuration, quickMapperChecker.mapperFile);
                    mapperBuilderAssistant.setCurrentNamespace(mapper.getName());
                    XMLIncludeTransformer includeParser =
                        new XMLIncludeTransformer(configuration, mapperBuilderAssistant);
                    includeParser.applyIncludes(child.getNode());
                    return child;
                }
            }
        }
        // "select|insert|update|delete"
        return null;
    };

    private void loadMappedStatementByAnnotation() {
        MybatisMapperAnnotationBuilder mapperAnnotationBuilder =
            new MybatisMapperAnnotationBuilder(configuration, quickMapperChecker.mapper);
        mapperAnnotationBuilder.parse();
    }

    private void loadMappedStatementByMapperFile(String mapperXmlFile) throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream(mapperXmlFile);
        Map<String, XNode> sqlFragments = configuration.getSqlFragments();
        new XMLMapperBuilder(resourceAsStream, configuration, mapperXmlFile, sqlFragments).parse();
    }

    public void printSql() throws Exception {
        ColorConsole.colorPrintln("🚀 格式化SQL:");
        ColorConsole.colorPrintln(AnsiColor.BRIGHT_MAGENTA, "{}", getSql());
    }

    /**
     * sql信息进行检查
     * 
     * @param t
     *            泛型
     * @return QuickMapperChecker
     * @param <T>
     *            泛型
     */
    public static <T> QuickMapperPlusChecker analyse(T t) {
        // 1. 调用方法
        return quickMapperChecker;
    }

    /**
     * 绑定mapper文件
     * 
     * @param mapperFile
     *            mapper文件地址
     * @return QuickMapperChecker
     */
    public QuickMapperPlusChecker bindMapper(String mapperFile) {
        quickMapperChecker.setMapperFile(mapperFile);
        return quickMapperChecker;
    }
}
相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
SQL Java 数据库连接
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
【YashanDB知识库】解决mybatis的mapper文件sql语句结尾加分号";"报错
|
8月前
|
SQL XML Java
通过MyBatis的XML配置实现灵活的动态SQL查询
总结而言,通过MyBatis的XML配置实现灵活的动态SQL查询,可以让开发者以声明式的方式构建SQL语句,既保证了SQL操作的灵活性,又简化了代码的复杂度。这种方式可以显著提高数据库操作的效率和代码的可维护性。
494 18
|
SQL Java 数据库连接
MyBatis动态SQL字符串空值判断,这个细节99%的程序员都踩过坑!
本文深入探讨了MyBatis动态SQL中字符串参数判空的常见问题。通过具体案例分析,对比了`name != null and name != &#39;&#39;`与`name != null and name != &#39; &#39;`两种写法的差异,指出后者可能引发逻辑混乱。为避免此类问题,建议在后端对参数进行预处理(如trim去空格),简化MyBatis判断逻辑,提升代码健壮性与可维护性。细节决定成败,严谨处理参数判空是写出高质量代码的关键。
1605 0
|
7月前
|
SQL Web App开发 安全
SQL Server 2025 年 9 月更新 - 修复 CVE-2025-47997 SQL Server 信息泄露漏洞
SQL Server 2025 年 9 月更新 - 修复 CVE-2025-47997 SQL Server 信息泄露漏洞
440 0
SQL Server 2025 年 9 月更新 - 修复 CVE-2025-47997 SQL Server 信息泄露漏洞
|
9月前
|
SQL Web App开发 安全
SQL Server 2025年7月更新 - 修复 CVE-2025-49718 Microsoft SQL Server 信息泄露漏洞
SQL Server 2025年7月更新 - 修复 CVE-2025-49718 Microsoft SQL Server 信息泄露漏洞
794 0
SQL Server 2025年7月更新 - 修复 CVE-2025-49718 Microsoft SQL Server 信息泄露漏洞
|
8月前
|
SQL Java 数据库连接
SSM相关问题-1--#{}和${}有什么区别吗?--Mybatis都有哪些动态sql?能简述一下动 态sql的执行原理吗?--Spring支持的几种bean的作用域 Scope
在MyBatis中,`#{}`是预处理占位符,可防止SQL注入,适用于大多数参数传递场景;而`${}`是直接字符串替换,不安全,仅用于动态表名、列名等特殊场景。二者在安全性、性能及使用场景上有显著区别。
332 0
|
11月前
|
SQL XML Java
菜鸟之路Day35一一Mybatis之XML映射与动态SQL
本文介绍了MyBatis框架中XML映射与动态SQL的使用方法,作者通过实例详细解析了XML映射文件的配置规范,包括namespace、id和resultType的设置。文章还对比了注解与XML映射的优缺点,强调复杂SQL更适合XML方式。在动态SQL部分,重点讲解了`&lt;if&gt;`、`&lt;where&gt;`、`&lt;set&gt;`、`&lt;foreach&gt;`等标签的应用场景,如条件查询、动态更新和批量删除,并通过代码示例展示了其灵活性与实用性。最后,通过`&lt;sql&gt;`和`&lt;include&gt;`实现代码复用,优化维护效率。
1074 5
|
SQL Java 数据库连接
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
【YashanDB 知识库】解决 mybatis 的 mapper 文件 sql 语句结尾加分号";"报错
|
SQL 缓存 Java
框架源码私享笔记(02)Mybatis核心框架原理 | 一条SQL透析核心组件功能特性
本文详细解构了MyBatis的工作机制,包括解析配置、创建连接、执行SQL、结果封装和关闭连接等步骤。文章还介绍了MyBatis的五大核心功能特性:支持动态SQL、缓存机制(一级和二级缓存)、插件扩展、延迟加载和SQL注解,帮助读者深入了解其高效灵活的设计理念。
|
SQL XML Java
九、MyBatis动态SQL
九、MyBatis动态SQL
231 2