百度真题:MyBatis插件开发实战
面试重要程度:⭐⭐⭐⭐⭐
真题来源:百度2024秋招技术面试
考察重点:插件机制原理、拦截器开发、实际应用场景
预计阅读时间:35分钟
真题背景
面试官: "我看你简历上写熟悉MyBatis,那你能不能给我们开发一个MyBatis插件,用来监控SQL执行时间,并且对慢SQL进行告警?请详细说说插件的实现原理和具体代码。"
考察意图:
- 对MyBatis插件机制的深度理解
- 实际编码能力和问题解决能力
- 对生产环境监控的实践经验
- 代码设计和架构思维
🎯 插件机制原理深度解析
拦截器工作原理
核心概念:
// MyBatis插件本质上是一个拦截器 // 基于JDK动态代理实现,可以拦截四大核心对象的方法调用 /** * 四大核心对象: * 1. Executor - 执行器,执行SQL语句 * 2. StatementHandler - 语句处理器,处理SQL语句 * 3. ParameterHandler - 参数处理器,处理SQL参数 * 4. ResultSetHandler - 结果集处理器,处理查询结果 */ // 插件接口定义 public interface Interceptor { // 拦截目标方法的调用 Object intercept(Invocation invocation) throws Throwable; // 生成目标对象的代理 default Object plugin(Object target) { return Plugin.wrap(target, this); } // 设置插件属性 default void setProperties(Properties properties) { // 空实现 } }
🚀 SQL执行时间监控插件实现
核心监控插件
/** * SQL执行时间监控插件 * 功能: * 1. 监控所有SQL执行时间 * 2. 慢SQL告警(可配置阈值) * 3. SQL执行统计 * 4. 异常SQL记录 */ @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}) }) @Component @Slf4j public class SqlExecutionTimeInterceptor implements Interceptor { // 慢SQL阈值(毫秒),默认1秒 private long slowSqlThreshold = 1000; // 是否启用详细日志 private boolean enableDetailLog = true; // SQL统计信息 private final ConcurrentHashMap<String, SqlStatistics> sqlStatisticsMap = new ConcurrentHashMap<>(); // 告警服务 @Autowired(required = false) private AlertService alertService; @Override public Object intercept(Invocation invocation) throws Throwable { long startTime = System.currentTimeMillis(); String sqlId = null; String sql = null; Object parameter = null; try { // 获取SQL信息 MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; parameter = invocation.getArgs()[1]; sqlId = mappedStatement.getId(); // 获取实际执行的SQL BoundSql boundSql = mappedStatement.getBoundSql(parameter); sql = boundSql.getSql(); if (enableDetailLog) { log.info("开始执行SQL - ID: {}, SQL: {}", sqlId, formatSql(sql)); } // 执行原方法 Object result = invocation.proceed(); // 计算执行时间 long endTime = System.currentTimeMillis(); long executionTime = endTime - startTime; // 记录执行成功 recordSqlExecution(sqlId, sql, parameter, executionTime, true, null); // 检查是否为慢SQL if (executionTime > slowSqlThreshold) { handleSlowSql(sqlId, sql, parameter, executionTime); } if (enableDetailLog) { log.info("SQL执行完成 - ID: {}, 耗时: {}ms", sqlId, executionTime); } return result; } catch (Exception e) { long endTime = System.currentTimeMillis(); long executionTime = endTime - startTime; // 记录执行失败 recordSqlExecution(sqlId, sql, parameter, executionTime, false, e); log.error("SQL执行异常 - ID: {}, 耗时: {}ms, SQL: {}, 异常: {}", sqlId, executionTime, formatSql(sql), e.getMessage(), e); // 发送异常告警 if (alertService != null) { alertService.sendSqlErrorAlert(sqlId, sql, executionTime, e); } throw e; } } /** * 处理慢SQL */ private void handleSlowSql(String sqlId, String sql, Object parameter, long executionTime) { log.warn("慢SQL告警 - ID: {}, 耗时: {}ms, SQL: {}, 参数: {}", sqlId, executionTime, formatSql(sql), formatParameter(parameter)); // 发送慢SQL告警 if (alertService != null) { alertService.sendSlowSqlAlert(sqlId, sql, parameter, executionTime); } // 记录慢SQL到数据库或文件 recordSlowSql(sqlId, sql, parameter, executionTime); } /** * 记录SQL执行统计 */ private void recordSqlExecution(String sqlId, String sql, Object parameter, long executionTime, boolean success, Exception exception) { sqlStatisticsMap.compute(sqlId, (key, statistics) -> { if (statistics == null) { statistics = new SqlStatistics(sqlId, sql); } statistics.addExecution(executionTime, success); if (!success && exception != null) { statistics.addException(exception); } return statistics; }); } /** * 格式化SQL */ private String formatSql(String sql) { if (sql == null) { return ""; } return sql.replaceAll("\\s+", " ").trim(); } /** * 格式化参数 */ private String formatParameter(Object parameter) { if (parameter == null) { return "null"; } try { if (parameter instanceof String || parameter instanceof Number) { return parameter.toString(); } else { // 使用JSON序列化复杂对象,但限制长度 String json = JsonUtils.toJson(parameter); return json.length() > 500 ? json.substring(0, 500) + "..." : json; } } catch (Exception e) { return parameter.getClass().getSimpleName() + "@" + parameter.hashCode(); } } @Override public Object plugin(Object target) { // 只拦截Executor类型的对象 if (target instanceof Executor) { return Plugin.wrap(target, this); } return target; } @Override public void setProperties(Properties properties) { // 从配置文件读取参数 String threshold = properties.getProperty("slowSqlThreshold"); if (StringUtils.isNotBlank(threshold)) { this.slowSqlThreshold = Long.parseLong(threshold); } String enableDetail = properties.getProperty("enableDetailLog"); if (StringUtils.isNotBlank(enableDetail)) { this.enableDetailLog = Boolean.parseBoolean(enableDetail); } log.info("SQL监控插件配置 - 慢SQL阈值: {}ms, 详细日志: {}", slowSqlThreshold, enableDetailLog); } }
统计信息数据结构
/** * SQL执行统计信息 */ @Data public class SqlStatistics { private String sqlId; private String sql; private long totalExecutions; // 总执行次数 private long successExecutions; // 成功执行次数 private long failedExecutions; // 失败执行次数 private long totalExecutionTime; // 总执行时间 private long minExecutionTime; // 最小执行时间 private long maxExecutionTime; // 最大执行时间 private double avgExecutionTime; // 平均执行时间 private LocalDateTime firstExecutionTime; // 首次执行时间 private LocalDateTime lastExecutionTime; // 最后执行时间 private List<String> recentExceptions; // 最近的异常信息 public SqlStatistics(String sqlId, String sql) { this.sqlId = sqlId; this.sql = sql; this.totalExecutions = 0; this.successExecutions = 0; this.failedExecutions = 0; this.totalExecutionTime = 0; this.minExecutionTime = Long.MAX_VALUE; this.maxExecutionTime = 0; this.recentExceptions = new ArrayList<>(); } /** * 添加执行记录 */ public synchronized void addExecution(long executionTime, boolean success) { this.totalExecutions++; this.totalExecutionTime += executionTime; if (success) { this.successExecutions++; } else { this.failedExecutions++; } // 更新最小最大执行时间 this.minExecutionTime = Math.min(this.minExecutionTime, executionTime); this.maxExecutionTime = Math.max(this.maxExecutionTime, executionTime); // 计算平均执行时间 this.avgExecutionTime = (double) this.totalExecutionTime / this.totalExecutions; // 更新执行时间 LocalDateTime now = LocalDateTime.now(); if (this.firstExecutionTime == null) { this.firstExecutionTime = now; } this.lastExecutionTime = now; } /** * 添加异常信息 */ public synchronized void addException(Exception exception) { String exceptionInfo = exception.getClass().getSimpleName() + ": " + exception.getMessage(); // 只保留最近10个异常 if (recentExceptions.size() >= 10) { recentExceptions.remove(0); } recentExceptions.add(exceptionInfo); } /** * 获取成功率 */ public double getSuccessRate() { if (totalExecutions == 0) { return 0.0; } return (double) successExecutions / totalExecutions * 100; } }
告警服务实现
/** * SQL告警服务 */ public interface AlertService { /** * 发送慢SQL告警 */ void sendSlowSqlAlert(String sqlId, String sql, Object parameter, long executionTime); /** * 发送SQL异常告警 */ void sendSqlErrorAlert(String sqlId, String sql, long executionTime, Exception exception); } /** * 告警服务实现 */ @Service @Slf4j public class AlertServiceImpl implements AlertService { @Autowired(required = false) private DingTalkService dingTalkService; @Value("${sql.monitor.alert.enabled:true}") private boolean alertEnabled; @Override public void sendSlowSqlAlert(String sqlId, String sql, Object parameter, long executionTime) { if (!alertEnabled) { return; } try { String message = buildSlowSqlAlertMessage(sqlId, sql, parameter, executionTime); // 发送钉钉告警 if (dingTalkService != null) { dingTalkService.sendMessage("慢SQL告警", message); } } catch (Exception e) { log.error("发送慢SQL告警失败", e); } } @Override public void sendSqlErrorAlert(String sqlId, String sql, long executionTime, Exception exception) { if (!alertEnabled) { return; } try { String message = buildSqlErrorAlertMessage(sqlId, sql, executionTime, exception); if (dingTalkService != null) { dingTalkService.sendMessage("SQL异常告警", message); } } catch (Exception e) { log.error("发送SQL异常告警失败", e); } } private String buildSlowSqlAlertMessage(String sqlId, String sql, Object parameter, long executionTime) { StringBuilder sb = new StringBuilder(); sb.append("🐌 慢SQL告警\n"); sb.append("执行时间: ").append(executionTime).append("ms\n"); sb.append("SQL ID: ").append(sqlId).append("\n"); sb.append("SQL语句: ").append(sql).append("\n"); sb.append("参数: ").append(parameter).append("\n"); sb.append("时间: ").append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); return sb.toString(); } private String buildSqlErrorAlertMessage(String sqlId, String sql, long executionTime, Exception exception) { StringBuilder sb = new StringBuilder(); sb.append("❌ SQL异常告警\n"); sb.append("执行时间: ").append(executionTime).append("ms\n"); sb.append("SQL ID: ").append(sqlId).append("\n"); sb.append("SQL语句: ").append(sql).append("\n"); sb.append("异常信息: ").append(exception.getMessage()).append("\n"); sb.append("异常类型: ").append(exception.getClass().getSimpleName()).append("\n"); sb.append("时间: ").append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); return sb.toString(); } }
🔧 插件配置与注册
Spring Boot集成配置
@Configuration public class MyBatisPluginConfig { @Bean public SqlExecutionTimeInterceptor sqlExecutionTimeInterceptor() { return new SqlExecutionTimeInterceptor(); } @Bean public ConfigurationCustomizer configurationCustomizer(SqlExecutionTimeInterceptor sqlInterceptor) { return configuration -> { // 注册SQL监控插件 configuration.addInterceptor(sqlInterceptor); // 设置插件属性 Properties properties = new Properties(); properties.setProperty("slowSqlThreshold", "1000"); properties.setProperty("enableDetailLog", "true"); sqlInterceptor.setProperties(properties); }; } }
监控管理接口
/** * SQL监控管理接口 */ @RestController @RequestMapping("/api/sql-monitor") public class SqlMonitorController { @Autowired private SqlExecutionTimeInterceptor sqlInterceptor; @GetMapping("/statistics") public Result<Map<String, SqlStatistics>> getStatistics() { Map<String, SqlStatistics> statistics = sqlInterceptor.getSqlStatistics(); return Result.success(statistics); } @GetMapping("/slow-sql") public Result<List<SqlStatistics>> getSlowSqlStatistics() { List<SqlStatistics> slowSqls = sqlInterceptor.getSlowSqlStatistics(); return Result.success(slowSqls); } @PostMapping("/clear-statistics") public Result<Void> clearStatistics() { sqlInterceptor.clearStatistics(); return Result.success(); } }
💡 面试回答要点
标准回答框架
第一部分:原理阐述
"MyBatis插件基于JDK动态代理实现,可以拦截四大核心对象的方法调用。 我设计的SQL监控插件主要拦截Executor的query和update方法, 通过@Intercepts注解指定拦截点,在intercept方法中实现监控逻辑。"
第二部分:技术实现
"插件的核心是记录SQL执行前后的时间戳,计算执行时间。 对于慢SQL,我设置了可配置的阈值,超过阈值就触发告警。 同时维护了一个ConcurrentHashMap来统计每个SQL的执行情况, 包括执行次数、平均耗时、成功率等指标。"
第三部分:生产应用
"在生产环境中,这个插件帮助我们及时发现性能问题。 通过钉钉告警,运维团队能够第一时间收到慢SQL通知。 统计功能让我们能够分析SQL性能趋势,指导数据库优化工作。"
扩展考点
1. 性能影响
面试官:"这个插件会不会影响系统性能?" 回答要点: - 插件本身开销很小,主要是时间戳记录 - 使用异步方式发送告警,不阻塞主流程 - 统计信息使用内存缓存,定期清理 - 可以通过配置开关控制功能启用
2. 扩展功能
面试官:"还能增加什么功能?" 回答要点: - SQL执行计划分析 - 参数绑定监控 - 连接池状态监控 - 自动SQL优化建议 - 与APM系统集成
本章核心要点:
- ✅ MyBatis插件机制原理和四大核心对象
- ✅ 完整的SQL监控插件实现
- ✅ 统计信息收集和告警机制
- ✅ Spring Boot集成配置
- ✅ 生产环境应用和性能考虑
总结: MyBatis插件开发体现了对框架深度理解和实际问题解决能力,是高级Java工程师必备技能
#java秋招面试#Java面试圣经 文章被收录于专栏
Java面试圣经