利用Mybatis拦截器,全局处理入库字段
场景
需要对某张表的个别字段删除全部空格、替换半角括号,但是项目里入口比较多,不止有前端录入,还有接口接收的数据。即使现在全部入口处理了,后续新增入口也不能保证。所以需要统一处理,一劳永逸。
实现
EnableCustomInterceptor
标识实体类入库时会使用自定义拦截器(mybatis)
代码语言:javascript复制@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface EnableCustomInterceptor {
}
DeleteWhitespace
- 标识字段,入库时会去除首尾空格
- 生效条件
- 实体类需要使用注解{@link EnableCustomInterceptor};
- Mapper方法的参数必须包含实体类对象(可嵌套到集合中)
- 通过mybatis拦截器实现{@link DeleteWhitespaceFieldInterceptor}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DeleteWhitespace {
}
ReplaceHalfWidthChars
- 标识字段,入库时会替换半角括号为全角
- 生效条件
- 实体类需要使用注解{@link EnableCustomInterceptor};
- Mapper方法的参数必须包含实体类对象(可嵌套到集合中)
- 通过mybatis拦截器实现{@link ReplaceHalfWidthCharsFieldInterceptor}
@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());
}
});
}
}