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 …

OpenCV相机标定与3D重建(25)计算两个三维点集之间的最优仿射变换矩阵(3x4)函数estimateAffine3D()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算两个3D点集之间的最优仿射变换。 它计算 [ x y z ] [ a 11 a 12 a 13 a 21 a 22 a 23 a 31 a 32 a 33 ] [ X Y Z ] [ b 1 b 2 b 3 ] \beg…

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

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

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

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

Jenkins 编写Pipeline 简介及使用初识详解

一、Jenkins Pipeline简介 Jenkins Pipeline是Jenkins的一个重要功能,Jenkins 2.0 以上才会有,一系列 Jenkins 插件将整个持续集成用解释性代码 Jenkinsfile 来描述,它允许开发者以代码的方式定义整个持续集成和交付(CI/CD)流程,包括构建、测试、部署和监控等步骤。Jenk…

构建一个rust生产应用读书笔记四(实战1)

我们需要从访客那里收集哪些信息&#xff0c;以便将其登记为电子邮件通讯的订阅者&#xff1f; 电子邮件地址&#xff1a;这是最基本的要求&#xff0c;因为我们需要通过电子邮件地址向订阅者发送内容。姓名&#xff1a;虽然这不是强制性的&#xff0c;但我们希望收集一个名字…

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” 加载插件&…

信奥题解:勾股数计算中的浮点数精度问题

来源:GESP C++ 二级模拟题 本文给出官方参考答案的详细解析,包括每一部分的功能和关键点,以及与浮点数精度相关的问题的分析。 题目描述 勾股数是很有趣的数学概念。如果三个正整数a 、b 、c ,满足 a 2 + b 2 = c 2 a^2 + b^2 = c^2 a2+b2=c2 ,而且1 ≤ a ≤ b ≤ c ,…

解决 Spring 单例 Bean 注入原型 Bean 被共享的问题

在使用 Spring 开发应用时&#xff0c;Bean 的作用域&#xff08;Scope&#xff09; 决定了 Bean 实例的生命周期。Spring 提供了多种作用域&#xff0c;其中最常见的是 单例&#xff08;singleton&#xff09; 和 原型&#xff08;prototype&#xff09; 作用域。 单例作用域…

解决 OpenCV 与 FFmpeg 版本不兼容导致的编译错误

解决 OpenCV 与 FFmpeg 版本不兼容导致的编译错误 在安装并编译 OpenCV 3.2.0 版本时&#xff0c;出现的编译错误主要是由于 OpenCV 代码中使用的 FFmpeg 库宏定义与最新版 FFmpeg 库中的定义不一致所致。具体来说&#xff0c;原有的宏 CODEC_FLAG_GLOBAL_HEADER 和 AVFMT_RAW…

剑指offer搜索二维矩阵

题目连接 https://leetcode.cn/problems/search-a-2d-matrix-ii/’ 代码 自己想出来的 解法一 初始化两个指针&#xff0c;i0,j列数-1 若此时matrix[i][j]target 则返回true 若此时matrix[i][j]>target,表明在第j列中不可能存在target&#xff0c;因为列是升序的 若此时ma…

无管理员权限 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; 周期定时器: 开启软…

【网络安全】WIFI WPA/WPA2协议:深入解析与实践

WIFI WPA/WPA2协议&#xff1a;深入解析与实践 1. WPA/WPA2 协议 1.1 监听 Wi-Fi 流量 解析 WPA/WPA2 的第一步是监听 Wi-Fi 流量&#xff0c;捕获设备与接入点之间的 4 次握手数据。然而&#xff0c;设备通常不会频繁连接或重新连接&#xff0c;为了加速过程&#xff0c;攻…

【ubuntu18.04】ubuntu18.04挂在硬盘出现 Wrong diagnostic page; asked for 1 got 8解决方案

错误日志 [ 8754.700227] usb 2-3: new full-speed USB device number 3 using xhci_hcd [ 8754.867389] usb 2-3: New USB device found, idVendor0e0f, idProduct0002, bcdDevice 1.00 [ 8754.867421] usb 2-3: New USB device strings: Mfr1, Product2, SerialNumber0 [ 87…

金碟中间件-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 …