Java + Spring Boot + MyBatis获取以及持久化sql语句的方法
在Java的Spring Boot项目中结合MyBatis获取实际执行的SQL语句,可以通过以下几种方法实现:
方法一:配置MyBatis日志级别
通过调整日志级别,MyBatis会输出执行的SQL语句及参数,适用于快速调试。
-
修改
application.properties
文件:properties
复制
下载
# 设置特定Mapper接口的日志级别为DEBUG logging.level.com.example.mapper=DEBUG # 使用MyBatis标准日志输出(可选) mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
-
观察控制台输出:
执行查询时,控制台将显示带占位符的SQL和参数列表,例如:复制
下载
==> Preparing: SELECT * FROM user WHERE id = ? ==> Parameters: 1(Integer)
方法二:自定义MyBatis拦截器
编写拦截器获取BoundSql并处理参数,生成完整SQL。
-
创建拦截器类:
java
复制
下载
@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 SqlInterceptor implements Interceptor {@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement ms = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs().length > 1 ? invocation.getArgs()[1] : null;BoundSql boundSql = ms.getBoundSql(parameter);Configuration configuration = ms.getConfiguration();String sql = boundSql.getSql();Object parameterObject = boundSql.getParameterObject();List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();// 格式化SQL,替换参数占位符String formattedSql = formatSql(sql, parameterObject, parameterMappings, configuration);System.out.println("Executing SQL: " + formattedSql);return invocation.proceed();}private String formatSql(String sql, Object parameter, List<ParameterMapping> mappings, Configuration config) {if (mappings.isEmpty() || parameter == null) return sql;MetaObject metaObject = config.newMetaObject(parameter);return mappings.stream().map(m -> getParameterValue(metaObject, m)).reduce(sql, (s, p) -> s.replaceFirst("\\?", p));}private String getParameterValue(MetaObject metaObject, ParameterMapping mapping) {Object value = metaObject.getValue(mapping.getProperty());return value instanceof String ? "'" + value + "'" : value.toString();}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {} }
-
注册拦截器到Spring容器:
java
复制
下载
@Configuration public class MyBatisConfig {@Beanpublic SqlInterceptor sqlInterceptor() {return new SqlInterceptor();}@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(dataSource);factoryBean.setPlugins(new Interceptor[]{sqlInterceptor()});return factoryBean.getObject();} }
方法三:使用P6Spy监听JDBC操作
通过P6Spy代理JDBC驱动,记录完整SQL,适合生产环境。
-
添加P6Spy依赖:
xml
复制
下载
运行
<dependency><groupId>p6spy</groupId><artifactId>p6spy</artifactId><version>3.9.1</version> </dependency>
-
修改数据源配置:
properties
复制
下载
spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/mydb spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
-
配置
spy.properties
:
在src/main/resources
下创建spy.properties
:properties
复制
下载
module.log=com.p6spy.engine.logging.P6LogFactory appender=com.p6spy.engine.spy.appender.Slf4JLogger logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat customLogMessageFormat=%(currentTime) | SQL: %(sqlSingleLine)
方法对比表
方法 | 优点 | 缺点 |
---|---|---|
配置日志级别 | 简单快捷,无需代码改动 | SQL与参数分离,无法直接复制执行 |
自定义拦截器 | 灵活,可定制SQL格式 | 需要处理参数替换逻辑,复杂度较高 |
P6Spy | 完整SQL记录,配置简单 | 引入额外依赖,需修改数据源配置 |
选择适合你需求的方法,即可在Spring Boot中获取MyBatis执行的SQL语句。
在 Spring Boot + MyBatis 项目中,将执行的 SQL 语句持久化到数据库中存储,可以通过以下步骤实现:
1. 创建数据库表存储 SQL 日志
首先设计一个表来记录 SQL 执行信息,例如:
sql
复制
下载
CREATE TABLE sql_execution_log (id BIGINT AUTO_INCREMENT PRIMARY KEY,sql_statement TEXT NOT NULL, -- 执行的 SQL 语句parameters VARCHAR(1000), -- SQL 参数execution_time DATETIME, -- 执行时间duration BIGINT, -- 执行耗时(毫秒)success TINYINT(1), -- 是否执行成功error_message TEXT, -- 错误信息(可选)created_by VARCHAR(100) -- 操作人(可选) );
2. 通过 MyBatis 拦截器捕获 SQL
使用自定义拦截器获取 SQL 并记录到数据库,以下是完整实现:
2.1 自定义拦截器类
java
复制
下载
@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 SqlLogInterceptor implements Interceptor {@Autowiredprivate SqlLogService sqlLogService; // 注入日志服务@Overridepublic Object intercept(Invocation invocation) throws Throwable {MappedStatement ms = (MappedStatement) invocation.getArgs()[0];Object parameter = invocation.getArgs()[1];BoundSql boundSql = ms.getBoundSql(parameter);// 获取 SQL 和参数String sql = boundSql.getSql();String params = getParameterString(boundSql.getParameterObject());// 记录执行开始时间和状态long startTime = System.currentTimeMillis();boolean success = true;String errorMsg = null;try {return invocation.proceed(); // 执行 SQL} catch (Exception e) {success = false;errorMsg = e.getMessage();throw e;} finally {long duration = System.currentTimeMillis() - startTime;// 异步保存日志(避免影响主流程性能)CompletableFuture.runAsync(() -> {SqlExecutionLog log = new SqlExecutionLog();log.setSqlStatement(sql);log.setParameters(params);log.setExecutionTime(new Date());log.setDuration(duration);log.setSuccess(success);log.setErrorMessage(errorMsg);sqlLogService.saveLog(log);});}}// 将参数对象转为字符串(简化示例)private String getParameterString(Object parameter) {if (parameter == null) return "null";try {return new ObjectMapper().writeValueAsString(parameter);} catch (JsonProcessingException e) {return "参数序列化失败";}}@Overridepublic Object plugin(Object target) {return Plugin.wrap(target, this);}@Overridepublic void setProperties(Properties properties) {} }
2.2 定义日志实体类和 Mapper
java
复制
下载
// 实体类 @Data public class SqlExecutionLog {private Long id;private String sqlStatement;private String parameters;private Date executionTime;private Long duration;private Boolean success;private String errorMessage; }// Mapper 接口 @Mapper public interface SqlLogMapper {@Insert("INSERT INTO sql_execution_log (sql_statement, parameters, execution_time, duration, success, error_message) " +"VALUES (#{sqlStatement}, #{parameters}, #{executionTime}, #{duration}, #{success}, #{errorMessage})")@Options(useGeneratedKeys = true, keyProperty = "id")void insert(SqlExecutionLog log); }
2.3 日志服务类
java
复制
下载
@Service public class SqlLogService {@Autowiredprivate SqlLogMapper sqlLogMapper;@Transactional(propagation = Propagation.REQUIRES_NEW) // 独立事务public void saveLog(SqlExecutionLog log) {sqlLogMapper.insert(log);} }
3. 注册拦截器到 MyBatis
java
复制
下载
@Configuration public class MyBatisConfig {@Beanpublic SqlLogInterceptor sqlLogInterceptor() {return new SqlLogInterceptor();}@Beanpublic SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();factoryBean.setDataSource(dataSource);factoryBean.setPlugins(new Interceptor[]{sqlLogInterceptor()});return factoryBean.getObject();} }
4. 优化点
4.1 异步写入日志
使用 CompletableFuture.runAsync()
异步保存日志,避免阻塞主流程。若需更高性能,可结合消息队列(如 RabbitMQ/Kafka)或线程池。
4.2 独立事务
通过 @Transactional(propagation = Propagation.REQUIRES_NEW)
确保日志保存不受主事务回滚影响。
4.3 参数脱敏
在 getParameterString()
方法中过滤敏感信息(如密码):
java
复制
下载
private String getParameterString(Object parameter) {String json = new ObjectMapper().writeValueAsString(parameter);return json.replaceAll("\"password\":\"[^\"]+\"", "\"password\":\"***\""); }
5. 验证结果
执行 MyBatis 操作后,检查 sql_execution_log
表中是否生成记录:
sql
复制
下载
SELECT * FROM sql_execution_log;
方法对比
方案 | 优点 | 缺点 |
---|---|---|
MyBatis 拦截器 | 灵活可控,直接集成到代码 | 需要处理事务和性能问题 |
P6Spy + 数据库输出 | 配置简单,无需代码改动 | 需要依赖 P6Spy,扩展性低 |
根据需求选择合适方案,拦截器方案适合需要深度定制的场景,P6Spy 适合快速集成。
在Spring Boot项目中,通过P6Spy实现SQL日志的监控与数据库持久化,可以结合其拦截能力和自定义配置,具体步骤如下:
一、添加P6Spy依赖
在项目的pom.xml
中引入P6Spy依赖(版本推荐3.9.1):
xml
复制
下载
运行
<dependency><groupId>p6spy</groupId><artifactId>p6spy</artifactId><version>3.9.1</version> </dependency>
二、修改数据源配置
将原有的JDBC驱动替换为P6Spy的代理驱动,并调整数据库连接URL前缀:
properties
复制
下载
# application.properties spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/your_db?useSSL=false&serverTimezone=UTC
这里需将原数据库驱动(如MySQL的com.mysql.jdbc.Driver
)通过P6Spy代理,URL中添加jdbc:p6spy:
前缀28。
三、配置spy.properties
在src/main/resources
下创建spy.properties
文件,配置日志输出策略和数据库实际驱动:
properties
复制
下载
# 指定日志模块及实际数据库驱动 module.log=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory driverlist=com.mysql.jdbc.Driver# 自定义日志格式类 logMessageFormat=com.example.config.P6SpyCustomLogger appender=com.p6spy.engine.spy.appender.Slf4JLogger# 排除不必要日志类别 excludecategories=info,debug,result,batc,resultset# 慢SQL监控(可选) outagedetection=true outagedetectioninterval=2
此配置通过logMessageFormat
指定自定义日志格式类,将SQL输出到应用日志中27。
四、自定义SQL日志格式
实现MessageFormattingStrategy
接口,生成包含完整SQL及执行时间的日志:
java
复制
下载
public class P6SpyCustomLogger implements MessageFormattingStrategy {@Overridepublic String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {return StringUtils.isNotBlank(sql) ?String.format("SQL执行耗时: %dms | SQL语句:\n%s", elapsed, sql.replaceAll("\\s+", " ")) : "";} }
此示例将SQL格式化输出,并移除多余空格78。
五、日志持久化到数据库
若需将SQL日志存储至数据库,需额外设计表和异步写入逻辑:
1. 创建日志表
sql
复制
下载
CREATE TABLE sql_log (id BIGINT AUTO_INCREMENT PRIMARY KEY,sql_text TEXT,parameters TEXT,execution_time DATETIME,duration BIGINT,success TINYINT(1) );
2. 异步写入服务
在自定义日志类中调用服务层异步保存日志:
java
复制
下载
@Service public class SqlLogService {@Autowiredprivate SqlLogMapper sqlLogMapper;@Async@Transactional(propagation = Propagation.REQUIRES_NEW)public void saveLog(String sql, long duration) {SqlLog log = new SqlLog();log.setSqlText(sql);log.setExecutionTime(new Date());log.setDuration(duration);sqlLogMapper.insert(log);} }// 在自定义Logger中注入SqlLogService并调用 public class P6SpyCustomLogger implements MessageFormattingStrategy {@Autowiredprivate SqlLogService sqlLogService;@Overridepublic String formatMessage(...) {sqlLogService.saveLog(sql, elapsed);return formattedLog;} }
需确保日志服务通过@Async
异步执行,避免阻塞主线程26。
六、高级配置
1. 参数脱敏
在日志格式化时过滤敏感信息(如密码):
java
复制
下载
private String filterSensitiveParams(String sql) {return sql.replaceAll("\"password\":\"[^\"]+\"", "\"password\":\"***\""); }
2. 慢SQL告警
通过outagedetectioninterval
设置慢SQL阈值(单位秒),超时的SQL会单独记录28。
方案对比
方案 | 优点 | 缺点 |
---|---|---|
P6Spy日志输出 | 配置简单,无需代码侵入 | 需结合异步服务实现持久化 |
MyBatis拦截器 | 直接集成业务逻辑,灵活 | 需处理参数替换和性能问题 |
通过以上步骤,P6Spy可完整记录SQL执行细节,并通过异步服务将日志持久化到数据库。此方案适用于调试、性能分析和审计场景,且对原有代码无侵入性
在 SQL Server 2008 R2 中使用 P6Spy 配置数据源,需调整驱动和连接参数。以下是详细步骤:
一、修改数据源配置
在 application.properties
中配置 P6Spy 代理驱动和 SQL Server 连接信息:
properties
复制
下载
# application.properties spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver spring.datasource.url=jdbc:p6spy:sqlserver://localhost:1433;databaseName=YourDBName spring.datasource.username=your_username spring.datasource.password=your_password# SQL Server 其他可选参数(按需添加) spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.maximum-pool-size=10
关键配置说明
-
驱动类名:使用
com.p6spy.engine.spy.P6SpyDriver
作为代理驱动。 -
URL 格式:
-
原 SQL Server URL 格式:
jdbc:sqlserver://主机:端口;databaseName=数据库名
-
修改为 P6Spy 代理后的 URL:添加
jdbc:p6spy:
前缀,即jdbc:p6spy:sqlserver://...
。
-
-
认证方式:
-
若使用 Windows 集成身份验证,需在 URL 中添加
integratedSecurity=true
,并确保sqljdbc_auth.dll
在类路径中。
-
二、添加 SQL Server JDBC 驱动依赖
在 pom.xml
中添加 SQL Server 官方 JDBC 驱动:
xml
复制
下载
运行
<dependency><groupId>com.microsoft.sqlserver</groupId><artifactId>mssql-jdbc</artifactId><version>9.4.1.jre8</version> <!-- 适配 SQL Server 2008 R2 的版本 --> </dependency>
注意:
SQL Server 2008 R2 建议使用
mssql-jdbc
6.0 或 7.x 版本(需 JDK 1.8+)。若使用旧版驱动(如
sqljdbc4
),需手动下载 JAR 并添加到项目中。
三、配置 spy.properties
在 src/main/resources
下创建 spy.properties
,配置 P6Spy 的日志行为和实际驱动:
properties
复制
下载
# spy.properties# 指定实际数据库驱动类(SQL Server) driverlist=com.microsoft.sqlserver.jdbc.SQLServerDriver# 日志模块配置 module.log=com.p6spy.engine.logging.P6LogFactory appender=com.p6spy.engine.spy.appender.Slf4JLogger# 自定义日志格式(可选) logMessageFormat=com.example.config.P6SpyCustomLogger# 排除无关日志 excludecategories=info,debug,result,batc,resultset# 慢SQL监控(可选) outagedetection=true outagedetectioninterval=2
关键参数说明
-
driverlist
:必须设置为 SQL Server 的驱动类com.microsoft.sqlserver.jdbc.SQLServerDriver
。 -
appender
:使用Slf4JLogger
将日志输出到应用日志系统(如 Logback)。
四、处理 Windows 集成身份验证
若需使用 Windows 身份验证(非用户名/密码方式),需额外配置:
-
修改 URL:
properties
复制
下载
spring.datasource.url=jdbc:p6spy:sqlserver://localhost:1433;databaseName=YourDBName;integratedSecurity=true
-
添加
sqljdbc_auth.dll
:-
从 Microsoft JDBC 驱动包 中提取
sqljdbc_auth.dll
。 -
将文件放在
C:\Windows\System32
(Windows)或 Java 库路径中。
-
五、验证配置
-
启动应用:检查控制台是否输出 P6Spy 的初始化日志,例如:
复制
下载
P6SpyOptions 初始化成功
-
执行 SQL:触发数据库操作后,观察日志中是否包含完整的 SQL 语句:
复制
下载
SQL执行耗时: 15ms | SQL语句: SELECT * FROM users WHERE id = 1
-
检查数据库连接:若出现连接失败,检查以下内容:
-
驱动类名和 URL 是否正确。
-
SQL Server 服务是否运行(端口 1433 是否开放)。
-
防火墙是否阻止连接。
-
六、完整配置示例
1. application.properties
properties
复制
下载
# 数据源配置 spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver spring.datasource.url=jdbc:p6spy:sqlserver://localhost:1433;databaseName=TestDB spring.datasource.username=sa spring.datasource.password=your_password# Hikari 连接池配置 spring.datasource.hikari.connection-timeout=30000 spring.datasource.hikari.maximum-pool-size=10
2. spy.properties
properties
复制
下载
driverlist=com.microsoft.sqlserver.jdbc.SQLServerDriver module.log=com.p6spy.engine.logging.P6LogFactory appender=com.p6spy.engine.spy.appender.Slf4JLogger logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat customLogMessageFormat=%(currentTime) | SQL: %(sqlSingleLine)
常见问题排查
问题 | 解决方案 |
---|---|
驱动类未找到 | 检查 mssql-jdbc 依赖是否引入,或手动添加 JAR 包。 |
连接超时 | 确认 SQL Server 实例已启动,且防火墙允许 1433 端口通信。 |
身份验证失败 | 检查用户名/密码是否正确,或配置 integratedSecurity=true 及 sqljdbc_auth.dll 。 |
P6Spy 未生效 | 确保 spy.properties 位于 src/main/resources 目录下。 |
通过以上配置,即可在 SQL Server 2008 R2 中通过 P6Spy 捕获 SQL 并持久化到数据库。