第15篇:Mybatis中打印Sql信息
!!! tip Sql打印需求 在Mybatis中如果我们要对我们的sql信息进行检查, 只能启动Spring容器, 去执行根据成功和失败来判断我们的逻辑是否有问题。 此时会比较耗时,因为要启动容器。基于这个痛点, 本文要设计一个工具。使我们不依赖Spring容器,也不依赖任何外部插件,直接就把 Sql信息的打印出来。 !!!
仓库地址: https://github.com/lxchinesszz/mybatis-sql-helper
使用方法
1OrderBatchEntityQuery query = JMockData.mock(OrderBatchEntityQuery.class);
2// 如果需要绑定xml就使用bindMapper
3QuickMapperChecker.analyse(QuickMapperChecker.mock(IOrderMapper.class).list(query))
4 .bindMapper("mapper/center/ReplenishOrderMapper.xml").printSql();
5// 如果完全依赖注解跟简单
6QuickMapperChecker.analyse(QuickMapperChecker.mock(IOrderMapper.class).list(query))
7 .printSql();一、设计思路

基于前面我们对Mybatis的学习,我们知道所有的sql信息,都会被解析成MappedStatement,并保存在 Configuration。 那么我们要做的
第一步就是解析sql信息成MappedStatement。而在Mybatis中的sql是可以写在Mapper.xml也可以使用注解形式, 直接写到接口类中的。
第二个知识点,Mybatis中是可以使用很多标签的如
第三步组装sql信息, 前面的学习我们知道sql信息如果是$变量符,那么会在直接会编译成sql信息。而动态sql是由DynamicSqlSource来直接解析参数 生成sql的。那么我们就需要将#占位符都调换成变量符,然后利用DynamicSqlSource给直接生成sql信息的。
第四步sql信息格式化。
第五步使用方法设计。
二、思路实现
2.1 MappedStatement解析
2.1.1 xml参数解析
1private void loadMappedStatementByMapperFile(String mapperXmlFile) throws Exception {
2 InputStream resourceAsStream = Resources.getResourceAsStream(mapperXmlFile);
3 Map<String, XNode> sqlFragments = configuration.getSqlFragments();
4 new XMLMapperBuilder(resourceAsStream, configuration, mapperXmlFile, sqlFragments).parse();
5}2.1.2 注解sql解析
1private void loadMappedStatementByAnnotation() {
2 MapperAnnotationBuilder mapperAnnotationBuilder =
3 new MapperAnnotationBuilder(configuration, quickMapperChecker.mapper);
4 mapperAnnotationBuilder.parse();
5}当执行完上面的代码,所有MappedStatement就生成了并保存到你指定的Configuration中了。
2.2 Sql中标签解析
2.2.1 Include 标签解析
拿到所有的sql执行标签"select|insert|update|delete",去执行include参数替换。 includeParser.applyIncludes(child.getNode());执行后 include 标签就替换成真正的sql片段了。
1 private XNode findNode() throws Exception {
2 InputStream resourceAsStream = Resources.getResourceAsStream(this.mapperFile);
3 XPathParser xPathParser = new XPathParser(resourceAsStream);
4 XNode mapperNode = xPathParser.evalNode("/mapper");
5 List<XNode> children = mapperNode.getChildren();
6 for (XNode child : children) {
7 if (child.getStringAttribute("id").equals(quickMapperChecker.methodName)) {
8 MapperBuilderAssistant mapperBuilderAssistant =
9 new MapperBuilderAssistant(configuration, quickMapperChecker.mapperFile);
10 mapperBuilderAssistant.setCurrentNamespace(mapper.getName());
11 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, mapperBuilderAssistant);
12 includeParser.applyIncludes(child.getNode());
13 return child;
14 }
15 }
16 // "select|insert|update|delete"
17 return null;
18 };2.2.1 其他标签解析
1 private void initNodeHandlerMap() {
2 nodeHandlerMap.put("trim", new TrimHandler());
3 nodeHandlerMap.put("where", new WhereHandler());
4 nodeHandlerMap.put("set", new SetHandler());
5 nodeHandlerMap.put("foreach", new ForEachHandler());
6 nodeHandlerMap.put("if", new IfHandler());
7 nodeHandlerMap.put("choose", new ChooseHandler());
8 nodeHandlerMap.put("when", new IfHandler());
9 nodeHandlerMap.put("otherwise", new OtherwiseHandler());
10 nodeHandlerMap.put("bind", new BindHandler());
11 }这里我们要使用XMLScriptBuilder#parseDynamicTags。很可惜这个方法是受到保护的。 我们只能使用反射来对参数进行解析。
1 // 解析xml中的标签信息
2 Method parseDynamicTags = XMLScriptBuilder.class.getDeclaredMethod("parseDynamicTags", XNode.class);
3 parseDynamicTags.setAccessible(true);
4
5 XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(configuration, node);
6 MixedSqlNode rootSqlNode = (MixedSqlNode)parseDynamicTags.invoke(xmlScriptBuilder, node);2.2.2 bind参数生成
这里要说明下,我们举一个列子。以下面例子,我们拿到的参数是query。
1List<OrderDO> list(@Param("query") OrderBatchEntityQuery query);而他的xml比较复杂的。
1<select id="list" resultType="com.center.dal.entity.OrderDO">
2 select *
3 from order as ro
4 left join order_detail rod on ro.id = rod.replenish_order_id
5 <where>
6 <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.ids)">
7 and ro.id in
8 <foreach collection="query.ids" open="(" separator="," index="index" item="id"
9 close=")">
10 #{id}
11 </foreach>
12 </if>
13 <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.orderCode)">
14 and ro.order_code = #{query.orderCode}
15 </if>
16 <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.statusList)">
17 and ro.status in
18 <foreach collection="query.statusList" open="(" separator="," index="index" item="status"
19 close=")">
20 #{status}
21 </foreach>
22 </if>
23 <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.title)">
24 and ro.title = #{query.title}
25 </if>
26 <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.salesWarehouseId)">
27 and ro.sales_warehouse_id = #{query.salesWarehouseId}
28 </if>
29 <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.brandCode)">
30 and ro.brand_code = #{query.brandCode}
31 </if>
32 <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.businessLineId)">
33 and ro.business_line_id = #{query.businessLineId}
34 </if>
35 <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.signOwnerCode)">
36 and ro.sign_owner_code = #{query.signOwnerCode}
37 </if>
38 <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.storageOwnerCode)">
39 and ro.storage_owner_code = #{query.storageOwnerCode}
40 </if>
41 <if test="@com.center.dal.util.MybatisIfUtils@isNotEmpty(query.goodsBarcodes)">
42 and rod.goods_barcode in
43 <foreach collection="query.goodsBarcodes" open="(" separator="," index="index" item="goods_barcode"
44 close=")">
45 #{goods_barcode}
46 </foreach>
47 </if>
48 </where>
49 </select>
50以上参数分为2部分,一部分是原始方法参数的解析。

而BoundsSql中ParameterMapping是这样的。

需要拿到参数中每个的数据信息。
1 // 解析xml中的标签信息
2 Method parseDynamicTags = XMLScriptBuilder.class.getDeclaredMethod("parseDynamicTags", XNode.class);
3 parseDynamicTags.setAccessible(true);
4
5 XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(configuration, node);
6 MixedSqlNode rootSqlNode = (MixedSqlNode)parseDynamicTags.invoke(xmlScriptBuilder, node);
7 DynamicContext context = new DynamicContext(configuration, namedParams);
8 rootSqlNode.apply(context);
9 // 标签信息参数解析
10 Map<String, Object> bindings = context.getBindings();
到这里复杂标签中的参数就获取到了。
2.3 占位符替换成变量符
2.3.1 占位符替换变量符
因为#占位符都会先调换成?。而参数都会按照顺序放在ParameterMapping中。

这里我们要写代码将?替换成${ParameterMapping#getProperty}。
1 /**
2 * 处理占位符已经被替换成?的时候,用于将占位符重新替换成变量符
3 *
4 * @param sql
5 * 占位符sql
6 * @param index
7 * 占位符当前处理的索引
8 * @param parameterMappings
9 * 占位符参数信息
10 * @return String 变量符sql
11 */
12 private String resetSql(String sql, int index, List<ParameterMapping> parameterMappings, MetaObject metaObject) {
13 int i = sql.indexOf("?");
14 if (i > -1) {
15 ParameterMapping parameterMapping = parameterMappings.get(index);
16 String property = parameterMapping.getProperty();
17 Class<?> javaType = parameterMapping.getJavaType();
18 Object value = metaObject.getValue(parameterMapping.getProperty());
19 String s;
20 if (javaType.equals(String.class) || value instanceof String) {
21 s = sql.replaceFirst("?", ""${" + property + "}"");
22 } else {
23 s = sql.replaceFirst("?", "${" + property + "}");
24 }
25 sql = resetSql(s, ++index, parameterMappings, metaObject);
26 }
27 return sql;
28 }2.3.2 生成Sql
利用变量符能直接生成sql的能力,我们直接将参数准备好,使用就好了。
1 // 获取原始参数信息
2 Object namedParams = paramNameResolver.getNamedParams(quickMapperChecker.args);
3 // 复杂参数解析
4 Map<String, Object> bindings = context.getBindings();
5 // 标签参数 + 原始参数
6 ((Map)namedParams).putAll(bindings);
7 TextSqlNode textSqlNode = new TextSqlNode(resetSql(sql, 0, parameterMappings, metaObject));
8 new DynamicSqlSource(configuration, textSqlNode).getBoundSql(namedParams).getSql());2.4 sql格式化
这里我们就直接使用druid库中的sql格式化工具
1 <dependency>
2 <groupId>com.alibaba</groupId>
3 <artifactId>druid</artifactId>
4 <version>1.2.6</version>
5 </dependency>这里因为我们知道是mysql数据库所以执行使用mysql格式化
1SQLUtils.formatMySql(boundSql.getSql());2.5 使用方法设计
基于上门的代码,首先我们已经不依赖Spring容器了,所以要想分析sql就不用启动整个项目了。 直接将要分析的类和方法进行执行就行了。
1OrderBatchEntityQuery query = JMockData.mock(OrderBatchEntityQuery.class);
2// 如果需要绑定xml就使用bindMapper
3QuickMapperChecker.analyse(QuickMapperChecker.mock(IOrderMapper.class).list(query))
4 .bindMapper("mapper/center/ReplenishOrderMapper.xml").printSql();
5// 如果完全依赖注解跟简单
6QuickMapperChecker.analyse(QuickMapperChecker.mock(IOrderMapper.class).list(query))
7 .printSql();三、完整代码
代码较为简单这里附带源码
3.1 Mybatis 使用
1@NoArgsConstructor
2public class QuickMapperChecker {
3
4 /**
5 * 方法签名id
6 */
7 @Getter
8 public String mapperId;
9
10 @Setter
11 public String methodName;
12
13 /**
14 * 方法参数
15 */
16 @Getter
17 private Object[] args;
18
19 /**
20 * 参数解析器
21 */
22 @Getter
23 private ParamNameResolver paramNameResolver;
24
25 /**
26 * mapper类型
27 */
28 private Class<?> mapper;
29
30 /**
31 * mybatis配置
32 */
33 @Getter
34 private Configuration configuration;
35
36 @Getter
37 @Setter
38 private String mapperFile;
39
40 private boolean simple;
41
42 public QuickMapperChecker(String mapperId, Object[] args, ParamNameResolver paramNameResolver, Class<?> mapper,
43 Configuration configuration) {
44 this.mapperId = mapperId;
45 this.args = args;
46 this.paramNameResolver = paramNameResolver;
47 this.mapper = mapper;
48 this.configuration = configuration;
49 }
50
51 public static QuickMapperChecker proxy() {
52 if (Objects.isNull(quickMapperChecker)) {
53 quickMapperChecker = new QuickMapperChecker();
54 quickMapperChecker.simple = true;
55 }
56 return quickMapperChecker;
57 }
58
59 private static QuickMapperChecker quickMapperChecker;
60
61 private static final Map<Class<?>, Object> PRIMITIVE_WRAPPER_TYPE_MAP = new IdentityHashMap<>(8);
62
63 static {
64 PRIMITIVE_WRAPPER_TYPE_MAP.put(Boolean.class, false);
65 PRIMITIVE_WRAPPER_TYPE_MAP.put(Byte.class, 0);
66 PRIMITIVE_WRAPPER_TYPE_MAP.put(Character.class, "");
67 PRIMITIVE_WRAPPER_TYPE_MAP.put(Double.class, 0D);
68 PRIMITIVE_WRAPPER_TYPE_MAP.put(Float.class, 0L);
69 PRIMITIVE_WRAPPER_TYPE_MAP.put(Integer.class, 0);
70 PRIMITIVE_WRAPPER_TYPE_MAP.put(Long.class, 0L);
71 PRIMITIVE_WRAPPER_TYPE_MAP.put(Short.class, 0);
72 PRIMITIVE_WRAPPER_TYPE_MAP.put(Void.class, Void.TYPE);
73
74 PRIMITIVE_WRAPPER_TYPE_MAP.put(boolean.class, false);
75 PRIMITIVE_WRAPPER_TYPE_MAP.put(byte.class, 0);
76 PRIMITIVE_WRAPPER_TYPE_MAP.put(char.class, "");
77 PRIMITIVE_WRAPPER_TYPE_MAP.put(double.class, 0D);
78 PRIMITIVE_WRAPPER_TYPE_MAP.put(float.class, 0L);
79 PRIMITIVE_WRAPPER_TYPE_MAP.put(int.class, 0);
80 PRIMITIVE_WRAPPER_TYPE_MAP.put(long.class, 0L);
81 PRIMITIVE_WRAPPER_TYPE_MAP.put(short.class, 0);
82 PRIMITIVE_WRAPPER_TYPE_MAP.put(void.class, null);
83 }
84
85 private static Class<?>[] interfacesFromMapper(Class<?> mapper) {
86 Class<?>[] interfaces = mapper.getInterfaces();
87 List<Class<?>> interfacesClass = new ArrayList<>();
88 if (interfaces.length > 0) {
89 interfacesClass.addAll(Arrays.asList(interfaces));
90 }
91 if (mapper.isInterface()) {
92 interfacesClass.add(mapper);
93 }
94 return interfacesClass.toArray(new Class[]{});
95 }
96
97 public static <T> T mock(Class<T> mapper) throws Exception {
98 return mock(mapper, new Configuration());
99 }
100
101 @SuppressWarnings("unchecked")
102 public static <T> T mock(Class<T> mapper, Configuration configuration) throws Exception {
103 return (T) Proxy.newProxyInstance(mapper.getClassLoader(), interfacesFromMapper(mapper),
104 (proxy, method, args) -> {
105 String mapperId = method.getDeclaringClass().getName() + "." + method.getName();
106 if (Objects.isNull(quickMapperChecker)) {
107 quickMapperChecker = new QuickMapperChecker(mapperId, args,
108 new ParamNameResolver(configuration, method), mapper, configuration);
109 quickMapperChecker.setMethodName(method.getName());
110 } else {
111 boolean simple = quickMapperChecker.simple;
112 quickMapperChecker = new QuickMapperChecker(mapperId, args,
113 new ParamNameResolver(configuration, method), mapper, configuration);
114 quickMapperChecker.simple = simple;
115 quickMapperChecker.setMethodName(method.getName());
116 }
117 Class<?> returnType = method.getReturnType();
118 Object result = PRIMITIVE_WRAPPER_TYPE_MAP.get(returnType);
119 if (quickMapperChecker.simple) {
120 quickMapperChecker.printSql();
121 }
122 return Objects.nonNull(result) ? result : new DefaultObjectFactory().create(returnType);
123 });
124 }
125
126 /**
127 * 处理占位符已经被替换成?的时候,用于将占位符重新替换成变量符
128 *
129 * @param sql 占位符sql
130 * @param index 占位符当前处理的索引
131 * @param parameterMappings 占位符参数信息
132 * @return String 变量符sql
133 */
134 private String resetSql(String sql, int index, List<ParameterMapping> parameterMappings, MetaObject metaObject) {
135 int i = sql.indexOf("?");
136 if (i > -1) {
137 ParameterMapping parameterMapping = parameterMappings.get(index);
138 String property = parameterMapping.getProperty();
139 Class<?> javaType = parameterMapping.getJavaType();
140 Object value = metaObject.getValue(parameterMapping.getProperty());
141 String s;
142 if (javaType.equals(String.class) || value instanceof String) {
143 s = sql.replaceFirst("?", ""${" + property + "}"");
144 } else {
145 s = sql.replaceFirst("?", "${" + property + "}");
146 }
147 sql = resetSql(s, ++index, parameterMappings, metaObject);
148 }
149 return sql;
150 }
151
152 /**
153 * sql打印
154 *
155 * @return String
156 * @throws Exception 未知异常
157 */
158 public String getSql() throws Exception {
159 if (!StringUtils.isBlank(this.mapperFile)) {
160 loadMappedStatementByMapperFile(this.mapperFile);
161 }
162 loadMappedStatementByAnnotation();
163 boolean hasMapped = configuration.hasStatement(quickMapperChecker.mapperId);
164 if (!hasMapped) {
165 throw new RuntimeException(
166 "未找到MappedStatement,请检查是否需要绑定mapper xml文件:[" + quickMapperChecker.mapperId + "]");
167 }
168 MappedStatement mappedStatement = configuration.getMappedStatement(quickMapperChecker.mapperId);
169 SqlSource sqlSource = mappedStatement.getSqlSource();
170 Object namedParams = paramNameResolver.getNamedParams(quickMapperChecker.args);
171 BoundSql boundSql = mappedStatement.getBoundSql(namedParams);
172 // 占位符
173 if (sqlSource instanceof RawSqlSource || sqlSource instanceof DynamicSqlSource) {
174 // 占位sql,将#替换成$
175 String sql = boundSql.getSql();
176 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
177 XNode node = findNode();
178 if (Objects.nonNull(node)) {
179 // 解析xml中的标签信息
180 Method parseDynamicTags = XMLScriptBuilder.class.getDeclaredMethod("parseDynamicTags", XNode.class);
181 parseDynamicTags.setAccessible(true);
182
183 XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(configuration, node);
184 MixedSqlNode rootSqlNode = (MixedSqlNode) parseDynamicTags.invoke(xmlScriptBuilder, node);
185 DynamicContext context = new DynamicContext(configuration, namedParams);
186 rootSqlNode.apply(context);
187 // 标签信息参数解析
188 Map<String, Object> bindings = context.getBindings();
189 // 标签参数 + 原始参数
190 ((Map) namedParams).putAll(bindings);
191 }
192 MetaObject metaObject = configuration.newMetaObject(namedParams);
193 processDate(parameterMappings, metaObject);
194 TextSqlNode textSqlNode = new TextSqlNode(resetSql(sql, 0, parameterMappings, metaObject));
195 return SQLUtils
196 .formatMySql((new DynamicSqlSource(configuration, textSqlNode).getBoundSql(namedParams).getSql()));
197 } else {
198 return SQLUtils.formatMySql(boundSql.getSql());
199 }
200 }
201
202 private void processDate(List<ParameterMapping> parameterMappings, MetaObject metaObject) {
203 for (ParameterMapping parameterMapping : parameterMappings) {
204 String property = parameterMapping.getProperty();
205 Object value = metaObject.getValue(property);
206 if (value instanceof Date) {
207 metaObject.setValue(property, DatePatternEnum.DATE_TIME_PATTERN.format((Date) value));
208 }
209 }
210 }
211
212 private XNode findNode() throws Exception {
213 InputStream resourceAsStream = Resources.getResourceAsStream(this.mapperFile);
214 XPathParser xPathParser = new XPathParser(resourceAsStream);
215 XNode mapperNode = xPathParser.evalNode("/mapper");
216 List<XNode> children = mapperNode.getChildren();
217 for (XNode child : children) {
218 if (child.getStringAttribute("id").equals(quickMapperChecker.methodName)) {
219 MapperBuilderAssistant mapperBuilderAssistant =
220 new MapperBuilderAssistant(configuration, quickMapperChecker.mapperFile);
221 mapperBuilderAssistant.setCurrentNamespace(mapper.getName());
222 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, mapperBuilderAssistant);
223 includeParser.applyIncludes(child.getNode());
224 return child;
225 }
226 }
227 // "select|insert|update|delete"
228 return null;
229 }
230
231 ;
232
233 private void loadMappedStatementByAnnotation() {
234 MapperAnnotationBuilder mapperAnnotationBuilder =
235 new MapperAnnotationBuilder(configuration, quickMapperChecker.mapper);
236 mapperAnnotationBuilder.parse();
237 }
238
239 private void loadMappedStatementByMapperFile(String mapperXmlFile) throws Exception {
240 InputStream resourceAsStream = Resources.getResourceAsStream(mapperXmlFile);
241 Map<String, XNode> sqlFragments = configuration.getSqlFragments();
242 new XMLMapperBuilder(resourceAsStream, configuration, mapperXmlFile, sqlFragments).parse();
243 }
244
245 public void printSql() throws Exception {
246 ColorConsole.colorPrintln("🚀 格式化SQL:");
247 ColorConsole.colorPrintln(AnsiColor.BRIGHT_MAGENTA, "{}", getSql());
248 }
249
250 /**
251 * sql信息进行检查
252 *
253 * @param t 泛型
254 * @param <T> 泛型
255 * @return QuickMapperChecker
256 */
257 public static <T> QuickMapperChecker analyse(T t) {
258 // 1. 调用方法
259 return quickMapperChecker;
260 }
261
262 /**
263 * 绑定mapper文件
264 *
265 * @param mapperFile mapper文件地址
266 * @return QuickMapperChecker
267 */
268 public QuickMapperChecker bindMapper(String mapperFile) {
269 quickMapperChecker.setMapperFile(mapperFile);
270 return quickMapperChecker;
271 }
272}
2733.2 Mybatis Plus 使用
1/**
2 * 无需启动容器对sql信息进行检查
3 *
4 * @author liuxin 2022/4/27 17:48
5 */
6@NoArgsConstructor
7public class QuickMapperPlusChecker {
8
9 /**
10 * 方法签名id
11 */
12 @Getter
13 public String mapperId;
14
15 @Setter
16 public String methodName;
17
18 /**
19 * 方法参数
20 */
21 @Getter
22 private Object[] args;
23
24 /**
25 * 参数解析器
26 */
27 @Getter
28 private ParamNameResolver paramNameResolver;
29
30 /**
31 * mapper类型
32 */
33 private Class<?> mapper;
34
35 /**
36 * mybatis配置
37 */
38 @Getter
39 private MybatisConfiguration configuration;
40
41 @Getter
42 @Setter
43 private String mapperFile;
44
45 private boolean simple;
46
47 public QuickMapperPlusChecker(String mapperId, Object[] args, ParamNameResolver paramNameResolver, Class<?> mapper,
48 MybatisConfiguration configuration) {
49 this.mapperId = mapperId;
50 this.args = args;
51 this.paramNameResolver = paramNameResolver;
52 this.mapper = mapper;
53 this.configuration = configuration;
54 }
55
56 public static QuickMapperPlusChecker proxy() {
57 if (Objects.isNull(quickMapperChecker)) {
58 quickMapperChecker = new QuickMapperPlusChecker();
59 quickMapperChecker.simple = true;
60 }
61 return quickMapperChecker;
62 }
63
64 private static QuickMapperPlusChecker quickMapperChecker;
65
66 private static final Map<Class<?>, Object> PRIMITIVE_WRAPPER_TYPE_MAP = new IdentityHashMap<>(8);
67
68 static {
69 PRIMITIVE_WRAPPER_TYPE_MAP.put(Boolean.class, false);
70 PRIMITIVE_WRAPPER_TYPE_MAP.put(Byte.class, 0);
71 PRIMITIVE_WRAPPER_TYPE_MAP.put(Character.class, "");
72 PRIMITIVE_WRAPPER_TYPE_MAP.put(Double.class, 0D);
73 PRIMITIVE_WRAPPER_TYPE_MAP.put(Float.class, 0L);
74 PRIMITIVE_WRAPPER_TYPE_MAP.put(Integer.class, 0);
75 PRIMITIVE_WRAPPER_TYPE_MAP.put(Long.class, 0L);
76 PRIMITIVE_WRAPPER_TYPE_MAP.put(Short.class, 0);
77 PRIMITIVE_WRAPPER_TYPE_MAP.put(Void.class, Void.TYPE);
78
79 PRIMITIVE_WRAPPER_TYPE_MAP.put(boolean.class, false);
80 PRIMITIVE_WRAPPER_TYPE_MAP.put(byte.class, 0);
81 PRIMITIVE_WRAPPER_TYPE_MAP.put(char.class, "");
82 PRIMITIVE_WRAPPER_TYPE_MAP.put(double.class, 0D);
83 PRIMITIVE_WRAPPER_TYPE_MAP.put(float.class, 0L);
84 PRIMITIVE_WRAPPER_TYPE_MAP.put(int.class, 0);
85 PRIMITIVE_WRAPPER_TYPE_MAP.put(long.class, 0L);
86 PRIMITIVE_WRAPPER_TYPE_MAP.put(short.class, 0);
87 PRIMITIVE_WRAPPER_TYPE_MAP.put(void.class, null);
88 }
89
90 private static Class<?>[] interfacesFromMapper(Class<?> mapper) {
91 Class<?>[] interfaces = mapper.getInterfaces();
92 List<Class<?>> interfacesClass = new ArrayList<>();
93 if (interfaces.length > 0) {
94 interfacesClass.addAll(Arrays.asList(interfaces));
95 }
96 if (mapper.isInterface()) {
97 interfacesClass.add(mapper);
98 }
99 return interfacesClass.toArray(new Class[] {});
100 }
101
102 public static <T> T mock(Class<T> mapper) throws Exception {
103 return mock(mapper, new MybatisConfiguration());
104 }
105
106 @SuppressWarnings("unchecked")
107 public static <T> T mock(Class<T> mapper, MybatisConfiguration configuration) throws Exception {
108 return (T)Proxy.newProxyInstance(mapper.getClassLoader(), interfacesFromMapper(mapper),
109 (proxy, method, args) -> {
110 String mapperId = mapper.getName() + "." + method.getName();
111 if (Objects.isNull(quickMapperChecker)) {
112 quickMapperChecker = new QuickMapperPlusChecker(mapperId, args,
113 new ParamNameResolver(configuration, method), mapper, configuration);
114 quickMapperChecker.setMethodName(method.getName());
115 } else {
116 boolean simple = quickMapperChecker.simple;
117 quickMapperChecker = new QuickMapperPlusChecker(mapperId, args,
118 new ParamNameResolver(configuration, method), mapper, configuration);
119 quickMapperChecker.simple = simple;
120 quickMapperChecker.setMethodName(method.getName());
121 }
122 Class<?> returnType = method.getReturnType();
123 Object result = PRIMITIVE_WRAPPER_TYPE_MAP.get(returnType);
124 if (quickMapperChecker.simple) {
125 quickMapperChecker.printSql();
126 }
127 return Objects.nonNull(result) ? result : new DefaultObjectFactory().create(returnType);
128 });
129 }
130
131 /**
132 * 处理占位符已经被替换成?的时候,用于将占位符重新替换成变量符
133 *
134 * @param sql
135 * 占位符sql
136 * @param index
137 * 占位符当前处理的索引
138 * @param parameterMappings
139 * 占位符参数信息
140 * @return String 变量符sql
141 */
142 private String resetSql(String sql, int index, List<ParameterMapping> parameterMappings, MetaObject metaObject) {
143 int i = sql.indexOf("?");
144 if (i > -1) {
145 ParameterMapping parameterMapping = parameterMappings.get(index);
146 String property = parameterMapping.getProperty();
147 Class<?> javaType = parameterMapping.getJavaType();
148 Object value = metaObject.getValue(parameterMapping.getProperty());
149 String s;
150 if (javaType.equals(String.class) || value instanceof String) {
151 s = sql.replaceFirst("?", ""${" + property + "}"");
152 } else {
153 s = sql.replaceFirst("?", "${" + property + "}");
154 }
155 sql = resetSql(s, ++index, parameterMappings, metaObject);
156 }
157 return sql;
158 }
159
160 /**
161 * sql打印
162 *
163 * @return String
164 * @throws Exception
165 * 未知异常
166 */
167 public String getSql() throws Exception {
168 if (!StringUtils.isBlank(this.mapperFile)) {
169 loadMappedStatementByMapperFile(this.mapperFile);
170 }
171 loadMappedStatementByAnnotation();
172 new SqlRunnerInjector().inject(configuration);
173 boolean hasMapped = configuration.hasStatement(quickMapperChecker.mapperId);
174 if (!hasMapped) {
175 throw new RuntimeException(
176 "未找到MappedStatement,请检查是否需要绑定mapper xml文件:[" + quickMapperChecker.mapperId + "]");
177 }
178 MappedStatement mappedStatement = configuration.getMappedStatement(quickMapperChecker.mapperId);
179 SqlSource sqlSource = mappedStatement.getSqlSource();
180 Object namedParams = paramNameResolver.getNamedParams(quickMapperChecker.args);
181 BoundSql boundSql = mappedStatement.getBoundSql(namedParams);
182 // 占位符
183 if (sqlSource instanceof RawSqlSource || sqlSource instanceof DynamicSqlSource) {
184 // 占位sql,将#替换成$
185 String sql = boundSql.getSql();
186 List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
187
188 XNode node = findNode();
189 if (Objects.nonNull(node)) {
190 XMLScriptBuilder xmlScriptBuilder = new XMLScriptBuilder(configuration, node);
191 // 解析xml中的标签信息
192 Method parseDynamicTags = XMLScriptBuilder.class.getDeclaredMethod("parseDynamicTags", XNode.class);
193 parseDynamicTags.setAccessible(true);
194 MixedSqlNode rootSqlNode = (MixedSqlNode)parseDynamicTags.invoke(xmlScriptBuilder, node);
195 DynamicContext context = new DynamicContext(configuration, namedParams);
196 rootSqlNode.apply(context);
197 // 标签信息参数解析
198 Map<String, Object> bindings = context.getBindings();
199 // 标签参数 + 原始参数
200 ((Map)namedParams).putAll(bindings);
201 }
202 MetaObject metaObject = configuration.newMetaObject(namedParams);
203 processDate(parameterMappings, metaObject);
204 TextSqlNode textSqlNode = new TextSqlNode(resetSql(sql, 0, parameterMappings, metaObject));
205 return SQLUtils
206 .formatMySql((new DynamicSqlSource(configuration, textSqlNode).getBoundSql(namedParams).getSql()));
207 } else {
208 return SQLUtils.formatMySql(boundSql.getSql());
209 }
210 }
211
212 private void processDate(List<ParameterMapping> parameterMappings, MetaObject metaObject) {
213 for (ParameterMapping parameterMapping : parameterMappings) {
214 String property = parameterMapping.getProperty();
215 Object value = metaObject.getValue(property);
216 if (value instanceof Date) {
217 metaObject.setValue(property, DatePatternEnum.DATE_TIME_PATTERN.format((Date)value));
218 }
219 }
220 }
221
222 private XNode findNode() throws Exception {
223 if (StringUtils.isNotBlank(this.mapperFile)) {
224 InputStream resourceAsStream = Resources.getResourceAsStream(this.mapperFile);
225 XPathParser xPathParser = new XPathParser(resourceAsStream);
226 XNode mapperNode = xPathParser.evalNode("/mapper");
227 List<XNode> children = mapperNode.getChildren();
228 for (XNode child : children) {
229 if (child.getStringAttribute("id").equals(quickMapperChecker.methodName)) {
230 MapperBuilderAssistant mapperBuilderAssistant =
231 new MapperBuilderAssistant(configuration, quickMapperChecker.mapperFile);
232 mapperBuilderAssistant.setCurrentNamespace(mapper.getName());
233 XMLIncludeTransformer includeParser =
234 new XMLIncludeTransformer(configuration, mapperBuilderAssistant);
235 includeParser.applyIncludes(child.getNode());
236 return child;
237 }
238 }
239 }
240 // "select|insert|update|delete"
241 return null;
242 };
243
244 private void loadMappedStatementByAnnotation() {
245 MybatisMapperAnnotationBuilder mapperAnnotationBuilder =
246 new MybatisMapperAnnotationBuilder(configuration, quickMapperChecker.mapper);
247 mapperAnnotationBuilder.parse();
248 }
249
250 private void loadMappedStatementByMapperFile(String mapperXmlFile) throws Exception {
251 InputStream resourceAsStream = Resources.getResourceAsStream(mapperXmlFile);
252 Map<String, XNode> sqlFragments = configuration.getSqlFragments();
253 new XMLMapperBuilder(resourceAsStream, configuration, mapperXmlFile, sqlFragments).parse();
254 }
255
256 public void printSql() throws Exception {
257 ColorConsole.colorPrintln("🚀 格式化SQL:");
258 ColorConsole.colorPrintln(AnsiColor.BRIGHT_MAGENTA, "{}", getSql());
259 }
260
261 /**
262 * sql信息进行检查
263 *
264 * @param t
265 * 泛型
266 * @return QuickMapperChecker
267 * @param <T>
268 * 泛型
269 */
270 public static <T> QuickMapperPlusChecker analyse(T t) {
271 // 1. 调用方法
272 return quickMapperChecker;
273 }
274
275 /**
276 * 绑定mapper文件
277 *
278 * @param mapperFile
279 * mapper文件地址
280 * @return QuickMapperChecker
281 */
282 public QuickMapperPlusChecker bindMapper(String mapperFile) {
283 quickMapperChecker.setMapperFile(mapperFile);
284 return quickMapperChecker;
285 }
286}
287