Java + Spring Boot + MyBatis获取以及持久化sql语句的方法

在Java的Spring Boot项目中结合MyBatis获取实际执行的SQL语句,可以通过以下几种方法实现:

方法一:配置MyBatis日志级别

通过调整日志级别,MyBatis会输出执行的SQL语句及参数,适用于快速调试。

  1. 修改application.properties文件:

    properties

    复制

    下载

    复制代码
    # 设置特定Mapper接口的日志级别为DEBUG
    logging.level.com.example.mapper=DEBUG
    # 使用MyBatis标准日志输出(可选)
    mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
  2. 观察控制台输出:

    执行查询时,控制台将显示带占位符的SQL和参数列表,例如:

    复制

    下载

    复制代码
    ==>  Preparing: SELECT * FROM user WHERE id = ?
    ==> Parameters: 1(Integer)

方法二:自定义MyBatis拦截器

编写拦截器获取BoundSql并处理参数,生成完整SQL。

  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 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) {}
    }
  2. 注册拦截器到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,适合生产环境。

  1. 添加P6Spy依赖:

    xml

    复制

    下载

    运行

    复制代码
    <dependency>
        <groupId>p6spy</groupId>
        <artifactId>p6spy</artifactId>
        <version>3.9.1</version>
    </dependency>
  2. 修改数据源配置:

    properties

    复制

    下载

    复制代码
    spring.datasource.url=jdbc:p6spy:mysql://localhost:3306/mydb
    spring.datasource.driver-class-name=com.p6spy.engine.spy.P6SpyDriver
  3. 配置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 在类路径中。

二、添加 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 身份验证(非用户名/密码方式),需额外配置:

  1. 修改 URL

    properties

    复制

    下载

    复制代码
    spring.datasource.url=jdbc:p6spy:sqlserver://localhost:1433;databaseName=YourDBName;integratedSecurity=true
  2. 添加 sqljdbc_auth.dll

    • Microsoft JDBC 驱动包 中提取 sqljdbc_auth.dll

    • 将文件放在 C:\Windows\System32(Windows)或 Java 库路径中。


五、验证配置

  1. 启动应用:检查控制台是否输出 P6Spy 的初始化日志,例如:

    复制

    下载

    复制代码
    P6SpyOptions 初始化成功
  2. 执行 SQL:触发数据库操作后,观察日志中是否包含完整的 SQL 语句:

    复制

    下载

    复制代码
    SQL执行耗时: 15ms | SQL语句: SELECT * FROM users WHERE id = 1
  3. 检查数据库连接:若出现连接失败,检查以下内容:

    • 驱动类名和 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=truesqljdbc_auth.dll
P6Spy 未生效 确保 spy.properties 位于 src/main/resources 目录下。

通过以上配置,即可在 SQL Server 2008 R2 中通过 P6Spy 捕获 SQL 并持久化到数据库。

相关推荐
.生产的驴28 分钟前
SpringBoot 接口国际化i18n 多语言返回 中英文切换 全球化 语言切换
java·开发语言·spring boot·后端·前端框架
Howard_Stark32 分钟前
Spring的BeanFactory和FactoryBean的区别
java·后端·spring
饮长安千年月33 分钟前
学生管理系统审计
java·网络安全·代码审计
八股文领域大手子36 分钟前
深入浅出限流算法(三):追求极致精确的滑动日志
开发语言·数据结构·算法·leetcode·mybatis·哈希算法
-曾牛41 分钟前
Spring Boot中@RequestParam、@RequestBody、@PathVariable的区别与使用
java·spring boot·后端·intellij-idea·注解·spring boot 注解·混淆用法
软件2051 小时前
【UserDetailsService】
spring boot
新时代苦力工1 小时前
处理对象集合,输出Map<String, Map<String, List<MyObject>>>格式数据,无序组合键处理方法
java·数据结构·list
niesiyuan0001 小时前
MAC如何安装多版本jdk(以8,11,17为例)
java
zcyf08091 小时前
kafka理论学习汇总
java·分布式·学习·kafka
再拼一次吧2 小时前
Spring进阶篇
java·后端·spring