后端必看!Spring Boot3 中记录 MyBatis SQL 执行时间的高效方法
你有没有遇到过这样的困扰?在使用 Spring Boot3 和 MyBatis 开发后端项目时,项目上线后,数据库操作越来越频繁,性能问题逐渐暴露。想要排查问题,却不知道从哪里入手?尤其是 SQL 执行效率低下,严重影响了系统的响应速度,却连具体哪条 SQL 耗时较长都不清楚,这可怎么办?
在如今互联网应用规模不断扩大、业务复杂度日益提升的背景下,对于互联网大厂的后端开发人员来说,Spring Boot3 和 MyBatis 是常用的开发框架组合。系统的性能优化成为了开发过程中的关键环节,而准确记录 SQL 执行时间,是定位和解决性能问题的重要前提。只有知道了每条 SQL 语句的执行时长,才能有针对性地进行优化,提升整个系统的运行效率,给用户带来更好的使用体验。如果不能及时发现并解决 SQL 执行效率问题,可能会导致用户流失,影响公司业务发展。
使用 AOP(面向切面编程)
AOP 是 Spring 框架的核心技术之一,它允许开发者在不修改源代码的情况下,增加额外的功能。我们可以通过定义一个切面(Aspect),在方法执行前后插入自定义的逻辑,来记录 SQL 执行时间。首先,在项目的 pom.xml 文件中引入 AOP 相关的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
然后,定义一个切面类,例如SqlExecutionTimeAspect:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class SqlExecutionTimeAspect {
private static final Logger logger = LoggerFactory.getLogger(SqlExecutionTimeAspect.class);
@Around("execution(* com.example.dao.*.*(..))") // 这里根据实际的DAO包路径进行调整
public Object recordSqlExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
logger.info("SQL执行时间:{}ms", executionTime);
return result;
}
}
在上述代码中,@Around注解定义了一个环绕通知,execution(* com.example.dao.*.*(..))表示匹配指定包下 DAO 接口的所有方法。在方法执行前后获取当前时间,计算时间差并记录到日志中。
使用 MyBatis 拦截器
MyBatis 提供了插件机制,我们可以通过实现Interceptor接口来拦截 SQL 执行过程,并添加自定义逻辑。新建一个 MyBatis 的拦截器类,例如SqlTimeInterceptor:
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class SqlTimeInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(SqlTimeInterceptor.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = invocation.proceed();
long endTime = System.currentTimeMillis();
long executionTime = endTime - startTime;
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
BoundSql boundSql = ms.getBoundSql(args[1]);
String sql = boundSql.getSql();
logger.info("SQL: {},执行时间:{}ms", sql, executionTime);
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
接着,需要将拦截器注册到 MyBatis 的配置中。在 Spring Boot3 的配置类中,添加如下代码:
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
@Configuration
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setPlugins(new SqlTimeInterceptor()); // 注册拦截器
return factoryBean.getObject();
}
}
这样,每次执行 SQL 时,都会记录 SQL 语句及其执行时间。
使用数据源代理(如 datasource - proxy)
datasource - proxy是一个强大的数据源代理工具,它可以在不修改代码的情况下,记录 SQL 执行时间等信息。首先,在 pom.xml 文件中添加依赖:
<dependency>
<groupId>com.github.ttddyy</groupId>
<artifactId>datasource-proxy</artifactId>
<version>1.10</version>
</dependency>
然后,在 application.properties 或 application.yml 中配置数据源代理的相关属性,例如在 application.yml 中:
spring:
datasource:
url: jdbc:mysql://localhost:3306/your_database
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
connection-timeout: 30000
maximum-pool-size: 10
proxy:
log:
level: DEBUG # 设置日志级别
slowQueryThreshold: 500 # 设置慢查询阈值为500ms
启动应用后,所有通过 JDBC 执行的 SQL 语句及其执行耗时都将记录在日志中。
使用 Druid 连接池
Druid 是一个功能强大的数据库连接池,它自带的监控工具 Druid Monitor 可以方便地查看 SQL 执行时间等监控信息。在 pom.xml 中引入 Druid 的依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.16</version>
</dependency>
在 application.yml 中进行相关配置:
spring:
datasource:
druid:
url: jdbc:mysql://localhost:3306/your_database
username: your_username
password: your_password
driver-class-name: com.mysql.cj.jdbc.Driver
initial-size: 5
min-idle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,slf4j # 启用统计和日志过滤
connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=500
配置完成后,通过访问 Druid Monitor 的地址(默认是/druid,具体可根据配置调整),输入用户名和密码(默认都是 admin,也可在配置中修改),就可以在监控页面查看 SQL 执行时间、慢 SQL 等详细信息。
使用 p6spy
p6spy是一个强大的 SQL 监控工具,它可以拦截 JDBC 调用,记录 SQL 执行时间等信息。在 pom.xml 中引入p6spy的依赖:
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.9.1</version>
</dependency>
在 resource 目录下新建spy.properties文件,进行相关配置:
# 模块配置
modulelist=com.p6spy.engine.spy.P6SpyFactory,com.p6spy.engine.logging.appender.StdoutLogger
# 日志格式
logMessageFormat=com.p6spy.engine.spy.appender.MessageFormattingStrategyImpl
# 开启慢SQL记录,阈值为500ms
appender=com.p6spy.engine.logging.appender.FileLogger
# 日志文件路径
logfile=spy.log
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 真实JDBC驱动
driverlist=com.mysql.cj.jdbc.Driver
启动项目后,p6spy就会对 SQL 执行时间进行记录并输出到spy.log文件中。
通过以上几种方法,我们就可以在 Spring Boot3 项目中方便地记录 MyBatis 执行 SQL 的时间。无论是使用 AOP、MyBatis 拦截器,还是数据源代理、Druid 连接池、p6spy,都各有优势。大家可以根据项目的实际需求和特点,选择最适合的方式。
如果你在实际操作过程中,使用了这些方法,或者还有其他更好的记录 SQL 执行时间的技巧,欢迎在评论区分享交流!也别忘了点赞、收藏这篇文章,方便后续回顾和学习,一起提升后端开发技能,打造更高效的系统!