AOP实现操作日志记录+SQL优化器升级

文章目录

    • 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>

CleanShot 2024-07-21 at 10.11.27@2x

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.效果展示

CleanShot 2024-07-21 at 13.18.32@2x

3.SQL优化器升级

1.目录

CleanShot 2024-07-21 at 13.26.27@2x

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.效果展示

CleanShot 2024-07-21 at 13.57.11@2x

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>

CleanShot 2024-07-21 at 14.26.46@2x

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/64362.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CH582F BLE5.3 蓝牙核心板开发板 60MHz RAM:32KB ROM:448KB

CH582F BLE5.3 蓝牙核心板开发板 60MHz RAM:32KB ROM:448KB 是一款基于南京沁恒&#xff08;WCH&#xff09;推出的高性能、低功耗无线通信芯片CH582F的开发板。以下是该开发板的功能和参数详细介绍&#xff1a; 主要特性 双模蓝牙支持&#xff1a; 支持蓝牙5.0标准&#xff0…

AI技术在演示文稿制作中的应用一键生成PPT

在快节奏的现代工作环境中&#xff0c;时间就是金钱。为了提高工作效率&#xff0c;许多专业人士都在寻找能够快速生成演示文稿&#xff08;PPT&#xff09;的工具。本文将探讨AI技术如何帮助用户自动生成演示文稿&#xff0c;从文案撰写到排版&#xff0c;最终输出成品&#x…

【Redis篇】Set和Zset 有序集合基本使用

目录 Set 基本命令 sadd SMEMBERS SISMEMBER SCARD 返回值&#xff1a; SPOP SMOVE SREM 集合间操作 交集&#xff1a; 并集&#xff1a; 差集&#xff1a; ​编辑 内部编码 使用场景&#xff1a; Zset 有序集合 Zset基本命令 ZADD ZCARD ZCOUNT ZRANGE …

安防监控Liveweb视频汇聚融合平台助力执法记录仪高效使用

Liveweb平台可接入的设备除了常见的智能分析网关与摄像头以外 &#xff0c;还可通过GB28181协议接入执法记录仪&#xff0c;实现对执法过程的全程监控与录像&#xff0c;并对执法轨迹与路径进行调阅回看。那么&#xff0c;如何做到执法记录仪高效使用呢&#xff1f; 由于执法记…

技术理性角度思考:游戏是什么?

1、从单纯技术理性角度来看&#xff0c;游戏就是若干事件的组合&#xff0c; 每一个事件都是若干代码控制的若干动画。通过各种事件&#xff0c;玩家持续输入&#xff0c;改变游戏状态&#xff0c;获得输出结果。 细分为界面切换事件&#xff0c;游戏逻辑事件&#xff0c; a…

IDEA 未启用lombok插件的Bug

项目中maven已引用了lombok依赖&#xff0c;之前运行没有问题的&#xff0c;但有时启动会提示&#xff1a; java: You arent using a compiler supported by lombok, so lombok will not work and has been disabled. Your processor is: com.sun.proxy.$Proxy8 Lombok support…

【vue】npm install 报错 python2 Error: not found: python2

如图所示&#xff0c;vue项目在下载依赖的时候报错找不到python2&#xff0c;有网友通过下载python2.7并配置环境变量解决了&#xff0c;这里有两个其他自测可用的方式&#xff0c;供各位作为参考。 报错的主要原因是因为【sass-loader】【node-sass】这两个依赖跟nodejs版本有…

批量DWG文件转换低版本(CAD图转低版本)——c#插件实现

此插件可实现指定路径下所有dwg文件&#xff08;包含子文件夹内dwg&#xff09;一键全部转为低版本&#xff08;包含2004、2007、2018版本&#xff0c;也可定制指定版本&#xff09;。效果如下&#xff1a; &#xff08;使用方法&#xff1a;命令行输入 “netload” 加载插件&…

无管理员权限 LCU auth-token、port 获取(全网首发 go)

一&#xff1a; 提要&#xff1a; 参考项目&#xff1a; https://github.com/Zzaphkiel/Seraphine 想做一个 lol 查战绩的软件&#xff0c;并且满足自己的需求&#xff08;把混子和大爹都表示出来&#xff09;&#xff0c;做的第一步就是获取 lcu token &#xff0c;网上清一色…

STM32F407ZGT6-UCOSIII笔记6:UCOS-III软件定时器

今日学习使用UCOS系统的软件定时器功能 本文学习与程序编写基于 正点原子的 STM32F1 UCOS开发手册 文章提供测试代码讲解、完整工程下载、测试效果图 软件定时器卡柱UCOS III 系统问题解决 目录 UCOS-III 软件定时器&#xff1a; 单次定时器&#xff1a; 周期定时器: 开启软…

金碟中间件-AAS-V10.0安装

金蝶中间件AAS-V10.0 AAS-V10.0安装 1.解压AAS-v10.0安装包 unzip AAS-V10.zip2.更新license.xml cd /root/ApusicAS/aas# 这里要将license复制到该路径 [rootvdb1 aas]# ls bin docs jmods lib modules templates config domains …

Reactor 响应式编程(第四篇:Spring Security Reactive)

系列文章目录 Reactor 响应式编程&#xff08;第一篇&#xff1a;Reactor核心&#xff09; Reactor 响应式编程&#xff08;第二篇&#xff1a;Spring Webflux&#xff09; Reactor 响应式编程&#xff08;第三篇&#xff1a;R2DBC&#xff09; Reactor 响应式编程&#xff08…

【Qt】信号、槽

目录 一、信号和槽的基本概念 二、connect函数&#xff1a;关联信号和槽 例子&#xff1a; 三、自定义信号和槽 1.自定义槽函数 2.自定义信号函数 例子&#xff1a; 四、带参的信号和槽 例子&#xff1a; 五、Q_OBJECT宏 六、断开信号和槽的连接 例子&#xff1a; …

PCIE概述

PCIE概述 文章目录 PCIE概述前言一、应用场景二、PCIE理论2.1 硬件2.2 拓扑结构&#xff1a;处理器和设备之间的关系2.3 速率2.4 层次接口2.5 四种请求类型2.5.1 bar空间2.5.2 memory2.5.3 IO2.5.4 configuration2.5.5 message 前言 参考链接&#xff1a; pcie总线知识点解析 …

Android Studio创建新项目并引入第三方so外部aar库驱动NFC读写器读写IC卡

本示例使用设备&#xff1a;https://item.taobao.com/item.htm?spma21dvs.23580594.0.0.52de2c1bbW3AUC&ftt&id615391857885 一、打开Android Studio,点击 File> New>New project 菜单&#xff0c;选择 要创建的项目模版&#xff0c;点击 Next 二、输入项目名称…

NX系列-使用 `nmcli` 命令创建 Wi-Fi 热点并设置固定 IP 地址

使用 nmcli 命令创建 Wi-Fi 热点并设置固定 IP 地址 一、前言 在一些场景下&#xff0c;我们需要将计算机或嵌入式设备&#xff08;例如 NVIDIA Orin NX&#xff09;转换为 Wi-Fi 热点&#xff0c;以便其他设备&#xff08;如手机、笔记本等&#xff09;能够连接并使用该设备…

用docker快速安装电子白板Excalidraw绘制流程图

注&#xff1a;本文操作以debian12.8 最小化安装环境为host系统。 一、彻底卸载原有的残留 apt-get purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras 二、设置docker的安装源 # Add Dockers official G…

mac 如何开启指定端口供外部访问?

前言 需要 mac 上开放指定端口&#xff0c;指定 ip 访问 解决 在 macOS 上开放一个端口&#xff0c;并指定只能特定的 IP 访问&#xff0c;可以使用 macOS 内置的 pfctl(Packet Filter)工具来实现。 1、 编辑 pf 配置文件&#xff1a; 打开 /etc/pf.conf 文件进行编辑。 可以使…

练习题:一维数组

练习题 第一题 键盘录入一组数列&#xff0c;利用冒泡排序将数据由大到小排序 代码 #include <stdio.h>int arr_home01() {int arr[10];int i,j,temp;printf("请输入10个测试整数&#xff1a;\n");int len sizeof(arr) / sizeof(arr[0]);for(i 0;i < …

手眼标定工具操作文档

1.手眼标定原理介绍 术语介绍 手眼标定&#xff1a;为了获取相机与机器人坐标系之间得位姿转换关系&#xff0c;需要对相机和机器人坐标系进行标定&#xff0c;该标定过程成为手眼标定&#xff0c;用于存储这一组转换关系的文件称为手眼标定文件。 ETH&#xff1a;即Eye To …