You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot集成Hibernate时如何监听特定数据库SELECT查询的准备与执行事件并获取SQL语句?

监听Hibernate SELECT查询的准备/执行事件(含SQL与连接信息)

Great observation about Hibernate's StatementPreparerImpl and the jdbcPrepareStatementStart hook — you're on the right track with leveraging Hibernate's internal observer mechanism to capture SQL and connection details, which is more reliable than just parsing logs. Let's break down how to implement this properly, plus a simpler alternative you might prefer.

1. 利用Hibernate的JdbcSessionContext.Observer扩展点

你的判断是对的:jdbcPrepareStatementStart 确实接收要执行的SQL语句,并且通过上下文可以获取到对应的数据库连接信息。这里是具体的实现步骤,适配Spring Boot + Hibernate 5.x/6.x:

1.1 自定义Observer捕获SQL与连接信息

实现JdbcSessionContext.Observer接口,重写监听方法过滤SELECT语句并收集连接元数据:

import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcSessionContext;
import java.sql.Connection;
import java.sql.SQLException;

public class CustomJdbcObserver implements JdbcSessionContext.Observer {

    @Override
    public void jdbcPrepareStatementStart(String sql) {
        // 只监听SELECT查询(可根据需求调整过滤规则)
        if (sql.trim().toLowerCase().startsWith("select")) {
            JdbcSessionContext context = getCurrentJdbcSessionContext();
            try {
                JdbcConnectionAccess connectionAccess = context.getJdbcConnectionAccess();
                Connection connection = connectionAccess.obtainConnection();
                
                // 获取连接核心信息:URL、用户名
                String dbUrl = connection.getMetaData().getURL();
                String dbUser = connection.getMetaData().getUserName();
                
                // 这里可以替换为日志记录、事件上报等业务逻辑
                System.out.printf("捕获SELECT查询 | SQL: %s | 连接URL: %s | 数据库用户: %s%n", sql, dbUrl, dbUser);
                
                // 注意:不要自行关闭连接,交还给Hibernate管理生命周期
                connectionAccess.releaseConnection(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    // 适配Hibernate版本获取当前上下文
    private JdbcSessionContext getCurrentJdbcSessionContext() {
        // Hibernate 5.x 示例
        return (JdbcSessionContext) org.hibernate.internal.SessionFactoryImpl.getCurrentSession();
        // Hibernate 6.x 可替换为:org.hibernate.resource.jdbc.spi.JdbcSessionContextAccessor.getCurrentJdbcSessionContext()
    }

    // 可选:监听语句执行事件
    @Override
    public void jdbcExecuteStatementStart(String sql) {
        // 自定义逻辑
    }
}

1.2 自定义JdbcSessionContext绑定Observer

让Hibernate使用我们的Observer,需要重写JdbcSessionContext的getObserver方法:

import org.hibernate.engine.jdbc.spi.JdbcSessionContextImpl;
import org.hibernate.service.ServiceRegistry;

public class CustomJdbcSessionContext extends JdbcSessionContextImpl {

    private final CustomJdbcObserver customObserver;

    public CustomJdbcSessionContext(ServiceRegistry serviceRegistry) {
        super(serviceRegistry);
        this.customObserver = new CustomJdbcObserver();
    }

    @Override
    public Observer getObserver() {
        return customObserver;
    }
}

1.3 Spring Boot中配置自定义SessionFactory

通过HibernatePropertiesCustomizer注入我们的扩展逻辑:

import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.boot.spi.SessionFactoryBuilderFactory;
import org.hibernate.boot.spi.SessionFactoryBuilderImplementor;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;

@Configuration
public class HibernateConfig {

    @Bean
    public HibernatePropertiesCustomizer hibernatePropertiesCustomizer() {
        return hibernateProperties -> 
            hibernateProperties.put("hibernate.session_factory_builder_factory", CustomSessionFactoryBuilderFactory.class.getName());
    }

    public static class CustomSessionFactoryBuilderFactory implements SessionFactoryBuilderFactory {
        @Override
        public SessionFactoryBuilder getSessionFactoryBuilder(SessionFactoryBuilderImplementor defaultBuilder) {
            defaultBuilder.applyJdbcSessionContextCustomizer(serviceRegistry -> new CustomJdbcSessionContext(serviceRegistry));
            return defaultBuilder;
        }
    }
}

2. 更简单的替代方案:StatementInspector

如果你觉得内部扩展太繁琐,可以用Hibernate官方提供的StatementInspector接口,它更面向开发者,兼容性更好:

2.1 实现自定义Inspector

import org.hibernate.resource.jdbc.spi.StatementInspector;
import java.sql.Connection;
import java.sql.SQLException;

public class CustomStatementInspector implements StatementInspector {

    @Override
    public String inspect(String sql, Connection connection) {
        if (sql.trim().toLowerCase().startsWith("select")) {
            try {
                String dbUrl = connection.getMetaData().getURL();
                String dbUser = connection.getMetaData().getUserName();
                System.out.printf("捕获SELECT查询 | SQL: %s | 连接URL: %s | 数据库用户: %s%n", sql, dbUrl, dbUser);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        // 返回原SQL,不干扰Hibernate执行逻辑
        return sql;
    }
}

2.2 Spring Boot配置启用

在application.properties中添加配置:

spring.jpa.properties.hibernate.session_factory.statement_inspector=com.yourpackage.CustomStatementInspector

为什么日志Appender拿不到连接信息?

你提到的自定义org.hibernate.SQL日志Appender,本质上只是打印Hibernate生成的SQL字符串——Hibernate在输出该日志时,并没有将连接的上下文元数据(比如URL、用户)传递给日志框架,所以无法从日志事件中提取这些信息。这也是为什么我们需要依赖Hibernate原生的扩展点,而非日志解析。

内容的提问来源于stack exchange,提问作者Kuba Szymanowski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 23:08:14