文章目录
- 1.引入依赖
- 1.sun-dependencies 指定依赖
- 2.将sun-dependencies进行install
- 3.sun-common-log引入依赖
- 2.sun-common-log代码实现
- 1.LogAspect.java(需要更改包时就修改Pointcut的切点表达式即可)
- 2.log4j2-spring.xml
- 3.效果展示
- 3.SQL优化器升级
- 1.目录
- 2.SqlBeautyInterceptor.java
- 3.MybatisConfiguration.java SQL优化器注入Bean容器
- 4.application.yml开启sql优化器
- 5.效果展示
- 4.遇到的bug
- 1.mapstruct失效(在build内配置annotationProcessorPaths)
1.引入依赖
1.sun-dependencies 指定依赖
<spring.aop.version>2.4.2</spring.aop.version><gson.version>2.8.6</gson.version>
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>${spring.aop.version}</version></dependency><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>${gson.version}</version></dependency>
2.将sun-dependencies进行install
3.sun-common-log引入依赖
<!-- gson序列化 --><dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId></dependency><!-- aop --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
2.sun-common-log代码实现
1.LogAspect.java(需要更改包时就修改Pointcut的切点表达式即可)
package com.sunxiansheng.log;import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;@Aspect
@Slf4j
@Component
@ConditionalOnProperty(name = "log.aspect.enable", havingValue = "true", matchIfMissing = true)
public class LogAspect {private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();private static final String ANSI_RESET = "\u001B[0m";private static final String ANSI_BLUE = "\u001B[34m";private static final String ANSI_YELLOW = "\u001B[33m";private static final String ANSI_GREEN = "\u001B[32m";private static final String ANSI_PURPLE = "\u001B[35m";/*** 配置切点,切Controller和Service的所有方法*/@Pointcut("execution(* com.sunxiansheng.*.controller.*Controller.*(..)) || execution(* com.sunxiansheng.*.service.*Service.*(..))")private void pointCut() {}/*** 环绕通知* @param pjp*/@Around("pointCut()")public Object around(ProceedingJoinPoint pjp) throws Throwable {// 获取参数Object[] args = pjp.getArgs();// 序列化为json字符串String req = GSON.toJson(args);// 获取方法签名MethodSignature methodSignature = (MethodSignature) pjp.getSignature();// 获取类名+方法名String className = methodSignature.getDeclaringType().getName();String methodName = methodSignature.getName();// 获取线程信息和时间戳String threadName = Thread.currentThread().getName();long threadId = Thread.currentThread().getId();String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));// 打印:方法名,请求参数,线程信息,时间戳log.info("\n========================\n" +"时间戳:{}{}{}\n" +"线程:{}{} (ID: {}){}\n" +"方法名:{}{}{}\n" +"请求参数:{}{}{}\n" +"========================",ANSI_GREEN, timestamp, ANSI_RESET,ANSI_PURPLE, threadName, threadId, ANSI_RESET,ANSI_BLUE, className + "." + methodName, ANSI_RESET,ANSI_YELLOW, req, ANSI_RESET);long startTime = System.currentTimeMillis();Object proceed;try {// 执行目标方法proceed = pjp.proceed();} catch (Throwable throwable) {// 捕获并记录异常log.error("\n========================\n" +"时间戳:{}{}{}\n" +"线程:{}{} (ID: {}){}\n" +"方法名:{}{}{}\n" +"执行异常:{}{}{}\n" +"堆栈信息:{}{}\n" +"========================",ANSI_GREEN, timestamp, ANSI_RESET,ANSI_PURPLE, threadName, threadId, ANSI_RESET,ANSI_BLUE, className + "." + methodName, ANSI_RESET,ANSI_YELLOW, throwable.getMessage(), ANSI_RESET, throwable,ANSI_RESET);throw throwable; // 重新抛出异常}// 拿到出参String resp = GSON.toJson(proceed).replace("\n", "\n ");long endTime = System.currentTimeMillis();// 打印响应参数和耗时log.info("\n========================\n" +"时间戳:{}{}{}\n" +"线程:{}{} (ID: {}){}\n" +"方法名:{}{}{}\n" +"响应参数:{}{}{}\n" +"耗时:{}{}{} ms\n" +"========================",ANSI_GREEN, timestamp, ANSI_RESET,ANSI_PURPLE, threadName, threadId, ANSI_RESET,ANSI_BLUE, className + "." + methodName, ANSI_RESET,ANSI_YELLOW, "\n " + resp, ANSI_RESET,ANSI_GREEN, endTime - startTime, ANSI_RESET);// 返回目标方法的执行结果return proceed;}
}
2.log4j2-spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出 -->
<!-- monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数 -->
<configuration monitorInterval="5"><!-- 日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --><!-- 变量配置 --><Properties><!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符 --><!-- %logger{36} 表示 Logger 名字最长36个字符 --><property name="LOG_PATTERN"value="%clr{%d{yyyy-MM-dd HH:mm:ss.SSS}}{faint} %clr{%5p} %clr{${sys:PID}}{magenta} %clr{---}{faint} %clr{[%15.15t]}{faint} %clr{%c{1.}.%M(%L)}{cyan} %clr{:}{faint} %m%n%xwEx" /><!-- 定义日志存储的路径,不要配置相对路径 --><property name="FILE_PATH" value="./logs" /><property name="FILE_NAME" value="sun-user-log" /></Properties><appenders><console name="Console" target="SYSTEM_OUT"><!-- 输出日志的格式 --><PatternLayout pattern="${LOG_PATTERN}" /><!-- 控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch) --><ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY" /></console><!-- 文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用 --><File name="Filelog" fileName="${FILE_PATH}/test.log" append="false"><PatternLayout pattern="${LOG_PATTERN}" /></File><!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 --><RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log"filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz"><!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) --><ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY" /><PatternLayout pattern="${LOG_PATTERN}" /><Policies><!-- interval属性用来指定多久滚动一次,默认是1 hour --><TimeBasedTriggeringPolicy interval="1" /><SizeBasedTriggeringPolicy size="10MB" /></Policies><!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖 --><DefaultRolloverStrategy max="15" /></RollingFile><!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 --><RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log"filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz"><!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) --><ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" /><PatternLayout pattern="${LOG_PATTERN}" /><Policies><!-- interval属性用来指定多久滚动一次,默认是1 hour --><TimeBasedTriggeringPolicy interval="1" /><SizeBasedTriggeringPolicy size="10MB" /></Policies><!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖 --><DefaultRolloverStrategy max="15" /></RollingFile><!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档 --><RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log"filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz"><!-- 控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) --><ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY" /><PatternLayout pattern="${LOG_PATTERN}" /><Policies><!-- interval属性用来指定多久滚动一次,默认是1 hour --><TimeBasedTriggeringPolicy interval="1" /><SizeBasedTriggeringPolicy size="10MB" /></Policies><!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖 --><DefaultRolloverStrategy max="15" /></RollingFile></appenders><!-- Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等 --><!-- 然后定义loggers,只有定义了logger并引入的appender,appender才会生效 --><loggers><!-- 过滤掉spring和mybatis的一些无用的DEBUG信息 --><logger name="org.mybatis" level="info" additivity="false"><AppenderRef ref="Console" /></logger><!-- 监控系统信息 --><!-- 若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出 --><Logger name="org.springframework" level="info" additivity="false"><AppenderRef ref="Console" /></Logger><!-- 异步日志 --><AsyncLogger name="com.sunxiansheng" level="info" additivity="false"><AppenderRef ref="RollingFileInfo" /><AppenderRef ref="Console" /></AsyncLogger><AsyncRoot level="info"><AppenderRef ref="Console" /><AppenderRef ref="Filelog" /><AppenderRef ref="RollingFileInfo" /><AppenderRef ref="RollingFileWarn" /><AppenderRef ref="RollingFileError" /></AsyncRoot><!-- 配置异步日志时注释掉这个 --><!-- <root level="info"> --><!-- <appender-ref ref="Console" /> --><!-- <appender-ref ref="Filelog" /> --><!-- <appender-ref ref="RollingFileInfo" /> --><!-- <appender-ref ref="RollingFileWarn" /> --><!-- <appender-ref ref="RollingFileError" /> --><!-- </root> --></loggers>
</configuration>
3.效果展示
3.SQL优化器升级
1.目录
2.SqlBeautyInterceptor.java
package com.sunxiansheng.inteceptor;import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.defaults.DefaultSqlSession.StrictMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.lang.reflect.Field;
import java.sql.Statement;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;/*** SQL优化器:显示完整的SQL*/
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})
})
public class SqlBeautyInterceptor implements Interceptor {private static final Logger logger = LoggerFactory.getLogger(SqlBeautyInterceptor.class);private static final Set<Class<?>> PRIMITIVE_WRAPPER_CLASSES = new HashSet<>(Arrays.asList(Byte.class, Short.class, Integer.class, Long.class, Double.class, Float.class, Character.class, Boolean.class));private static final String ANSI_RESET = "\u001B[0m";private static final String ANSI_YELLOW = "\u001B[33m";private static final String ANSI_GREEN = "\u001B[32m";private static final String ANSI_BLUE = "\u001B[34m";private static final String ANSI_RED = "\u001B[31m";@Overridepublic Object intercept(Invocation invocation) throws Throwable {StatementHandler statementHandler = (StatementHandler) invocation.getTarget();MetaObject metaObject = SystemMetaObject.forObject(statementHandler);MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");String mapperMethod = mappedStatement.getId();long startTime = System.currentTimeMillis();String threadName = Thread.currentThread().getName();long threadId = Thread.currentThread().getId();String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));try {return invocation.proceed();} catch (Throwable throwable) {logger.error("SQL execution error: ", throwable);throw throwable;} finally {long endTime = System.currentTimeMillis();long sqlCost = endTime - startTime;BoundSql boundSql = statementHandler.getBoundSql();String sql = boundSql.getSql();Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();String formattedSql = sql;try {formattedSql = formatSql(sql, parameterObject, parameterMappingList);formattedSql = beautifySql(formattedSql);} catch (Exception e) {logger.error("Error formatting SQL: ", e);formattedSql = formatSql(sql, parameterObject, parameterMappingList); // 如果格式化失败,返回未格式化但填充了数据的SQL}logger.info("\n========================\n线程信息:{}{} (ID: {}){}\n时间戳:{}{}{}\n堆栈信息:{}\nMapper方法:{}{}{}\nSQL:\n{}{}{}\n执行耗时: {}{}{} ms\n========================",ANSI_RED, threadName, threadId, ANSI_RESET,ANSI_GREEN, timestamp, ANSI_RESET,Arrays.toString(Thread.currentThread().getStackTrace()),ANSI_BLUE, mapperMethod, ANSI_RESET, ANSI_YELLOW, formattedSql, ANSI_RESET, ANSI_GREEN, sqlCost, ANSI_RESET);}}private String getMapperMethod(StatementHandler statementHandler) {try {Field mappedStatementField = statementHandler.getClass().getDeclaredField("mappedStatement");mappedStatementField.setAccessible(true);MappedStatement mappedStatement = (MappedStatement) mappedStatementField.get(statementHandler);return mappedStatement.getId();} catch (Exception e) {logger.error("Error getting mapper method: ", e);return "Unknown method";}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {}private String formatSql(String sql, Object parameterObject, List<ParameterMapping> parameterMappingList) {if (sql == null || sql.trim().isEmpty()) {return "";}if (parameterObject == null || parameterMappingList == null || parameterMappingList.isEmpty()) {return sql;}String sqlWithoutReplacePlaceholder = sql;try {if (isStrictMap(parameterObject.getClass())) {StrictMap<?> strictMap = (StrictMap<?>) parameterObject;if (isList(strictMap.get("list").getClass())) {sql = handleListParameter(sql, (List<?>) strictMap.get("list"));}} else if (isMap(parameterObject.getClass())) {sql = handleMapParameter(sql, (Map<?, ?>) parameterObject, parameterMappingList);} else {sql = handleCommonParameter(sql, parameterMappingList, parameterObject);}} catch (Exception e) {logger.error("Error formatting SQL: ", e);return sqlWithoutReplacePlaceholder; // 返回未格式化但填充了数据的SQL}return sql;}private String handleCommonParameter(String sql, List<ParameterMapping> parameterMappingList, Object parameterObject) throws Exception {Class<?> parameterObjectClass = parameterObject.getClass();List<Field> allFields = new ArrayList<>();while (parameterObjectClass != null) {allFields.addAll(Arrays.asList(parameterObjectClass.getDeclaredFields()));parameterObjectClass = parameterObjectClass.getSuperclass();}for (ParameterMapping parameterMapping : parameterMappingList) {String propertyValue = null;String propertyName = parameterMapping.getProperty();Field field = allFields.stream().filter(f -> f.getName().equals(propertyName)).findFirst().orElse(null);if (field != null) {field.setAccessible(true);Object value = field.get(parameterObject);propertyValue = formatParameterValue(value);logger.debug("Parameter name: {}, value: {}, type: {}", propertyName, propertyValue, value != null ? value.getClass().getSimpleName() : "null");} else if (isPrimitiveOrPrimitiveWrapper(parameterObject.getClass())) {propertyValue = parameterObject.toString();logger.debug("Primitive parameter: {}, value: {}", propertyName, propertyValue);}if (propertyValue != null) {sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(propertyValue));}}return sql;}private String formatParameterValue(Object value) {if (value == null) {return "null";} else if (value instanceof String) {return "\"" + value + "\"";} else if (value instanceof LocalDateTime) {return "\"" + value.toString() + "\"";} else if (value instanceof Date) {return "\"" + new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(value) + "\"";} else if (value instanceof Boolean) {return (Boolean) value ? "1" : "0";} else if (value instanceof Number) {return value.toString();} else {return value.toString();}}private String handleMapParameter(String sql, Map<?, ?> paramMap, List<ParameterMapping> parameterMappingList) {for (ParameterMapping parameterMapping : parameterMappingList) {String propertyName = parameterMapping.getProperty();Object propertyValue = getNestedParamValue(paramMap, propertyName);if (propertyValue == null) {logger.warn("Parameter '{}' not found in parameter map. Available parameters: {}", propertyName, paramMap.keySet());continue;}String formattedValue = formatParameterValue(propertyValue);logger.debug("Parameter name: {}, value: {}, type: {}", propertyName, formattedValue, propertyValue.getClass().getSimpleName());sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(formattedValue));}return sql;}private Object getNestedParamValue(Map<?, ?> paramMap, String propertyName) {if (propertyName.contains(".")) {String[] nestedProperties = propertyName.split("\\.");Object value = paramMap;for (String prop : nestedProperties) {if (value instanceof Map) {value = ((Map<?, ?>) value).get(prop);} else {// 如果不是Map,尝试通过反射获取属性值try {Field field = value.getClass().getDeclaredField(prop);field.setAccessible(true);value = field.get(value);} catch (NoSuchFieldException | IllegalAccessException e) {logger.error("Error accessing nested property '{}' on '{}'", prop, value.getClass().getName(), e);return null;}}if (value == null) {return null;}}return value;} else {return paramMap.get(propertyName);}}private String handleListParameter(String sql, Collection<?> col) {if (col != null && !col.isEmpty()) {for (Object obj : col) {String value = obj.toString();if (obj.getClass().isAssignableFrom(String.class)) {value = "\"" + value + "\"";}sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(value));}}return sql;}private String beautifySql(String sql) {// Remove excess whitespacesql = sql.replaceAll("[\\s\n]+", " ").trim();// Format common SQL keywords and clausessql = sql.replaceAll("(?i)\\b(SELECT|INSERT INTO|UPDATE|DELETE FROM|FROM|WHERE|SET|VALUES|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|OFFSET|UNION|UNION ALL|JOIN|ON|USING|DISTINCT|EXISTS|IN|IS NULL|IS NOT NULL|NOT IN|LIKE|BETWEEN|AND|OR|CASE|WHEN|THEN|ELSE|END)\\b", "\n$1");// Handle INSERT INTO and VALUES statements and parenthesis formattingsql = sql.replaceAll("(?i)\\b(INSERT INTO [^\\(]+\\()", "\n$1\n ");sql = sql.replaceAll("(?i)\\b(VALUES)\\s*\\(", "\n$1\n (");sql = sql.replaceFirst("\\)\\s*VALUES", "\n)\nVALUES");// Handle subqueries and nested queriessql = sql.replaceAll("(?i)\\b(SELECT)\\b", "\n$1");sql = sql.replaceAll("(?i)\\b(FROM)\\b", "\n$1");sql = sql.replaceAll("(?i)\\b(WHERE)\\b", "\n$1");sql = sql.replaceAll("(?i)\\b(GROUP BY)\\b", "\n$1");sql = sql.replaceAll("(?i)\\b(ORDER BY)\\b", "\n$1");// Handle indentation for subqueriessql = sql.replaceAll("\\(\\s*SELECT", "(\n SELECT");sql = sql.replaceAll("\\)\\s*\\b", ")");// Split the SQL into lines and formatString[] lines = sql.split("\n");StringBuilder formattedSql = new StringBuilder();int indentLevel = 0;for (String line : lines) {if (line.matches("(?i)^\\s*(SELECT|INSERT INTO|UPDATE|DELETE FROM|FROM|WHERE|SET|VALUES|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|OFFSET|UNION|UNION ALL|JOIN|ON|USING|DISTINCT|EXISTS|IN|IS NULL|IS NOT NULL|NOT IN|LIKE|BETWEEN|AND|OR|CASE|WHEN|THEN|ELSE|END)\\b.*")) {if (line.matches("(?i)^\\s*(FROM|WHERE|SET|VALUES|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|OFFSET|UNION|UNION ALL|JOIN|ON|USING|DISTINCT|EXISTS|IN|IS NULL|IS NOT NULL|NOT IN|LIKE|BETWEEN|AND|OR|CASE|WHEN|THEN|ELSE|END)\\b.*")) {indentLevel--;}formattedSql.append(repeat(" ", indentLevel)).append(line.trim()).append("\n");if (line.matches("(?i)^\\s*(SELECT|INSERT INTO|UPDATE|DELETE FROM)\\b.*")) {indentLevel++;}} else {formattedSql.append(repeat(" ", indentLevel)).append(line.trim()).append("\n");}}// Special handling for closing parenthesisformattedSql = new StringBuilder(formattedSql.toString().replace(") VALUES", ") VALUES"));formattedSql = new StringBuilder(formattedSql.toString().replaceAll("\\),", "),"));// Remove trailing spaces and tabsreturn formattedSql.toString().replaceAll("\n{2,}", "\n").trim();}private String repeat(String str, int count) {StringBuilder result = new StringBuilder();for (int i = 0; i < count; i++) {result.append(str);}return result.toString();}private boolean isPrimitiveOrPrimitiveWrapper(Class<?> clazz) {return clazz.isPrimitive() || PRIMITIVE_WRAPPER_CLASSES.contains(clazz);}private boolean isStrictMap(Class<?> clazz) {return StrictMap.class.isAssignableFrom(clazz);}private boolean isList(Class<?> clazz) {return List.class.isAssignableFrom(clazz);}private boolean isMap(Class<?> clazz) {return Map.class.isAssignableFrom(clazz);}
}
3.MybatisConfiguration.java SQL优化器注入Bean容器
package com.sunxiansheng.config;import com.sunxiansheng.inteceptor.SqlBeautyInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** MyBatis配置类:用于将Bean注入容器*/
@Configuration
public class MybatisConfiguration {// SQL优化器注入Bean容器@Bean// 只有当 sql.beauty.show 的值为 "true" 时,相关的配置或 bean 才会被注册。// matchIfMissing: 如果设置为 true,这意味着如果在配置中没有找到 name 指定的属性,则条件视为匹配。@ConditionalOnProperty(name = {"sql.beauty.show"}, havingValue = "true", matchIfMissing = true)public SqlBeautyInterceptor sqlBeautyInterceptor() {return new SqlBeautyInterceptor();}}
4.application.yml开启sql优化器
# 自定义sql优化器
sql:beauty:show: true # 配置true或者不配置,就是启用sql优化器,配置其他的都是不启用
5.效果展示
4.遇到的bug
1.mapstruct失效(在build内配置annotationProcessorPaths)
<plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>1.8</source><target>1.8</target><annotationProcessorPaths><!-- Lombok 注解处理器 --><path><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>${lombok.version}</version></path><!-- MapStruct 注解处理器 --><path><groupId>org.mapstruct</groupId><artifactId>mapstruct-processor</artifactId><version>${mapstruct.version}</version></path></annotationProcessorPaths></configuration></plugin></plugins>