百度真题: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圣经

全部评论
坐标南京,OD岗位多多,欢迎私聊
点赞 回复 分享
发布于 08-23 15:39 贵州

相关推荐

不愿透露姓名的神秘牛友
09-11 13:00
投递长江存储等公司10个岗位
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务