如何在Spring Boot JPA+Hibernate中捕获单条SQL查询执行时长
解决方案:Spring Boot + JPA/Hibernate 捕获SQL/事务执行时长
方案1:Hibernate StatementInspector(推荐,支持单条SQL+时长捕获)
利用Hibernate原生的StatementInspector和StatementObserver,可以精准拦截每条SQL的执行时机,同时获取SQL语句和执行时长。
步骤1:实现SQL拦截器与计时器
import org.hibernate.resource.jdbc.spi.StatementInspector; import java.sql.Statement; import java.util.concurrent.ConcurrentHashMap; public class TimingStatementInspector implements StatementInspector { private final ConcurrentHashMap<Statement, Long> startTimes = new ConcurrentHashMap<>(); @Override public String inspect(String sql) { // 返回原始SQL,可在此处预处理SQL(比如脱敏) return sql; } // 记录Statement的执行开始时间 public void recordStartTime(Statement statement) { startTimes.put(statement, System.currentTimeMillis()); } // 计算并返回执行时长,同时清理记录 public long calculateDuration(Statement statement) { Long startTime = startTimes.remove(statement); return startTime != null ? System.currentTimeMillis() - startTime : -1; } }
步骤2:绑定Hibernate Statement执行监听
import org.hibernate.jpa.spi.JpaEntityManagerFactory; import org.hibernate.resource.jdbc.spi.StatementObserver; import jakarta.persistence.EntityManagerFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.stereotype.Component; @Component public class HibernateTimingConfig implements InitializingBean { private final EntityManagerFactory entityManagerFactory; private final TimingStatementInspector inspector; public HibernateTimingConfig(EntityManagerFactory entityManagerFactory, TimingStatementInspector inspector) { this.entityManagerFactory = entityManagerFactory; this.inspector = inspector; } @Override public void afterPropertiesSet() { JpaEntityManagerFactory jpaEmf = (JpaEntityManagerFactory) entityManagerFactory; jpaEmf.getSessionFactory().getSessionFactoryOptions() .getServiceRegistry() .getService(org.hibernate.engine.jdbc.spi.JdbcServices.class) .getStatementPreparer() .addStatementObserver(new StatementObserver() { @Override public void statementPrepared(Statement statement) { inspector.recordStartTime(statement); } @Override public void statementExecuted(Statement statement, String sql) { long duration = inspector.calculateDuration(statement); // 此处可对接监控系统、日志系统或自定义业务逻辑 System.out.printf("SQL执行时长:%dms,语句:%s%n", duration, sql); } }); } }
步骤3:配置启用Inspector
在application.yml中添加配置:
spring: jpa: properties: hibernate: session_factory: statement_inspector: com.yourpackage.TimingStatementInspector
方案2:Spring AOP 拦截Repository查询
如果不需要直接获取SQL,仅需统计Repository方法的执行时长,可使用AOP拦截Spring Data JPA的查询方法。
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; @Aspect @Component public class QueryTimingAspect { // 拦截自定义Repository下的所有查询类方法(find/query/get开头) @Around("execution(* com.yourpackage.repository.*.*(..)) && " + "(execution(* *find*(..)) || execution(* *query*(..)) || execution(* *get*(..)))") public Object measureQueryTime(ProceedingJoinPoint joinPoint) throws Throwable { StopWatch stopWatch = new StopWatch(); stopWatch.start(); Object result = joinPoint.proceed(); stopWatch.stop(); long duration = stopWatch.getTotalTimeMillis(); String methodName = joinPoint.getSignature().getName(); System.out.printf("Repository方法[%s]执行时长:%dms%n", methodName, duration); return result; } }
方案3:AOP拦截事务执行时长
若仅需要统计事务级别的执行时长,可直接拦截@Transactional注解的方法。
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import org.springframework.util.StopWatch; @Aspect @Component public class TransactionTimingAspect { @Around("@annotation(org.springframework.transaction.annotation.Transactional)") public Object measureTransactionTime(ProceedingJoinPoint joinPoint) throws Throwable { StopWatch stopWatch = new StopWatch(); stopWatch.start(); Object result = joinPoint.proceed(); stopWatch.stop(); long duration = stopWatch.getTotalTimeMillis(); String methodName = joinPoint.getSignature().getName(); System.out.printf("事务方法[%s]执行时长:%dms%n", methodName, duration); return result; } }
内容的提问来源于stack exchange,提问作者Twisted Tea
相关产品推荐
相关产品推荐

