MyBatis拦截器是框架提供的核心扩展点之一,它允许开发者在SQL执行的生命周期中插入自定义逻辑。ResultSetHandler拦截器作为其中最关键的一环,专门用于处理JDBC结果集到Java对象的转换过程。在实际项目中,我们经常需要在这个环节实现数据脱敏、字段映射修正、结果集缓存等需求。
重要提示:拦截器的实现必须严格遵守MyBatis的接口规范,错误的实现可能导致整个ORM流程崩溃。
MyBatis采用责任链模式管理拦截器,当执行SQL查询时,结果集处理流程会依次经过所有注册的ResultSetHandler拦截器。每个拦截器都可以对结果集进行以下操作:
java复制// 典型拦截器方法签名
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置处理逻辑
Object result = invocation.proceed();
// 后置处理逻辑
return processResult(result);
}
在Mybatis配置中,拦截器需要通过@Intercepts注解声明其拦截的目标方法。对于ResultSetHandler,最常用的拦截点是handleResultSets方法:
java复制@Intercepts({
@Signature(type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class})
})
public class CustomResultInterceptor implements Interceptor {
// 实现代码...
}
在金融、医疗等行业系统中,我们经常需要对查询结果中的敏感字段(如身份证号、手机号)进行实时脱敏处理。通过ResultSetHandler拦截器可以实现无侵入式的解决方案:
java复制public Object intercept(Invocation invocation) throws Throwable {
List<Object> results = (List<Object>) invocation.proceed();
return results.stream().map(item -> {
if (item instanceof User) {
User user = (User) item;
user.setIdCard(maskSensitiveInfo(user.getIdCard()));
}
return item;
}).collect(Collectors.toList());
}
实战经验:对于大型结果集,建议使用并行流处理以提高性能,但要注意线程安全问题。
当数据库字段命名规范与Java对象属性不一致时,除了使用MyBatis的@Result注解,我们还可以通过拦截器统一处理:
java复制private Object convertFieldNames(Object result) {
if (result instanceof Map) {
Map<String, Object> map = (Map<String, Object>) result;
Map<String, Object> newMap = new LinkedHashMap<>();
map.forEach((k, v) -> newMap.put(convertColumnToField(k), v));
return newMap;
}
return result;
}
对于热点数据,可以在ResultSetHandler层面实现二级缓存:
java复制public Object intercept(Invocation invocation) throws Throwable {
String cacheKey = buildCacheKey(invocation);
Object cached = cache.get(cacheKey);
if (cached != null) {
return cached;
}
Object result = invocation.proceed();
cache.put(cacheKey, result);
return result;
}
通过MetaObject可以动态获取和修改结果对象的属性值:
java复制MetaObject metaObject = SystemMetaObject.forObject(result);
for (String property : metaObject.getGetterNames()) {
Object value = metaObject.getValue(property);
// 处理属性值...
}
处理大批量结果时,需要注意内存消耗问题:
java复制// 分批次处理结果
int batchSize = 1000;
List<Object> results = (List<Object>) invocation.proceed();
for (int i = 0; i < results.size(); i += batchSize) {
List<Object> batch = results.subList(i, Math.min(i + batchSize, results.size()));
processBatch(batch);
}
对于存储过程返回的多个结果集,需要特殊处理:
java复制if (result instanceof List) {
List<?> resultList = (List<?>) result;
if (!resultList.isEmpty() && resultList.get(0) instanceof List) {
// 处理多结果集情况
}
}
java复制try {
return invocation.proceed();
} catch (ClassCastException e) {
throw new RuntimeException("结果类型转换失败,请检查拦截器返回值", e);
}
java复制Set<Object> processedObjects = new IdentityHashSet<>();
if (!processedObjects.add(result)) {
return result; // 已处理过的对象直接返回
}
建议为拦截器添加性能统计:
java复制long start = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long cost = System.currentTimeMillis() - start;
if (cost > 100) { // 超过100ms的记录警告
log.warn("ResultSet处理耗时: {}ms", cost);
}
}
当同时使用PageHelper等插件时,需要注意执行顺序问题。可以通过@Order注解或配置顺序控制拦截器链的执行优先级。
对于需要处理复杂映射的场景,建议结合TypeHandler一起使用。在实际项目中,我曾通过组合使用ResultSetHandler拦截器和自定义TypeHandler,成功将遗留系统的数据转换逻辑从业务代码中完全解耦,使核心业务代码的可读性提升了40%以上。