利用Mybatis拦截器,全局处理入库字段

2022-09-21 10:12:18 浏览数 (1)

利用Mybatis拦截器,全局处理入库字段

场景

需要对某张表的个别字段删除全部空格、替换半角括号,但是项目里入口比较多,不止有前端录入,还有接口接收的数据。即使现在全部入口处理了,后续新增入口也不能保证。所以需要统一处理,一劳永逸。

实现

EnableCustomInterceptor

标识实体类入库时会使用自定义拦截器(mybatis)

代码语言:javascript复制
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableCustomInterceptor {
}

DeleteWhitespace

  • 标识字段,入库时会去除首尾空格
  • 生效条件
    1. 实体类需要使用注解{@link EnableCustomInterceptor};
    2. Mapper方法的参数必须包含实体类对象(可嵌套到集合中)
  • 通过mybatis拦截器实现{@link DeleteWhitespaceFieldInterceptor}
代码语言:javascript复制
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DeleteWhitespace {
}

ReplaceHalfWidthChars

  • 标识字段,入库时会替换半角括号为全角
  • 生效条件
    1. 实体类需要使用注解{@link EnableCustomInterceptor};
    2. Mapper方法的参数必须包含实体类对象(可嵌套到集合中)
  • 通过mybatis拦截器实现{@link ReplaceHalfWidthCharsFieldInterceptor}
代码语言:javascript复制
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ReplaceHalfWidthChars {
}

AbstractFieldInterceptor

字段拦截器基类

代码语言:javascript复制
public abstract class AbstractFieldInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement mappedStatement = (MappedStatement) args[0];
        Object sqlParams = args[1];

        if (mappedStatement.getSqlCommandType() == SqlCommandType.INSERT
            || mappedStatement.getSqlCommandType() == SqlCommandType.UPDATE
        ) {
            final Class<?> entityClass = getMapperGenericClass(mappedStatement);
            if (entityClass.getAnnotation(EnableCustomInterceptor.class)!=null) {
                traverseParam(sqlParams, entityClass);
            }
        }
        return invocation.proceed();
    }

    /**
     * 拦截字段
     *
     * @param param
     * @param entityClass
     * @author yuyouyang
     * @date 2021/12/23 11:15
     * @version 1.0
     */
    protected abstract void interceptField(Object param, Class<?> entityClass);

    /**
     * 递归遍历参数中的实体类对象
     *
     * @param param
     * @param entityClass
     * @author yuyouyang
     * @date 2021/12/23 9:58
     * @version 1.0
     */
    protected void traverseParam(Object param, Class<?> entityClass) {
        if (param == null) {
            return;
        }

        if (param.getClass().getAnnotation(EnableCustomInterceptor.class) != null) {
            interceptField(param, entityClass);
        }else if (param instanceof Map) {
            final Map map = (Map) param;
            for (Object value : map.values()) {
                traverseParam(value, entityClass);
            }
        } else if (param instanceof Collection) {
            final Collection collection = (Collection) param;
            for (Object item : collection) {
                traverseParam(item, entityClass);
            }
        }
    }

    /**
     * 从MappedStatement拿到其集成的BaseMapper的泛型
     * <pre>
     *     DemoMapper extends BaseMapper<Demo, String>
     * </pre>
     * 此处获取的就是Demo的类
     *
     * copy from {@link AutoCompleteTimestampInterceptor#getMapperGenericClass(MappedStatement)}
     *
     * @param ms ms
     * @return BaseMapper的泛型
     */
    protected Class<?> getMapperGenericClass(MappedStatement ms) {
        boolean isJavaAnnotationMapper = ms.getResource().contains(".java");
        if (isJavaAnnotationMapper) {
            Class mapper;
            String mapperName = ms.getResource()
                                  .substring(0, ms.getResource().indexOf(".java")).replaceAll("/", "\.");
            try {
                mapper = Class.forName(mapperName);
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException("找不到指定的mapper: "   mapperName);
            }
            ParameterizedType superInterface = (ParameterizedType) mapper.getGenericInterfaces()[0];
            return (Class) superInterface.getActualTypeArguments()[0];
        } else {
            throw new UnsupportedOperationException("不支持使用XML方式构造Mapper!");
        }
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
    }
}
  • intercept(Invocation invocation):关键方法,拦截作用,拦截的时机根据子类上的@Intercepts决定
    • 忽略删除操作
  • traverseParam():遍历参数,因为项目里实际使用mybatis时,参数可能存到Map、List里。
  • interceptField:模板模式,交给子类实现

DeleteWhitespaceFieldInterceptor

代码语言:javascript复制
@ConditionalOnClass(value = org.apache.ibatis.plugin.Interceptor.class)
@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
@Slf4j
public class DeleteWhitespaceFieldInterceptor extends AbstractFieldInterceptor {

    /**
     * 对具有 {@link DeleteWhitespace} 注解的字段进行DeleteWhitespace
     *
     * @param sqlParams
     * @param entityClass
     * @author yuyouyang
     * @date 2021/12/23 10:00
     * @version 2.12.3
     */
    @Override
    protected void interceptField(Object sqlParams, Class<?> entityClass) {
        Arrays.stream(ArteryClassUtil.getModelFields(entityClass))
              .filter(field-> field.getAnnotation(ReplaceHalfWidthChars.class) != null)
              .forEach(field->{
                  if (field.getType() == String.class) {
                      try {
                          ReflectionUtils.makeAccessible(field);
                          Object oldValue = field.get(sqlParams);
                          field.set(sqlParams, StringUtils.deleteWhitespace((String) oldValue));
                      } catch (IllegalAccessException e) {
                          log.error("Mybatis拦截器处理字段空格异常,class:{},field:{}", entityClass.getName(), field.getName(), e);
                      }
                  } else {
                      log.warn("Mybatis拦截器处理字段空格失败,字段类型不是String,class:{},field:{}", entityClass.getName(), field.getName());
                  }
              });
    }
}
  • 记得做类型校验

ReplaceHalfWidthCharsFieldInterceptor

代码语言:javascript复制
@ConditionalOnClass(value = org.apache.ibatis.plugin.Interceptor.class)
@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
@Slf4j
public class ReplaceHalfWidthCharsFieldInterceptor extends AbstractFieldInterceptor{

    /**
     * 对具有 {@link ReplaceHalfWidthChars} 注解的字段进行替换半角括号为全角
     *
     * @param sqlParams
     * @param entityClass
     * @author yuyouyang
     * @date 2021/12/23 10:00
     * @version 2.12.3
     */
    @Override
    protected void interceptField(Object sqlParams, Class<?> entityClass) {
        Arrays.stream(ArteryClassUtil.getModelFields(entityClass))
              .filter(field->field.getAnnotation(ReplaceHalfWidthChars.class) != null)
              .forEach(field->{
                  if (field.getType() == String.class) {
                      try {
                          ReflectionUtils.makeAccessible(field);
                          Object oldValue = field.get(sqlParams);
                          final String newValue = StringUtils.replaceEach((String) oldValue,
                                                                          new String[]{"(", ")"},
                                                                          new String[]{"(", ")"});
                          field.set(sqlParams, newValue);
                      } catch (IllegalAccessException e) {
                          log.error("Mybatis拦截器处理字段替换半角括号为全角异常,class:{},field:{}", entityClass.getName(), field.getName(), e);
                      }
                  } else {
                      log.warn("Mybatis拦截器处理字段替换半角括号失败,字段类型不是String,class:{},field:{}", entityClass.getName(), field.getName());
                  }
              });
    }
}

0 人点赞