项目概况

第15篇:Mybatis中打印Sql信息

2026-01-2911 min read后端技术
Mybatis

!!! tip Sql打印需求 在Mybatis中如果我们要对我们的sql信息进行检查, 只能启动Spring容器, 去执行根据成功和失败来判断我们的逻辑是否有问题。 此时会比较耗时,因为要启动容器。基于这个痛点, 本文要设计一个工具。使我们不依赖Spring容器,也不依赖任何外部插件,直接就把 Sql信息的打印出来。 !!!

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

使用方法

java
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信息。而动态sql是由DynamicSqlSource来直接解析参数 生成sql的。那么我们就需要将#占位符都调换成变量符,然后利用DynamicSqlSource给直接生成sql信息的。

第四步sql信息格式化。

第五步使用方法设计。

二、思路实现

2.1 MappedStatement解析

2.1.1 xml参数解析

java
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解析

java
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片段了。

java
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 其他标签解析

java
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。很可惜这个方法是受到保护的。 我们只能使用反射来对参数进行解析。

java
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。

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

而他的xml比较复杂的。

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是这样的。

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

java
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}。

java
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的能力,我们直接将参数准备好,使用就好了。

java
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格式化工具

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

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

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

2.5 使用方法设计

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

java
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 使用

java
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} 273

3.2 Mybatis Plus 使用

java
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