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 并持久化到数据库。

相关推荐
qq_3340602125 分钟前
spring5-配外部文件-spEL-工厂bean-FactoryBean-注解配bean
java·spring·web
Uranus^1 小时前
使用Spring Boot和Spring Security结合JWT实现安全的RESTful API
java·spring boot·spring security·jwt·restful api
FAQEW1 小时前
介绍一下什么是反射(面试题详细讲解)
java·开发语言·反射
是三好2 小时前
并发容器(Collections)
java·多线程·juc
jian110582 小时前
java项目实战、pom.xml配置解释、pojo 普通java对象
java·开发语言·python
述雾学java2 小时前
Spring Boot是什么?MybatisPlus常用注解,LambdaQueryWrapper常用方法
java·spring boot·后端
jinhuazhe20132 小时前
maven 3.0多线程编译提高编译速度
java·maven
xosg3 小时前
HTMLUnknownElement的使用
java·前端·javascript
forestsea4 小时前
Java虚拟机面试题:内存管理(上)
java·开发语言