🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论
🔥🔥🔥(源码 + 调试运行 + 问题答疑)🔥🔥🔥 有兴趣可以联系我
🔥🔥🔥 文末有往期免费源码,直接领取获取(无删减,无套路)
MyBatis SQL日志输出全解析:从基础实现到高级优化
热门标题推荐
-
MyBatis SQL日志输出深度剖析:从基础实现到插件优化
-
手把手实现MyBatis SQL日志功能:原理、实现与陷阱规避
-
MyBatis SQL监控终极指南:掌握SQL执行的每一个细节
-
深入理解MyBatis日志机制:如何优雅输出可执行的SQL语句
-
MyBatis SQL日志输出全攻略:从简单打印到高级定制
正文
SQL日志输出是MyBatis开发中最常用且重要的功能之一,它帮助开发者监控和调试数据库操作。一个良好的SQL日志系统不仅能显示执行的SQL语句,还能展示参数值、执行时间等关键信息。本文将深入探讨MyBatis中SQL日志输出的实现原理、各种实现方式及其优缺点。
一、SQL日志输出的重要性
在开发过程中,SQL日志输出具有不可替代的价值:
-
调试与排错:当SQL执行出现问题时,日志可以帮助快速定位问题
-
性能监控:通过记录SQL执行时间,识别性能瓶颈
-
审计追踪:记录所有数据库操作,满足审计需求
-
SQL优化:分析实际执行的SQL,进行优化调整
二、基础SQL日志输出实现
最简单的SQL日志输出可以在StatementHandler.prepare方法之后或Executor执行SQL之前实现:
public class SimpleStatementHandler implements StatementHandler {@Overridepublic Statement prepare(Connection connection) throws SQLException {// 原始prepare逻辑Statement statement = connection.createStatement();// 输出SQL日志String sql = boundSql.getSql();System.out.println("Executing SQL: " + sql);return statement;}}
这种方式虽然简单,但只能输出带有占位符(?)的SQL,无法显示实际的参数值。
三、带参数值的SQL日志输出
要输出完整的可执行SQL,需要获取参数值并替换到SQL中的占位符:
public class ParameterAwareStatementHandler implements StatementHandler {@Overridepublic int update(Statement statement) throws SQLException {// 获取SQL和参数String sql = boundSql.getSql();Object parameter = boundSql.getParameterObject();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();// 构建完整SQLString completeSql = buildCompleteSql(sql, parameter, parameterMappings);System.out.println("Executing SQL: " + completeSql);// 执行原始逻辑return statement.executeUpdate(sql);}private String buildCompleteSql(String sql, Object parameter, List<ParameterMapping> parameterMappings) {if (parameterMappings == null || parameterMappings.isEmpty()) {return sql;}String completeSql = sql;for (int i = 0; i < parameterMappings.size(); i++) {ParameterMapping mapping = parameterMappings.get(i);Object value = getParameterValue(parameter, mapping);completeSql = completeSql.replaceFirst("\\?", formatParameterValue(value));}return completeSql;}}
注意:这种方法仅用于日志输出,不能直接执行拼接后的SQL,因为存在SQL注入风险。
四、SQL注入风险与安全处理
在拼接SQL参数时,必须注意安全性问题:
-
字符串值需要引号包裹:字符串参数必须用单引号包裹
-
特殊字符转义:包含单引号的字符串需要转义处理
-
NULL值处理:NULL值应该直接替换为NULL关键字
-
日期格式处理:日期类型需要转换为合适的字符串格式
private String formatParameterValue(Object value) {if (value == null) {return "NULL";}if (value instanceof String) {return "'" + ((String) value).replace("'", "''") + "'";}if (value instanceof Date) {return "'" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format((Date) value) + "'";}if (value instanceof Number) {return value.toString();}return "'" + value.toString().replace("'", "''") + "'";}
五、基于插件的优雅实现
使用MyBatis插件机制可以实现更优雅、非侵入式的SQL日志输出:
1. 定义日志插件
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})public class SqlLogPlugin implements Interceptor {private static final Logger logger = LoggerFactory.getLogger(SqlLogPlugin.class);@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];BoundSql boundSql = mappedStatement.getBoundSql(parameter);String sql = boundSql.getSql();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();// 构建完整SQLString completeSql = buildCompleteSql(sql, parameter, parameterMappings);long start = System.currentTimeMillis();Object result = invocation.proceed();long end = System.currentTimeMillis();logger.info("SQL: {} | Time: {}ms", completeSql, (end - start));return result;}}
2. 配置插件
在MyBatis配置文件中注册插件:
<plugins><plugin interceptor="com.example.SqlLogPlugin"><property name="logLevel" value="DEBUG"/></plugin></plugins>
六、高级SQL日志功能
除了基本的SQL输出,还可以实现更高级的日志功能:
1. 执行时间监控
public class PerformanceMonitorPlugin implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {long start = System.currentTimeMillis();Object result = invocation.proceed();long end = System.currentTimeMillis();long threshold = 1000; // 1秒阈值if (end - start > threshold) {logger.warn("Slow SQL detected: {}ms", (end - start));}return result;}}
2. SQL格式化输出
对于复杂的SQL,格式化输出更易于阅读:
private String formatSql(String sql) {// 简单的SQL格式化逻辑sql = sql.replaceAll("(?i)select", "\nSELECT ").replaceAll("(?i)from", "\nFROM ").replaceAll("(?i)where", "\nWHERE ").replaceAll("(?i)join", "\nJOIN ").replaceAll("(?i)order by", "\nORDER BY ").replaceAll("(?i)group by", "\nGROUP BY ");return sql;}
3. 敏感数据脱敏
对于包含敏感信息的SQL,需要进行脱敏处理:
private String maskSensitiveData(String sql) {// 脱敏手机号sql = sql.replaceAll("1[3-9]\\d{9}", "1**********");// 脱敏身份证号sql = sql.replaceAll("\\d{17}[\\dXx]", "***************");// 脱敏银行卡号sql = sql.replaceAll("\\d{16,19}", "*******************");return sql;}
七、集成日志框架
直接使用System.out.println不是生产环境的最佳选择,应该集成成熟的日志框架:
1. SLF4J集成
public class Slf4jSqlLogger {private static final Logger logger = LoggerFactory.getLogger("SQL_LOGGER");public void logSql(String sql, long executionTime, Object... params) {if (logger.isDebugEnabled()) {StringBuilder logMessage = new StringBuilder();logMessage.append("SQL: ").append(sql);logMessage.append(" | Parameters: ").append(Arrays.toString(params));logMessage.append(" | Time: ").append(executionTime).append("ms");logger.debug(logMessage.toString());}}
}
2. 动态日志级别控制
通过配置动态控制SQL日志的详细程度:
public enum SqlLogLevel {NONE, // 不输出日志BASIC, // 只输出SQL语句PARAMS, // 输出SQL和参数PERFORMANCE, // 输出SQL、参数和执行时间FULL // 输出所有信息}
八、生产环境最佳实践
在生产环境中使用SQL日志时,应注意以下最佳实践:
-
性能影响:日志输出可能影响性能,应在必要时开启
-
日志级别控制:根据环境动态调整日志级别
-
敏感信息保护:对敏感数据进行脱敏处理
-
日志轮转:配置适当的日志轮转策略,避免日志文件过大
-
监控告警:对慢SQL和异常SQL设置监控告警
九、常见问题与解决方案
1. 日志输出不完整
问题:参数过多时日志输出被截断 解决方案:限制参数输出长度,或提供摘要信息
2. 性能开销过大
问题:日志输出导致性能显著下降 解决方案:使用异步日志输出,或采样输出部分SQL
3. 特殊类型处理
问题:Blob、Clob等特殊类型无法正常输出 解决方案:对这些类型提供特殊处理,输出摘要信息而非完整内容
十、总结
SQL日志输出是MyBatis开发中不可或缺的功能,从最简单的System.out.println到基于插件的完整解决方案,每种方式都有其适用场景。在实际项目中,应根据具体需求选择合适的实现方式,并注意安全性、性能和可维护性等方面的考虑。
通过本文的介绍,相信您已经对MyBatis SQL日志输出有了全面的了解,能够根据项目需求实现适合的SQL日志功能,从而更好地监控和优化数据库操作。
往期免费源码 (无删减,无套路):🔥🔥🔥
https://pan.baidu.com/s/1sjAr08PU9Xe7MQf1gjGM5w?pwd=6666
「在线考试系统源码(含搭建教程)」 (无删减,无套路):🔥🔥🔥
链接:https://pan.quark.cn/s/96c4f00fdb43 提取码:WR6M
往期免费源码对应视频:
免费获取--SpringBoot+Vue宠物商城网站系统
🥂(❁´◡`❁)您的点赞👍➕评论📝➕收藏⭐是作者创作的最大动力🤞
💖📕🎉🔥 支持我:点赞👍+收藏⭐️+留言📝欢迎留言讨论
🔥🔥🔥(源码 + 调试运行 + 问题答疑)
🔥🔥🔥 有兴趣可以联系我