第15篇:Mybatis中打印Sql信息

西魏陶渊明 ... 2022-5-8 Mybatis 大约 12 分钟

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();   
1
2
3
4
5
6
7

# 一、设计思路

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

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

第二个知识点,Mybatis中是可以使用很多标签的如 这些标签要先处理成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();
}
1
2
3
4
5

# 2.1.2 注解sql解析

private void loadMappedStatementByAnnotation() {
    MapperAnnotationBuilder mapperAnnotationBuilder =
        new MapperAnnotationBuilder(configuration, quickMapperChecker.mapper);
    mapperAnnotationBuilder.parse();
}
1
2
3
4
5

当执行完上面的代码,所有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;
    };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 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());
  }
1
2
3
4
5
6
7
8
9
10
11

这里我们要使用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);
1
2
3
4
5
6

# 2.2.2 bind参数生成

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

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

而他的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>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

以上参数分为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();
1
2
3
4
5
6
7
8
9
10

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

# 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;
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 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());
1
2
3
4
5
6
7
8

# 2.4 sql格式化

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

       <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.6</version>
        </dependency>
1
2
3
4
5

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

SQLUtils.formatMySql(boundSql.getSql());
1

# 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();   
1
2
3
4
5
6
7

# 三、完整代码

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

# 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;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273

# 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;
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287

本文由西魏陶渊明版权所有。如若转载,请注明出处:西魏陶渊明
上次编辑于: 2022年7月12日 20:38
贡献者: lxchinesszz