在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 { @Override public 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(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) {} }
-
注册拦截器到Spring容器:
java
复制
下载
@Configuration public class MyBatisConfig { @Bean public SqlInterceptor sqlInterceptor() { return new SqlInterceptor(); } @Bean public 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 {
@Autowired
private SqlLogService sqlLogService; // 注入日志服务
@Override
public 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 "参数序列化失败";
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public 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 {
@Autowired
private SqlLogMapper sqlLogMapper;
@Transactional(propagation = Propagation.REQUIRES_NEW) // 独立事务
public void saveLog(SqlExecutionLog log) {
sqlLogMapper.insert(log);
}
}
3. 注册拦截器到 MyBatis
java
复制
下载
@Configuration
public class MyBatisConfig {
@Bean
public SqlLogInterceptor sqlLogInterceptor() {
return new SqlLogInterceptor();
}
@Bean
public 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 {
@Override
public 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 {
@Autowired
private 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 {
@Autowired
private SqlLogService sqlLogService;
@Override
public 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
在类路径中。
- 若使用 Windows 集成身份验证,需在 URL 中添加
二、添加 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 并持久化到数据库。