在持久层框架的使用过程中,我们经常需要对SQL执行结果进行二次处理。MyBatis作为Java生态中最流行的ORM框架之一,其拦截器机制为我们提供了灵活的扩展点。其中ResultSetHandler拦截器是处理查询结果集的关键环节,掌握它的实战用法能够解决很多实际开发中的痛点问题。
我曾在多个数据敏感型项目中深度使用这个技术,比如在金融系统中对金额字段自动格式化、在医疗系统中对患者隐私数据自动脱敏。通过拦截器实现这些功能,不仅避免了重复代码,还保证了数据处理逻辑的一致性。下面我就结合这些实战经验,详细解析ResultSetHandler拦截器的实现原理和典型应用场景。
MyBatis提供了四种核心拦截器接口:
ResultSetHandler主要负责在SQL执行后,将ResultSet结果集转换为Java对象的过程。与其他拦截器相比,它的特点是:
当执行查询操作时,ResultSetHandler的拦截过程如下:
关键拦截点是handleResultSets方法,我们可以在这个阶段对结果集进行各种定制化处理。
首先创建一个基础的拦截器类:
java复制@Intercepts({
@Signature(type = ResultSetHandler.class,
method = "handleResultSets",
args = {Statement.class})
})
public class CustomResultInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 前置处理
System.out.println("Before processing result sets");
// 执行原有逻辑
Object result = invocation.proceed();
// 后置处理
if (result instanceof List) {
return processResultList((List) result);
}
return result;
}
private List processResultList(List list) {
// 结果集处理逻辑
return list.stream()
.map(this::processSingleResult)
.collect(Collectors.toList());
}
}
在MyBatis配置文件中注册拦截器:
xml复制<plugins>
<plugin interceptor="com.example.CustomResultInterceptor">
<!-- 可配置拦截器参数 -->
<property name="enable" value="true"/>
</plugin>
</plugins>
或者在Spring Boot中通过配置类注册:
java复制@Bean
public CustomResultInterceptor resultInterceptor() {
CustomResultInterceptor interceptor = new CustomResultInterceptor();
interceptor.setProperties(new Properties());
return interceptor;
}
java复制private Object processSingleResult(Object obj) {
if (obj instanceof User) {
User user = (User) obj;
user.setPhoneNumber(maskPhone(user.getPhoneNumber()));
user.setIdNumber(maskIdNumber(user.getIdNumber()));
}
return obj;
}
private String maskPhone(String phone) {
return phone.replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");
}
java复制private Object processSingleResult(Object obj) {
if (obj instanceof Product) {
Product product = (Product) obj;
// 价格单位转换(分->元)
product.setPrice(product.getPrice() / 100.0);
// 日期格式标准化
product.setCreateTime(formatDate(product.getCreateTime()));
}
return obj;
}
实现基于用户权限的动态字段返回控制:
java复制public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
User currentUser = SecurityContext.getCurrentUser();
if (result instanceof List) {
return ((List) result).stream()
.map(item -> filterFields(item, currentUser))
.collect(Collectors.toList());
}
return filterFields(result, currentUser);
}
private Object filterFields(Object obj, User user) {
if (obj instanceof Employee) {
Employee employee = (Employee) obj;
if (!user.hasRole("HR")) {
employee.setSalary(null);
employee.setBonus(null);
}
}
return obj;
}
java复制private CacheManager cacheManager;
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
CacheKey cacheKey = createCacheKey(ms, invocation.getArgs());
Object cached = cacheManager.get(cacheKey);
if (cached != null) {
return cached;
}
Object result = invocation.proceed();
cacheManager.put(cacheKey, result);
return result;
}
拦截器未生效:
类型转换异常:
循环引用问题:
可以定义自定义注解来实现更精细化的控制:
java复制@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MaskField {
MaskType value() default MaskType.DEFAULT;
}
public enum MaskType {
PHONE, ID_CARD, EMAIL, DEFAULT
}
然后在拦截器中根据注解进行处理:
java复制Field[] fields = obj.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(MaskField.class)) {
MaskType type = field.getAnnotation(MaskField.class).value();
// 根据不同类型进行脱敏处理
}
}
在Spring环境中,可以结合AOP实现更强大的功能:
java复制@Aspect
@Component
public class ResultInterceptorAspect {
@Autowired
private CustomResultInterceptor interceptor;
@Around("execution(* com.example.mapper.*.*(..))")
public Object aroundQuery(ProceedingJoinPoint pjp) throws Throwable {
Object result = pjp.proceed();
return interceptor.processResult(result);
}
}
这种组合方式既保留了MyBatis拦截器的灵活性,又获得了Spring AOP的强大功能。