├── sqlprinter-samples ├── spring-boot │ ├── src │ │ ├── main │ │ │ ├── resources │ │ │ │ ├── schema.sql │ │ │ │ ├── application.yml │ │ │ │ ├── mapper │ │ │ │ │ └── UserMapper.xml │ │ │ │ └── log4j.properties │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── github │ │ │ │ └── dreamroute │ │ │ │ └── sqlprinter │ │ │ │ └── boot │ │ │ │ ├── mapper │ │ │ │ └── UserMapper.java │ │ │ │ ├── Application.java │ │ │ │ └── domain │ │ │ │ └── User.java │ │ └── test │ │ │ └── java │ │ │ └── com │ │ │ └── github │ │ │ └── dreamroute │ │ │ └── sqlprinter │ │ │ └── boot │ │ │ └── mapper │ │ │ ├── UserMapperWithoutFormatTest.java │ │ │ └── UserMapperWithFormatTest.java │ └── pom.xml └── pom.xml ├── .gitignore ├── sqlprinter-spring-boot-starter ├── src │ └── main │ │ └── java │ │ └── com │ │ └── github │ │ └── dreamroute │ │ └── sqlprinter │ │ └── starter │ │ ├── converter │ │ └── def │ │ │ ├── EnumConverter.java │ │ │ └── DateConverter.java │ │ ├── anno │ │ ├── EnableSQLPrinter.java │ │ ├── SqlprinterProperties.java │ │ ├── ValueConverter.java │ │ └── SQLPrinterConfig.java │ │ ├── util │ │ └── PluginUtil.java │ │ └── interceptor │ │ ├── PrettyTable.java │ │ └── SqlPrinter.java └── pom.xml ├── README.md └── pom.xml /sqlprinter-samples/spring-boot/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE IF EXISTS `smart_user`; 2 | DROP TABLE IF EXISTS `smart_typehandler`; 3 | 4 | CREATE TABLE `smart_user` 5 | ( 6 | `id` bigint(0) NOT NULL AUTO_INCREMENT, 7 | `name` varchar(32) NULL, 8 | `password` varchar(32) NULL, 9 | `version` bigint(0) NULL DEFAULT 0, 10 | `birthday` datetime DEFAULT NULL, 11 | `gender` tinyint(4) null, 12 | PRIMARY KEY (`id`) 13 | ); -------------------------------------------------------------------------------- /sqlprinter-samples/spring-boot/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | sqlprinter: 2 | show: true 3 | filter: 4 | - com.github.dreamroute.sqlprinter.boot.mapper.UserMapper.selectById 5 | - com.github.dreamroute.sqlprinter.boot.mapper.UserMapper.selectByIds 6 | show-result: true 7 | show-result-exclude: password, version 8 | mybatis: 9 | mapper-locations: /mapper/* 10 | type-handlers-package: com.github.dreamroute.mybatis.pro.base.typehandler 11 | pro: 12 | delete-use-update: false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /sqlprinter-spring-boot-starter/target/classes/ 3 | /sqlprinter-spring-boot-starter/sqlprinter-spring-boot-starter.iml 4 | /sqlprinter-samples/sqlprinter-samples.iml 5 | /sqlprinter-samples/spring-boot/target/ 6 | /target/ 7 | /sqlprinter-spring-boot-starter/target/ 8 | /sqlprinter-samples/target/ 9 | /sqlprinter-samples/spring-boot/.flattened-pom.xml 10 | /sqlprinter-samples/spring-boot/spring-boot.iml 11 | /.flattened-pom.xml 12 | /sqlprinter.iml 13 | /sqlprinter-samples/.flattened-pom.xml 14 | /sqlprinter-spring-boot-starter/.flattened-pom.xml 15 | -------------------------------------------------------------------------------- /sqlprinter-samples/spring-boot/src/main/java/com/github/dreamroute/sqlprinter/boot/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.boot.mapper; 2 | 3 | import com.github.dreamroute.mybatis.pro.service.mapper.BaseMapper; 4 | import com.github.dreamroute.sqlprinter.boot.domain.User; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author w.dehai 10 | */ 11 | public interface UserMapper extends BaseMapper { 12 | List selectUsers(); 13 | List selectUserByIds(List ids); 14 | 15 | User selectUserByName(String number); 16 | } 17 | -------------------------------------------------------------------------------- /sqlprinter-spring-boot-starter/src/main/java/com/github/dreamroute/sqlprinter/starter/converter/def/EnumConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.starter.converter.def; 2 | 3 | import com.github.dreamroute.mybatis.pro.base.codec.enums.EnumMarker; 4 | import com.github.dreamroute.sqlprinter.starter.anno.ValueConverter; 5 | 6 | /** 7 | * 枚举转换器 8 | * 9 | * @author w.dehai.2021/9/7.15:51 10 | */ 11 | public class EnumConverter implements ValueConverter { 12 | @Override 13 | public Object convert(Object value) { 14 | if (value instanceof EnumMarker) { 15 | value = ((EnumMarker) value).getValue(); 16 | } 17 | return value; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /sqlprinter-spring-boot-starter/src/main/java/com/github/dreamroute/sqlprinter/starter/converter/def/DateConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.starter.converter.def; 2 | 3 | import cn.hutool.core.date.DateUtil; 4 | import com.github.dreamroute.sqlprinter.starter.anno.ValueConverter; 5 | 6 | import java.util.Date; 7 | 8 | /** 9 | * 日期转换器 10 | * 11 | * @author w.dehai.2021/9/7.15:35 12 | */ 13 | public class DateConverter implements ValueConverter { 14 | @Override 15 | public Object convert(Object value) { 16 | if (value instanceof Date) { 17 | value = DateUtil.format((Date) value, "yyyy-MM-dd HH:mm:ss.SSS"); 18 | } 19 | return value; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sqlprinter-spring-boot-starter/src/main/java/com/github/dreamroute/sqlprinter/starter/anno/EnableSQLPrinter.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.starter.anno; 2 | 3 | import org.springframework.context.annotation.Import; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | 11 | /** 12 | * @author w.dehai 13 | */ 14 | @Target(ElementType.TYPE) 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Import(SQLPrinterConfig.class) 17 | public @interface EnableSQLPrinter { 18 | 19 | /** 20 | * SQL打印时候的值转换工具,比如属性值是Date类型,而在sql中希望展示成yyyy-MM-dd HH:mm:ss类型,那么就把转换工具配置在此 21 | */ 22 | Class[] converters() default {}; 23 | } 24 | -------------------------------------------------------------------------------- /sqlprinter-spring-boot-starter/src/main/java/com/github/dreamroute/sqlprinter/starter/anno/SqlprinterProperties.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.starter.anno; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | 6 | /** 7 | * @author w.dehai 8 | */ 9 | @Data 10 | @ConfigurationProperties(prefix = "sqlprinter") 11 | public class SqlprinterProperties { 12 | 13 | /** 14 | * 是否显示SQL,默认显示 15 | */ 16 | private boolean show = true; 17 | 18 | /** 19 | * 是否显示查询结果表格 20 | */ 21 | private boolean showResult = false; 22 | 23 | /** 24 | * 结果表格中不打印的字段, 默认全部打印 25 | */ 26 | private String[] showResultExclude = {}; 27 | 28 | /** 29 | * 是否格式化SQL,默认格式化 30 | */ 31 | private boolean format = true; 32 | 33 | /** 34 | * 配置不需要打印SQL的mapper方法名 35 | */ 36 | private String[] filter; 37 | } 38 | -------------------------------------------------------------------------------- /sqlprinter-samples/spring-boot/src/main/java/com/github/dreamroute/sqlprinter/boot/Application.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.boot; 2 | 3 | import com.github.dreamroute.sqlprinter.starter.anno.EnableSQLPrinter; 4 | import com.github.dreamroute.sqlprinter.starter.converter.def.DateConverter; 5 | import com.github.dreamroute.sqlprinter.starter.converter.def.EnumConverter; 6 | import org.mybatis.spring.annotation.MapperScan; 7 | import org.springframework.boot.SpringApplication; 8 | import org.springframework.boot.autoconfigure.SpringBootApplication; 9 | 10 | /** 11 | * @author w.dehai 12 | */ 13 | @SpringBootApplication 14 | @MapperScan("com.github.dreamroute.sqlprinter.boot.mapper") 15 | @EnableSQLPrinter(converters = {DateConverter.class, EnumConverter.class}) 16 | public class Application { 17 | 18 | public static void main(String[] args) { 19 | SpringApplication.run(Application.class); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /sqlprinter-spring-boot-starter/src/main/java/com/github/dreamroute/sqlprinter/starter/anno/ValueConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.starter.anno; 2 | 3 | /** 4 | * 类型转换器 5 | * 6 | * @author w.dehai.2021/9/7.15:27 7 | */ 8 | public interface ValueConverter { 9 | 10 | /** 11 | * 将参数类型的值转成你希望在sql中显示的值,比如value是Date类型,而在sql中希望展示成yyyy-MM-dd HH:mm:ss类型,就在convert方法中实现此逻辑,大概是这样: 12 | *
13 |      * public class DateConverter implements ValueConverter {
14 |      *     @Override
15 |      *     public Object convert(Object value) {
16 |      *         if (value instanceof Date) {
17 |      *             value = DateUtil.format((Date) value, "yyyy-MM-dd HH:mm:sss.SSS");
18 |      *         }
19 |      *         return value;
20 |      *     }
21 |      * }
22 |      * 
23 | * 24 | * @param value 参数值 25 | * @return 返回显示值 26 | */ 27 | Object convert(Object value); 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sqlprinter-samples/spring-boot/src/main/resources/mapper/UserMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 16 | 17 | 20 | 21 | -------------------------------------------------------------------------------- /sqlprinter-samples/spring-boot/src/main/java/com/github/dreamroute/sqlprinter/boot/domain/User.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.boot.domain; 2 | 3 | import com.github.dreamroute.mybatis.pro.base.codec.enums.EnumMarker; 4 | import com.github.dreamroute.mybatis.pro.core.annotations.Id; 5 | import com.github.dreamroute.mybatis.pro.core.annotations.Table; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | 11 | import java.util.Date; 12 | 13 | /** 14 | * @author w.dehai 15 | */ 16 | @Data 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | @Table("smart_user") 20 | public class User { 21 | 22 | @Id 23 | private Integer id; 24 | private String name; 25 | private String password; 26 | private Long version; 27 | private Date birthday; 28 | private Gender gender; 29 | 30 | @Getter 31 | @AllArgsConstructor 32 | public enum Gender implements EnumMarker { 33 | MALE(1, "男"), 34 | FEMALE(2, "女"); 35 | 36 | private final Integer value; 37 | private final String desc; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /sqlprinter-samples/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | spring-boot 8 | 9 | 10 | sqlprinter 11 | com.github.dreamroute 12 | ${revision} 13 | 14 | 15 | sqlprinter-samples 16 | pom 17 | 18 | 19 | 20 | 21 | com.github.dreamroute 22 | mybatis-pro-boot-starter 23 | ${mybatis-pro.version} 24 | 25 | 26 | org.apache.tomcat.embed 27 | tomcat-embed-core 28 | 10.1.0-M14 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sqlprinter-samples/spring-boot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sqlprinter-samples 8 | com.github.dreamroute 9 | ${revision} 10 | 11 | 12 | spring-boot 13 | 14 | 15 | 16 | com.github.dreamroute 17 | sqlprinter-spring-boot-starter 18 | ${revision} 19 | 20 | 21 | com.ninja-squad 22 | DbSetup 23 | 2.0.0 24 | 25 | 26 | com.h2database 27 | h2 28 | test 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /sqlprinter-samples/spring-boot/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=debug,stdout 2 | 3 | log4j.rootCategory=INFO, stdout , R 4 | log4j.appender.stdout=org.apache.log4j.ConsoleAppender 5 | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout 6 | log4j.appender.stdout.layout.ConversionPattern=[QC] %p [%t] %C.%M(%L) | %m%n 7 | log4j.appender.R=org.apache.log4j.DailyRollingFileAppender 8 | log4j.appender.R.File=D:\Tomcat 5.5\logs\qc.log 9 | log4j.appender.R.layout=org.apache.log4j.PatternLayout 10 | log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n 11 | log4j.logger.com.neusoft=DEBUG 12 | log4j.logger.com.opensymphony.oscache=ERROR 13 | log4j.logger.net.sf.navigator=ERROR 14 | log4j.logger.org.apache.commons=ERROR 15 | log4j.logger.org.apache.struts=WARN 16 | log4j.logger.org.displaytag=ERROR 17 | log4j.logger.org.springframework=DEBUG 18 | log4j.logger.com.ibatis.db=WARN 19 | log4j.logger.org.apache.velocity=FATAL 20 | log4j.logger.com.canoo.webtest=WARN 21 | log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN 22 | log4j.logger.org.hibernate=DEBUG 23 | log4j.logger.org.logicalcobwebs=WARN 24 | 25 | # show sql 26 | log4j.logger.com.ibatis=DEBUG 27 | log4j.logger.com.ibatis.common.jdbc.SimpleDataSource=DEBUG 28 | log4j.logger.com.ibatis.common.jdbc.ScriptRunner=DEBUG 29 | log4j.logger.com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG 30 | log4j.logger.java.sql.Connection=DEBUG 31 | log4j.logger.java.sql.Statement=DEBUG 32 | log4j.logger.java.sql.PreparedStatement=DEBUG -------------------------------------------------------------------------------- /sqlprinter-spring-boot-starter/src/main/java/com/github/dreamroute/sqlprinter/starter/anno/SQLPrinterConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.starter.anno; 2 | 3 | import cn.hutool.core.util.ReflectUtil; 4 | import com.github.dreamroute.sqlprinter.starter.interceptor.SqlPrinter; 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 7 | import org.springframework.context.ApplicationContext; 8 | import org.springframework.context.ApplicationContextAware; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | import org.springframework.core.annotation.AnnotationUtils; 12 | import org.springframework.lang.NonNull; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collection; 16 | import java.util.List; 17 | 18 | /** 19 | * 初始化插件配置信息 20 | * 21 | * @author w.dehai 22 | */ 23 | @Configuration 24 | @EnableConfigurationProperties(SqlprinterProperties.class) 25 | public class SQLPrinterConfig implements ApplicationContextAware { 26 | 27 | private final List convs = new ArrayList<>(); 28 | 29 | @Override 30 | public void setApplicationContext(@NonNull ApplicationContext context) throws BeansException { 31 | Collection values = context.getBeansWithAnnotation(EnableSQLPrinter.class).values(); 32 | values.forEach(e -> { 33 | EnableSQLPrinter annotation = AnnotationUtils.findAnnotation(e.getClass(), EnableSQLPrinter.class); 34 | 35 | // 转换器 36 | Class[] converters = annotation.converters(); 37 | for (Class converter : converters) { 38 | convs.add(ReflectUtil.newInstance(converter)); 39 | } 40 | }); 41 | } 42 | 43 | @Bean 44 | public SqlPrinter sqlPrinter(SqlprinterProperties sqlprinterProperties) { 45 | return new SqlPrinter(sqlprinterProperties, convs); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /sqlprinter-spring-boot-starter/src/main/java/com/github/dreamroute/sqlprinter/starter/util/PluginUtil.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2016 342252328@qq.com 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | package com.github.dreamroute.sqlprinter.starter.util; 25 | 26 | import org.apache.ibatis.reflection.MetaObject; 27 | import org.apache.ibatis.reflection.SystemMetaObject; 28 | 29 | import java.lang.reflect.Proxy; 30 | 31 | public final class PluginUtil { 32 | 33 | private PluginUtil() {} // private constructor 34 | 35 | /** 36 | *

Recursive get the original target object. 37 | *

If integrate more than a plugin, maybe there are conflict in these plugins, because plugin will proxy the object.
38 | * So, here get the orignal target object 39 | * 40 | * @param target proxy-object 41 | * @return original target object 42 | */ 43 | public static Object processTarget(Object target) { 44 | if(Proxy.isProxyClass(target.getClass())) { 45 | MetaObject mo = SystemMetaObject.forObject(target); 46 | return processTarget(mo.getValue("h.target")); 47 | } 48 | return target; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /sqlprinter-spring-boot-starter/src/main/java/com/github/dreamroute/sqlprinter/starter/interceptor/PrettyTable.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.starter.interceptor; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.stream.Collectors; 7 | 8 | /** 9 | * 控制台打印表格 10 | */ 11 | public class PrettyTable { 12 | /** 13 | * 数据 14 | */ 15 | private final List> rows = new ArrayList<>(); 16 | /** 17 | * 列数量 18 | */ 19 | private final int columnCount; 20 | /** 21 | * 列宽度 22 | */ 23 | private final int[] columnWidth; 24 | 25 | public PrettyTable(String[] header) { 26 | columnCount = header.length; 27 | columnWidth = new int[columnCount]; 28 | Arrays.fill(columnWidth, 0); 29 | addRow(header); 30 | } 31 | 32 | public void addRow(String[] row) { 33 | List list = Arrays.stream(row).map(value -> value != null ? value : "").collect(Collectors.toList()); 34 | rows.add(list); 35 | for (int i = 0; i < columnWidth.length; i++) { 36 | columnWidth[i] = Math.max(list.get(i).getBytes().length, columnWidth[i]); 37 | } 38 | } 39 | 40 | 41 | @Override 42 | public String toString() { 43 | StringBuilder builder = new StringBuilder(); 44 | //边距 45 | int margin = 1; 46 | //总列宽+2*边距数量*列数+列分隔符数量-1 47 | int lineLength = Arrays.stream(columnWidth).sum() + margin * 2 * columnCount + (columnCount - 1); 48 | 49 | builder.append("|").append(fillChars('=', lineLength)).append("|\n"); 50 | for (int i = 0; i < rows.size(); i++) { 51 | List row = rows.get(i); 52 | for (int j = 0; j < columnCount; j++) { 53 | String value = j < row.size() ? row.get(j) : ""; 54 | builder.append('|').append(fillChars(' ', margin)).append(value); 55 | builder.append(fillChars(' ', columnWidth[j] - value.getBytes().length + margin)); 56 | } 57 | builder.append("|\n"); 58 | if (i == 0) { 59 | builder.append("|").append(fillChars('=', lineLength)).append("|\n"); 60 | } 61 | if (i == rows.size() - 1) { 62 | builder.append("|").append(fillChars('=', lineLength)).append("|\n"); 63 | } 64 | } 65 | return builder.toString(); 66 | } 67 | 68 | private String fillChars(char c, int len) { 69 | char[] chArr = new char[len]; 70 | Arrays.fill(chArr, c); 71 | return new String(chArr); 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sqlprinter SQL打印插件 2 | 3 | ### MyBatis Simple SQL Print Plugin 4 | 5 | ![mybatis](http://mybatis.github.io/images/mybatis-logo.png) 6 | 7 | ### Get Start... 8 | ``` 9 | 10 | com.github.dreamroute 11 | sqlprinter-spring-boot-starter 12 | latest version 13 | 14 | ``` 15 | 16 | ---------- 17 | ### 最新版本:[点击查看](https://central.sonatype.com/artifact/com.github.dreamroute/sqlprinter-spring-boot-starter) 18 | 19 | -------------- 20 | 21 | > 描述:本插件主要是为了解决输出的sql中参数是问号'?'形式不易观察,而使用真实值替换掉问号'?'。生产环境如果不需要此插件(设置成false即可)。
22 | > 如果应用中使用了mybatis plus,那么打印sql没问题,但是格式化可能不成功,不影响业务,这是由于mybatis plus操蛋的改动了mybatis的插件接口 23 | 24 | ------------ 25 | ### 兼容性 26 | 如果你项目中使用了类似`mybatis plus`这种框架,优先使用mybatis plus依赖的mybatis,排除本插件的依赖,如下: 27 | ```xml 28 | 29 | com.github.dreamroute 30 | sqlprinter-spring-boot-starter 31 | xxx.version 32 | 33 | 34 | mybatis 35 | org.mybatis 36 | 37 | 38 | 39 | ``` 40 | 41 | ---------- 42 | ### 使用方式,Spring Boot项目: 43 | 1. 版本2.0.0之后:在启动类上使用@EnableSQLPrinter即可开起(如果生产环境不希望显示sql,在application.yml/properties中配置`sqlprinter.show=false`即可) 44 | 2. 是否格式化SQL,默认不格式化,可以配置(`sqlprinter.format = true`)来格式化SQL,对于一些比较特殊的SQL,如果格式化失败,那么会打印未被格式化时的sql,这时候会打印错误日志, 45 | 但是不会对业务造成任何影响,如果在乎错误日志,觉得错误日志不好看,那么可以关闭格式化功能 46 | > 格式化的好处:1. 系统打印的SQL很整齐,风格一致;2. 由于mybatis使用动态标签,如果不格式化,那么打印的SQL会移除不满足条件的动态标签,显得SQL很凌乱 47 | 3. 过滤功能:对于有一些sql打印比较频繁,不希望展示在日志中,那么可以在application.yml/properties中配置中配置sqlprinter.filter数组(数组内容就是Mapper接口的方法名),如下: 48 | ``` 49 | sqlprinter: 50 | show: true 51 | filter: 52 | - com.github.dreamroute.sqlprinter.boot.mapper.UserMapper.selectById 53 | - com.github.dreamroute.sqlprinter.boot.mapper.UserMapper.selectAll 54 | ``` 55 | 那么selectById和selectAll方法就不会打印sql了。 56 | 4. 对于查询sql,显示查询结果,整条效果如下: 57 | ``` 58 | ==> com.github.dreamroute.sqlprinter.boot.mapper.UserMapper.selectUserByIds 59 | SELECT * 60 | FROM smart_user 61 | WHERE id IN (1, 2) 62 | ``` 63 | 5. 配置项: 64 | ``` 65 | sqlprinter.show-result = true/false(是否显示查询结果,默认true) 66 | ``` 67 | ``` 68 | |==========================================================| 69 | | id | name | password | version | birthday | gender | 70 | |==========================================================| 71 | | 1 | w.dehai | null | 0 | null | null | 72 | | 2 | Dreamroute | null | 0 | null | null | 73 | |==========================================================| 74 | ``` 75 | 76 | ---------- 77 | 78 | ### 2. 效果: ### 79 | > 之前:**insert into xxx (name, password) values (?, ?)** 80 | 81 | > 之后:**insert into xxx (name, password) values ('tom', '123456')** 82 | 83 | ---------- 84 | 85 | ### 2.1. 自定义显示内容 86 | 1. 在插件打印SQL的时候,对于有些特殊数据类型,可能插件默认打印方式不符合你的要求,比如日期类型Date默认打印的就是Date类型 87 | 调用`toString`方法的结果,类似这样`Tue Sep 07 16:25:28 CST 2021`,而你需要的是`2021-09-07 16:25:028.673`,如果从控制台或者日志文件中直接复制带有这种`Tue Sep 07 16:25:28 CST 2021`时间的sql去数据库执行,很有可能会报错, 88 | 这时你就可以自定义日期类型的打印格式,打印成`2021-09-07 16:25:028.673`这种易读并且可以直接用于执行的格式。 89 | 2. 对于`Date`参数,希望打印`yyyy-MM-dd HH:mm:ss.SSS`类型的日期 90 | 3. 创建日期转换器类,实现`ValueConverter`接口: 91 | ```java 92 | public class DateConverter implements ValueConverter { 93 | @Override 94 | public Object convert(Object value) { 95 | if (value instanceof Date) { 96 | value = DateUtil.format((Date) value, "yyyy-MM-dd HH:mm:ss"); 97 | } 98 | return value; 99 | } 100 | } 101 | ``` 102 | 4. 在`@EnableSQLPrinter`的属性`converters`中加入即可,比如`@EnableSQLPrinter(converters = {DateConverter.class, EnumConverter.class})` 103 | 5. 此时你的属性为`Date`的字段打印的就是`2021-09-07 16:25:028.673`这种格式的了 104 | 6. 框架已经内置提供了两个现成的转换工具,你可以直接使用,分别是`日期`和`枚举`值转换工具,如果满足你的需求就用,不满足就自定义,在def包下: 105 | 1. com.github.dreamroute.sqlprinter.starter.converter.def.DateConverter 106 | 2. com.github.dreamroute.sqlprinter.starter.converter.def.EnumConverter 107 | 108 | ### 3.插件说明: ### 109 | 1. 本插件是为了方便程序员观察真实sql的打印情况(问号'?'已经被真实值替换),特别是参数较多的sql,很直观清晰,可以直接复制sql在数据库中执行,非常友好。
110 | 2. 本插件仅仅是打印sql,插件内部不会破坏mybatis的任何核心,也不会和任何其他插件造成冲突,可以放心使用。 111 | 112 | ---------- 113 | 114 | ### 4.关于插件: ### 115 | 如果您有什么建议或者意见,欢迎留言,也欢迎pull request,作者会将你优秀的思想加入到插件里面来,为其他人更好的解决问题。 116 | 117 | ---------- 118 | ### 5.Demo ### 119 | 本项目可以直接pull到本地执行单元测试观察效果 120 | 121 | ---------- 122 | 123 | ### 7.关于作者: ### 124 | 作者QQ:342252328 125 | 作者邮箱:342252328@qq.com 126 | -------------------------------------------------------------------------------- /sqlprinter-samples/spring-boot/src/test/java/com/github/dreamroute/sqlprinter/boot/mapper/UserMapperWithoutFormatTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.boot.mapper; 2 | 3 | import com.github.dreamroute.common.util.test.Appender; 4 | import com.github.dreamroute.sqlprinter.boot.domain.User; 5 | import com.github.dreamroute.sqlprinter.starter.anno.EnableSQLPrinter; 6 | import com.github.dreamroute.sqlprinter.starter.anno.SqlprinterProperties; 7 | import com.github.dreamroute.sqlprinter.starter.converter.def.DateConverter; 8 | import com.github.dreamroute.sqlprinter.starter.converter.def.EnumConverter; 9 | import com.github.dreamroute.sqlprinter.starter.interceptor.SqlPrinter; 10 | import com.ninja_squad.dbsetup.DbSetup; 11 | import com.ninja_squad.dbsetup.Operations; 12 | import com.ninja_squad.dbsetup.destination.DataSourceDestination; 13 | import com.ninja_squad.dbsetup.operation.Insert; 14 | import org.junit.jupiter.api.AfterEach; 15 | import org.junit.jupiter.api.BeforeEach; 16 | import org.junit.jupiter.api.Test; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | 19 | import javax.annotation.Resource; 20 | import javax.sql.DataSource; 21 | import java.text.SimpleDateFormat; 22 | 23 | import static com.github.dreamroute.sqlprinter.boot.domain.User.Gender.MALE; 24 | import static com.google.common.collect.Lists.newArrayList; 25 | import static org.junit.jupiter.api.Assertions.assertNotNull; 26 | import static org.junit.jupiter.api.Assertions.assertTrue; 27 | 28 | /** 29 | * @author w.dehai 30 | */ 31 | @SpringBootTest 32 | @EnableSQLPrinter(converters = {DateConverter.class, EnumConverter.class}) 33 | class UserMapperWithoutFormatTest { 34 | 35 | @Resource 36 | private UserMapper userMapper; 37 | @Resource 38 | private DataSource dataSource; 39 | @Resource 40 | private SqlprinterProperties sqlprinterProperties; 41 | 42 | private boolean format; 43 | 44 | @BeforeEach 45 | void beforeEach() { 46 | new DbSetup(new DataSourceDestination(dataSource), Operations.truncate("smart_user")).launch(); 47 | Insert insert = Operations.insertInto("smart_user") 48 | .columns("id", "name") 49 | .values(1L, "w.dehai") 50 | .values(2L, "Dreamroute").build(); 51 | new DbSetup(new DataSourceDestination(dataSource), insert).launch(); 52 | 53 | // 手动设置格式化 54 | format = sqlprinterProperties.isFormat(); 55 | sqlprinterProperties.setFormat(false); 56 | } 57 | 58 | @AfterEach 59 | void afterEach() { 60 | // 手动还原格式化参数 61 | sqlprinterProperties.setFormat(format); 62 | } 63 | 64 | @Test 65 | void insertTest() throws Exception { 66 | User user = new User(); 67 | user.setName("Jaedong"); 68 | user.setPassword("123456"); 69 | String time = "2020-01-01 01:01:10.111"; 70 | user.setBirthday(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse(time)); 71 | user.setGender(MALE); 72 | Appender appender = new Appender(SqlPrinter.class); 73 | userMapper.insert(user); 74 | assertNotNull(user.getId()); 75 | String sql = "insert into smart_user(birthday,password,gender,name,version) VALUES ('2020-01-01 01:01:10.111','123456',1,'Jaedong',null)"; 76 | assertTrue(appender.contains(sql)); 77 | } 78 | 79 | @Test 80 | void updateTest() { 81 | User user = userMapper.selectById(1L); 82 | user.setPassword("update"); 83 | Appender appender = new Appender(SqlPrinter.class); 84 | userMapper.updateById(user); 85 | userMapper.updateByIdExcludeNull(user); 86 | assertTrue(appender.contains("update smart_user set birthday = null,password = 'update',gender = null,name = 'w.dehai',version = 0 where id = 1")); 87 | assertTrue(appender.contains(1, "update smart_user set password = 'update',name = 'w.dehai',version = 0 where id = 1")); 88 | } 89 | 90 | @Test 91 | void selectUsersTest() { 92 | Appender appender = new Appender(SqlPrinter.class); 93 | userMapper.selectUsers(); 94 | String sql = "select * from smart_user"; 95 | assertTrue(appender.contains(sql)); 96 | } 97 | 98 | @Test 99 | void selectUserByIdsTest() { 100 | Appender appender = new Appender(SqlPrinter.class); 101 | userMapper.selectUserByIds(newArrayList(1L, 2L)); 102 | String sql = "select * from smart_user where id in\n" + 103 | " ( \n" + 104 | " (1)\n" + 105 | " , \n" + 106 | " (2)\n" + 107 | " )"; 108 | assertTrue(appender.contains(sql)); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /sqlprinter-samples/spring-boot/src/test/java/com/github/dreamroute/sqlprinter/boot/mapper/UserMapperWithFormatTest.java: -------------------------------------------------------------------------------- 1 | package com.github.dreamroute.sqlprinter.boot.mapper; 2 | 3 | import com.github.dreamroute.common.util.test.Appender; 4 | import com.github.dreamroute.sqlprinter.boot.domain.User; 5 | import com.github.dreamroute.sqlprinter.starter.anno.EnableSQLPrinter; 6 | import com.github.dreamroute.sqlprinter.starter.converter.def.DateConverter; 7 | import com.github.dreamroute.sqlprinter.starter.converter.def.EnumConverter; 8 | import com.github.dreamroute.sqlprinter.starter.interceptor.SqlPrinter; 9 | import com.ninja_squad.dbsetup.DbSetup; 10 | import com.ninja_squad.dbsetup.Operations; 11 | import com.ninja_squad.dbsetup.destination.DataSourceDestination; 12 | import com.ninja_squad.dbsetup.operation.Insert; 13 | import org.apache.ibatis.reflection.MetaObject; 14 | import org.apache.ibatis.reflection.SystemMetaObject; 15 | import org.junit.jupiter.api.BeforeEach; 16 | import org.junit.jupiter.api.Test; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | 19 | import javax.annotation.Resource; 20 | import javax.sql.DataSource; 21 | import java.text.SimpleDateFormat; 22 | import java.util.HashSet; 23 | 24 | import static com.github.dreamroute.sqlprinter.boot.domain.User.Gender.MALE; 25 | import static com.google.common.collect.Lists.newArrayList; 26 | import static org.junit.jupiter.api.Assertions.assertEquals; 27 | import static org.junit.jupiter.api.Assertions.assertNotNull; 28 | import static org.junit.jupiter.api.Assertions.assertTrue; 29 | 30 | /** 31 | * @author w.dehai 32 | */ 33 | @SpringBootTest 34 | @EnableSQLPrinter(converters = {DateConverter.class, EnumConverter.class}) 35 | class UserMapperWithFormatTest { 36 | 37 | @Resource 38 | private UserMapper userMapper; 39 | @Resource 40 | private DataSource dataSource; 41 | @Resource 42 | private SqlPrinter sqlPrinter; 43 | 44 | @BeforeEach 45 | void beforeEach() { 46 | new DbSetup(new DataSourceDestination(dataSource), Operations.truncate("smart_user")).launch(); 47 | Insert insert = Operations.insertInto("smart_user") 48 | .columns("id", "name") 49 | .values(1L, "w.dehai") 50 | .values(2L, "Dreamroute").build(); 51 | new DbSetup(new DataSourceDestination(dataSource), insert).launch(); 52 | } 53 | 54 | @Test 55 | void selectByIdTest() { 56 | User user = userMapper.selectById(1L); 57 | assertEquals("w.dehai", user.getName()); 58 | } 59 | 60 | @Test 61 | void insertTest() throws Exception { 62 | User user = new User(); 63 | user.setName("Jaedong"); 64 | user.setPassword("123456"); 65 | String time = "2020-01-01 01:01:10.111"; 66 | user.setBirthday(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").parse(time)); 67 | user.setGender(MALE); 68 | Appender appender = new Appender(SqlPrinter.class); 69 | userMapper.insert(user); 70 | assertNotNull(user.getId()); 71 | String sql = 72 | "INSERT INTO smart_user (birthday, password, gender, name, version)\n" + 73 | "VALUES ('2020-01-01 01:01:10.111', '123456', 1, 'Jaedong', NULL)"; 74 | assertTrue(appender.contains(sql)); 75 | } 76 | 77 | @Test 78 | void updateTest() { 79 | User user = userMapper.selectById(1L); 80 | user.setPassword("update"); 81 | Appender appender = new Appender(SqlPrinter.class); 82 | userMapper.updateById(user); 83 | userMapper.updateByIdExcludeNull(user); 84 | assertTrue(appender.contains( 85 | "UPDATE smart_user\n" + 86 | "SET birthday = NULL, password = 'update', gender = NULL, name = 'w.dehai', version = 0\n" + 87 | "WHERE id = 1")); 88 | assertTrue(appender.contains(1, 89 | "UPDATE smart_user\n" + 90 | "SET password = 'update', name = 'w.dehai', version = 0\n" + 91 | "WHERE id = 1")); 92 | } 93 | 94 | @Test 95 | void selectUsersTest() { 96 | Appender appender = new Appender(SqlPrinter.class); 97 | userMapper.selectUsers(); 98 | assertTrue(appender.contains( 99 | "SELECT *\n" + 100 | "FROM smart_user")); 101 | } 102 | 103 | @Test 104 | void selectUserByIdsTest() { 105 | Appender appender = new Appender(SqlPrinter.class); 106 | userMapper.selectUserByIds(newArrayList(1L, 2L)); 107 | String sql = 108 | "SELECT *\n" + 109 | "FROM smart_user\n" + 110 | "WHERE id IN (1, 2)"; 111 | assertTrue(appender.contains(sql)); 112 | } 113 | 114 | /** 115 | * filter手动设置成空 116 | */ 117 | @Test 118 | void filterNullTest() { 119 | MetaObject mo = SystemMetaObject.forObject(sqlPrinter); 120 | mo.setValue("filter", new HashSet<>()); 121 | Appender appender = new Appender(SqlPrinter.class); 122 | userMapper.selectById(1L, "id", "name"); 123 | String sql = "" + 124 | "SELECT id, name\n" + 125 | "FROM smart_user\n" + 126 | "WHERE id = 1"; 127 | assertTrue(appender.contains(sql)); 128 | } 129 | 130 | @Test 131 | void selectUserByNameTest() { 132 | User user = userMapper.selectUserByName("w.dehai"); 133 | assertEquals("w.dehai", user.getName()); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /sqlprinter-spring-boot-starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sqlprinter 8 | com.github.dreamroute 9 | ${revision} 10 | 11 | 12 | sqlprinter-spring-boot-starter 13 | ${project.artifactId} 14 | https://github.com/Dreamroute/sqlprinter 15 | print sql use real value replace '?' in sql fragment. 16 | 17 | 18 | MIT 19 | https://opensource.org/licenses/mit-license.php 20 | 21 | 22 | 23 | 24 | w.dehai 25 | 342252328@qq.com 26 | 27 | 28 | 29 | 30 | https://github.com/Dreamroute/sqlprinter 31 | 32 | 33 | 34 | 35 | org.mybatis 36 | mybatis 37 | true 38 | 39 | 40 | cn.hutool 41 | hutool-all 42 | 43 | 44 | com.github.dreamroute 45 | mybatis-pro-base 46 | 47 | 48 | jackson-databind 49 | com.fasterxml.jackson.core 50 | 51 | 52 | 53 | 54 | com.alibaba 55 | druid 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-test 60 | 61 | 62 | org.projectlombok 63 | lombok 64 | 65 | 66 | com.github.dreamroute 67 | common-util 68 | 69 | 70 | 71 | 72 | 73 | 74 | nexus 75 | 76 | 77 | 78 | org.apache.maven.plugins 79 | maven-deploy-plugin 80 | ${maven-deploy-plugin.version} 81 | 82 | false 83 | 84 | 85 | 86 | 87 | 88 | 89 | oss 90 | 91 | 92 | 93 | org.sonatype.central 94 | central-publishing-maven-plugin 95 | 0.6.0 96 | true 97 | 98 | central 99 | true 100 | published 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | org.apache.maven.plugins 114 | maven-source-plugin 115 | ${maven-source-plugin.version} 116 | 117 | 118 | attach-sources 119 | 120 | jar 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-javadoc-plugin 131 | ${maven-javadoc-plugin.version} 132 | 133 | UTF-8 134 | UTF-8 135 | UTF-8 136 | 137 | 138 | 139 | attach-javadocs 140 | 141 | jar 142 | 143 | 144 | -Xdoclint:none 145 | UTF-8 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | org.apache.maven.plugins 154 | maven-gpg-plugin 155 | ${maven-gpg-plugin.version} 156 | 157 | 158 | sign-artifacts 159 | verify 160 | 161 | sign 162 | 163 | 164 | 165 | 166 | 167 | --batch 168 | --no-tty 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | sqlprinter-spring-boot-starter 8 | sqlprinter-samples 9 | 10 | 11 | 12 | org.springframework.boot 13 | spring-boot-starter-parent 14 | 2.7.10 15 | 16 | 17 | com.github.dreamroute 18 | sqlprinter 19 | ${revision} 20 | pom 21 | 22 | 23 | 24 | 2.3.23-RELEASE 25 | 26 | 27 | https://maven.aliyun.com/repository/public 28 | https://maven.aliyun.com/repository/public 29 | http://nexus.yzw.cn/repository/maven-releases 30 | http://nexus.yzw.cn/repository/maven-snapshots 31 | https://oss.sonatype.org/content/repositories/snapshots 32 | https://oss.sonatype.org/content/repositories/snapshots 33 | 34 | 35 | 1.8 36 | UTF-8 37 | ${java.version} 38 | ${java.version} 39 | ${java.version} 40 | ${maven.compiler.encoding} 41 | ${project.build.sourceEncoding} 42 | 43 | 44 | 1.2.16 45 | 3.5.7 46 | 31.0.1-jre 47 | 1.2.83 48 | 5.8.20 49 | 1.33 50 | 1.6.7 51 | 1.2.6 52 | 3.11 53 | 54 | 55 | 1.6 56 | 3.2.1 57 | 3.0.0 58 | 2.8.2 59 | 3.2.0 60 | 1.2.4 61 | 3.8.1 62 | 1.18.16.0 63 | 2.22.2 64 | 1.6.8 65 | 66 | 67 | 68 | 69 | 70 | com.h2database 71 | h2 72 | 1.4.200 73 | test 74 | 75 | 76 | org.apache.commons 77 | commons-lang3 78 | ${commons-lang3.version} 79 | 80 | 81 | com.github.dreamroute 82 | common-util 83 | ${common-util.version} 84 | 85 | 86 | com.alibaba 87 | druid 88 | ${druid.version} 89 | 90 | 91 | org.mybatis 92 | mybatis 93 | ${mybatis.version} 94 | true 95 | 96 | 97 | com.github.dreamroute 98 | mybatis-pro-base 99 | ${mybatis-pro.version} 100 | true 101 | 102 | 103 | cn.hutool 104 | hutool-all 105 | ${hutool-all.version} 106 | 107 | 108 | 109 | 110 | 111 | 112 | nexus 113 | 114 | true 115 | 116 | 117 | 118 | nexus 119 | ${repository.nexus.url} 120 | 121 | true 122 | 123 | 124 | true 125 | always 126 | 127 | 128 | 129 | 130 | 131 | nexus 132 | ${repository.nexus.url} 133 | 134 | true 135 | 136 | 137 | true 138 | always 139 | 140 | 141 | 142 | 143 | 144 | nexus 145 | ${dist.nexus.release} 146 | 147 | 148 | nexus 149 | ${dist.nexus.snapshot} 150 | 151 | 152 | 153 | 154 | oss 155 | 156 | 157 | nexus 158 | ${repository.oss.url} 159 | 160 | true 161 | 162 | 163 | true 164 | always 165 | 166 | 167 | 168 | 169 | 170 | nexus 171 | ${repository.oss.url} 172 | 173 | true 174 | 175 | 176 | true 177 | always 178 | 179 | 180 | 181 | 182 | 183 | oss 184 | ${dist.oss.release} 185 | 186 | 187 | oss 188 | ${dist.oss.snapshot} 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | org.apache.maven.plugins 199 | maven-compiler-plugin 200 | ${maven-compiler-plugin.version} 201 | 202 | 203 | 204 | 205 | org.apache.maven.plugins 206 | maven-surefire-plugin 207 | ${maven-surefire-plugin.version} 208 | 209 | -Dfile.encoding=UTF-8 210 | true 211 | 212 | 213 | 214 | 215 | 216 | org.codehaus.mojo 217 | flatten-maven-plugin 218 | ${flatten-maven-plugin.version} 219 | 220 | true 221 | bom 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | flatten 231 | process-resources 232 | 233 | flatten 234 | 235 | 236 | 237 | flatten.clean 238 | clean 239 | 240 | clean 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | org.projectlombok 249 | lombok-maven-plugin 250 | ${lombok-maven-plugin.version} 251 | 252 | true 253 | 254 | 255 | 256 | 257 | 258 | org.apache.maven.plugins 259 | maven-deploy-plugin 260 | ${maven-deploy-plugin.version} 261 | 262 | true 263 | 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /sqlprinter-spring-boot-starter/src/main/java/com/github/dreamroute/sqlprinter/starter/interceptor/SqlPrinter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2016 342252328@qq.com 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | package com.github.dreamroute.sqlprinter.starter.interceptor; 25 | 26 | import cn.hutool.core.collection.CollUtil; 27 | import cn.hutool.core.text.CharSequenceUtil; 28 | import cn.hutool.core.util.ArrayUtil; 29 | import cn.hutool.core.util.ReflectUtil; 30 | import cn.hutool.core.util.StrUtil; 31 | import com.alibaba.druid.sql.SQLUtils; 32 | import com.github.dreamroute.sqlprinter.starter.anno.SqlprinterProperties; 33 | import com.github.dreamroute.sqlprinter.starter.anno.ValueConverter; 34 | import lombok.extern.slf4j.Slf4j; 35 | import org.apache.ibatis.executor.parameter.ParameterHandler; 36 | import org.apache.ibatis.executor.resultset.ResultSetHandler; 37 | import org.apache.ibatis.mapping.*; 38 | import org.apache.ibatis.plugin.Interceptor; 39 | import org.apache.ibatis.plugin.Intercepts; 40 | import org.apache.ibatis.plugin.Invocation; 41 | import org.apache.ibatis.plugin.Signature; 42 | import org.apache.ibatis.reflection.MetaObject; 43 | import org.apache.ibatis.session.Configuration; 44 | import org.apache.ibatis.session.SqlSessionFactory; 45 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 46 | import org.springframework.context.ApplicationListener; 47 | import org.springframework.context.event.ContextRefreshedEvent; 48 | 49 | import java.lang.reflect.Field; 50 | import java.sql.PreparedStatement; 51 | import java.sql.Statement; 52 | import java.util.*; 53 | 54 | import static java.util.Optional.ofNullable; 55 | 56 | /** 57 | * print simple sql 58 | * 59 | * @author 342252328@qq.com.2016-06-14 60 | * @version 1.0 61 | * @since JDK1.8 62 | */ 63 | @Slf4j 64 | @EnableConfigurationProperties(SqlprinterProperties.class) 65 | @Intercepts( 66 | { 67 | @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}), 68 | @Signature(type = ParameterHandler.class, method = "setParameters", args = {PreparedStatement.class}) 69 | } 70 | ) 71 | public class SqlPrinter implements Interceptor, ApplicationListener { 72 | 73 | private final SqlprinterProperties sqlprinterProperties; 74 | private final List converters; 75 | private final Set filter; 76 | private final boolean show; 77 | private final boolean showResult; 78 | private final String[] showResultExclude; 79 | 80 | private Configuration config; 81 | 82 | public SqlPrinter(SqlprinterProperties props, List converters) { 83 | this.sqlprinterProperties = props; 84 | this.converters = converters; 85 | filter = new HashSet<>(Arrays.asList(ofNullable(props.getFilter()).orElseGet(() -> new String[0]))); 86 | this.show = props.isShow(); 87 | this.showResult = props.isShowResult(); 88 | this.showResultExclude = props.getShowResultExclude(); 89 | } 90 | 91 | @Override 92 | public void onApplicationEvent(ContextRefreshedEvent event) { 93 | SqlSessionFactory sqlSessionFactory = event.getApplicationContext().getBean(SqlSessionFactory.class); 94 | this.config = sqlSessionFactory.getConfiguration(); 95 | } 96 | 97 | @Override 98 | public Object intercept(Invocation invocation) throws Exception { 99 | 100 | // 调用原始方法 101 | Object result = invocation.proceed(); 102 | 103 | try { 104 | Object target = invocation.getTarget(); 105 | Parse p = new Parse(); 106 | // 非查询走这里(非查询不走ResultSetHandler接口,所以放在这里, 直接打印sql) 107 | if (target instanceof ParameterHandler) { 108 | MetaObject m = config.newMetaObject(target); 109 | MappedStatement ms = (MappedStatement) m.getValue("mappedStatement"); 110 | SqlCommandType sqlCommandType = ms.getSqlCommandType(); 111 | if (sqlCommandType != SqlCommandType.SELECT) { 112 | p.id = ms.getId(); 113 | processSql(ms.getId(), ms.getBoundSql(m.getValue("parameterObject")), p); 114 | } 115 | // 对于((PreparedStatement) countStmt).execute()这种方式执行的sql, 不会走ResultSetHandler拦截, 所以不会走下方的查询逻辑, 因此打印sql放在这里, 而分页插件就包含((PreparedStatement) countStmt).execute()这种查询 116 | else if(ms.getId().contains("分页统计")) { 117 | p = getSql(invocation); 118 | } 119 | } 120 | 121 | // 查询走这里, 这里会生成sql 122 | else { 123 | p = getSql(invocation); 124 | } 125 | 126 | if (CharSequenceUtil.isNotBlank(p.sql)) { 127 | printResult(result, p); 128 | } 129 | } catch (Exception e) { 130 | // print sql is not important, so ignore it. 131 | } 132 | 133 | return result; 134 | } 135 | 136 | private Parse getSql(Invocation invocation) { 137 | 138 | Parse parse = new Parse(); 139 | 140 | MetaObject resultSetHandler = config.newMetaObject(invocation.getTarget()); 141 | MappedStatement mappedStatement = (MappedStatement) resultSetHandler.getValue("mappedStatement"); 142 | String id = mappedStatement.getId(); 143 | parse.id = id; 144 | BoundSql boundSql = (BoundSql) resultSetHandler.getValue("boundSql"); 145 | processSql(id, boundSql, parse); 146 | return parse; 147 | } 148 | 149 | private void processSql(String id, BoundSql boundSql, Parse parse) { 150 | if (show && !filter.contains(id)) { 151 | 152 | Object parameterObject = boundSql.getParameterObject(); 153 | String originalSql = boundSql.getSql(); 154 | StringBuilder sb = new StringBuilder(originalSql); 155 | List parameterMappings = boundSql.getParameterMappings(); 156 | if (parameterMappings != null) { 157 | for (ParameterMapping parameterMapping : parameterMappings) { 158 | if (parameterMapping.getMode() != ParameterMode.OUT) { 159 | Object value; 160 | String propertyName = parameterMapping.getProperty(); 161 | if (boundSql.hasAdditionalParameter(propertyName)) { 162 | value = boundSql.getAdditionalParameter(propertyName); 163 | } else if (parameterObject == null) { 164 | value = null; 165 | } else if (config.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass())) { 166 | value = parameterObject; 167 | } else { 168 | MetaObject metaObject = config.newMetaObject(parameterObject); 169 | value = metaObject.getValue(propertyName); 170 | } 171 | 172 | // 转换器 173 | for (ValueConverter vc : converters) { 174 | value = vc.convert(value); 175 | } 176 | 177 | // sql中非数字类型和boolean类型的值加单引号 178 | if (value != null && !(value instanceof Number) && !(value instanceof Boolean)) { 179 | value = "'" + value + "'"; 180 | } 181 | 182 | // 替换问号 183 | int pos = sb.indexOf("?"); 184 | sb.replace(pos, pos + 1, String.valueOf(value)); 185 | } 186 | } 187 | 188 | String info = sb.toString(); 189 | if (sqlprinterProperties.isFormat()) { 190 | info = format(info); 191 | } 192 | 193 | parse.sql = info; 194 | } 195 | } 196 | } 197 | 198 | private void printResult(Object result, Parse parse) { 199 | 200 | String resp = "\r\n\r\n"; 201 | resp += "==> " + parse.id + "\r\n"; 202 | resp += parse.sql + "\r\n"; 203 | 204 | String[] columnNames = null; 205 | List data = null; 206 | if (showResult) { 207 | if (result instanceof List && CollUtil.isNotEmpty((Collection) result)) { 208 | Field[] fields = ReflectUtil.getFields(((List) result).get(0).getClass()); 209 | fields = filterExclude(fields); 210 | if (fields != null && fields.length > 0) { 211 | columnNames = generateColumnNames(fields); 212 | data = new ArrayList<>(((List) result).size()); 213 | for (int i = 0; i < ((List) result).size(); i++) { 214 | String[] d = new String[fields.length]; 215 | for (int j = 0; j < fields.length; j++) { 216 | Object v = ReflectUtil.getFieldValue(((List) result).get(i), fields[j]); 217 | for (ValueConverter vc : converters) { 218 | v = vc.convert(v); 219 | } 220 | d[j] = StrUtil.toString(v); 221 | } 222 | data.add(d); 223 | } 224 | } 225 | } else if (!(result instanceof List) && result != null) { 226 | Field[] fields = ReflectUtil.getFields(result.getClass()); 227 | fields = filterExclude(fields); 228 | columnNames = generateColumnNames(fields); 229 | data = new ArrayList<>(1); 230 | for (int i = 0; i < fields.length; i++) { 231 | String[] d = new String[fields.length]; 232 | Object v = ReflectUtil.getFieldValue(result, fields[i]); 233 | for (ValueConverter vc : converters) { 234 | v = vc.convert(v); 235 | } 236 | d[i] = StrUtil.toString(v); 237 | data.add(d); 238 | } 239 | } 240 | } 241 | 242 | if (columnNames != null && columnNames.length > 0 && CollUtil.isNotEmpty(data)) { 243 | PrettyTable table = new PrettyTable(columnNames); 244 | data.forEach(table::addRow); 245 | resp += table; 246 | } 247 | log.info(resp); 248 | } 249 | 250 | private Field[] filterExclude(Field[] fields) { 251 | if (showResultExclude != null && showResultExclude.length > 0) { 252 | return Arrays.stream(fields).filter(field -> !ArrayUtil.contains(showResultExclude, field.getName())).toArray(Field[]::new); 253 | } 254 | return fields; 255 | } 256 | 257 | private static String[] generateColumnNames(Field[] fields) { 258 | String[] columnNames = new String[fields.length]; 259 | for (int i = 0; i < fields.length; i++) { 260 | boolean accessible = fields[i].isAccessible(); 261 | fields[i].setAccessible(true); 262 | columnNames[i] = fields[i].getName(); 263 | fields[i].setAccessible(accessible); 264 | } 265 | return columnNames; 266 | } 267 | 268 | /** 269 | * 使用druid格式化sql,如果格式化失败,那么返回未经过格式化的sql,增加此格式化的原因是因为: 270 | * 1. 为了美观和打印的格式统一 271 | * 2. mysql的xml文件编写的sql带有动态标签,如果动态标签不满足条件时sql会有很多多余的换行和缩进 272 | * 273 | * @param sql 需要格式化的sql 274 | * @return 返回格式化之后的sql 275 | */ 276 | private String format(String sql) { 277 | try { 278 | // 此格式化在不改变sql语义的情况下会移除一些括号,比如: 279 | // 格式化前:select * from xx where id = (#{id} and name = #{name}) and pwd = #{pwd} 280 | // 格式化后:select * from xx where id = #{id} and name = #{name} and pwd = #{pwd}(括号被移除) 281 | // 但是如果移除括号会改变sql语义,那就不会被移除 282 | return SQLUtils.formatMySql(sql); 283 | } catch (Exception e) { 284 | return sql; 285 | } 286 | } 287 | 288 | private static class Parse { 289 | String sql; 290 | String id; 291 | } 292 | } --------------------------------------------------------------------------------