百度真题: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(); }
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
Java面试圣经 文章被收录于专栏
Java面试圣经,带你练透java圣经