百度真题: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圣经
查看9道真题和解析
