Mybatis字典枚举自动翻译

您所在的位置:网站首页 数据字典转换软件 Mybatis字典枚举自动翻译

Mybatis字典枚举自动翻译

2023-12-31 18:31| 来源: 网络整理| 查看: 265

背景

写crud业务的时候,枚举和字典处不论是前端处理还是后端处理都挺麻烦。前端翻译势必要得到所有的枚举,然后自己去找,后端还要给一个接口去查枚举。

究其原因就是数据设计的时候只存了code,没有存desc,存desc有的时候要经常变,导致旧数据错,也浪费空间

因此后端翻译是最正确,如何优雅的翻译枚举或者字典,是个问题。

方法

orm工具使用mybatis(我觉得手写sql在一些复杂场景还是很重要的),mybatis提供了强大的拦截器Intercepter,可以拦截sql执行之前,执行之后,映射完成之后等功能。我们常用的PageHelpler就是使用了拦截器,拦截sql执行之前,将原来要执行的sql,使用select count语句包装了一次。

下图摘自mybatis官网

image.png

因为mybatis提供了映射完成之后的拦截器,我们只需要使用拦截器得到最终结果,然后通过反射,去修改对象中的属性就可以了。

效果

User表中有一个status列,0表示关闭,1表示正常 image.png User Pojo类

public class User implements Serializable { private Integer id; private String name; private Date createDate; private transient Integer age; //使用自定义注解,标记字典为user_status,翻译的属性的statusText @DemoUtil.FieldBind(type = DemoDataBIndImpl.BindType.USER_STATUS,target = "statusText") private Integer status; //翻译的属性 private String statusText;

自动翻译了~ image.png

提示

MetaObject是mybatis提供一个反射操作工具,非常好用,mybatis需要反射操作的时候,都是用的它。

实现 定义一个注解 @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) public @interface FieldBind { String type(); //字典的分类 String target(); //翻译的目标属性 }

使用FieldProperty将注解和加注解的Field,封装一下

@Data @AllArgsConstructor class FieldProperty{ private String name;//属性名 private FieldBind fieldBind;//属性绑定的注解 } 字典来源

定一个DataBind,数据绑定接口

public interface DemoDataBind { /** * 使用反射对象MetaObject,操作目标对象Object * @param fieldBind 自定义的注解 * @param o 原始对象 * @param metaObject 原始对象的mybatis反射对象 */ void setMetaObject(FieldBind field, Object o, MetaObject metaObject); }

实现,这里的字典来源,这里是一个Map,可以在Spring启动的时候加载进来

public class DemoDataBIndImpl implements DemoDataBind{ public interface BindType { String USER_STATUS = "user_status"; } /** * 可以在系统启动的时候,将数据库的所有字典和java代码中的枚举缓存起来 * 分布式记得用redis,更新的时候记得要更新redis(一般很少更新字典) */ private Map STATUS_MAP = new ConcurrentHashMap() {{ put("0", "关闭"); put("1", "正常"); }}; @Override public void setMetaObject(DemoUtil.FieldBind fieldBind, Object fieldValue, MetaObject metaObject) { // 数据库中数据转换 if (BindType.USER_STATUS.equals(fieldBind.type())) { //使用反射工具类修改target属性,值从缓存中取到对应的结果 metaObject.setValue(fieldBind.target(), STATUS_MAP.get(String.valueOf(fieldValue))); } } } 拦截器

实现一个mybatis的拦截器,拦截对象是ResultSetHandler,拦截方法是handleResultSets

@Intercepts({@Signature( type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class} )}) public class DemoInterceptor implements Interceptor { private DemoDataBind demoDataBind;//字典数据绑定 public DemoInterceptor(DemoDataBind demoDataBind) { this.demoDataBind = demoDataBind; } @Override public Object intercept(Invocation invocation) throws Throwable { //一定是list结果 List proceed = (List) invocation.proceed(); if(proceed.isEmpty()) return proceed; else { //获取mybatis的Configuration,用于获得MetaData DefaultResultSetHandler resultSetHandler= (DefaultResultSetHandler) invocation.getTarget(); Field mappedStatement = resultSetHandler.getClass().getDeclaredField("mappedStatement"); mappedStatement.setAccessible(true); MappedStatement o = (MappedStatement) mappedStatement.get(resultSetHandler); Configuration configuration = o.getConfiguration(); // 迭代处理 Iterator iterator = proceed.iterator(); while (iterator.hasNext()){ Object next = iterator.next(); //检查是否需要翻译,是否需要翻译的标准是,检查目标对象的Class是否有自定义的注解, //有的话,调用字典数据绑定,取修改对象的target属性 if(null!=next && !DemoUtil.needTranslate(configuration,next, (m,f)->{ //得到自定义注解 DemoUtil.FieldBind fieldBind = f.getFieldBind(); //得到具体属性的值 Object value = m.getValue(f.getName()); demoDataBind.setMetaObject(fieldBind,value,m); } )){ //不需要翻译的话,跳过 break; } } return proceed; } } }

工具类,用于找属性

public class DemoUtil { private static Map> invalidClass; static { //缓存class和属性,不用每次都便利查找 fieldProMaps=new ConcurrentHashMap(); //不合法的class,有些class天生就不可能有我们自定义的注解,例如HashMap //如果第一个便利class属性之后发现没有自定义注解,也会被标记为不合法的class invalidClass=new CopyOnWriteArraySet(); invalidClass.add(HashMap.class);//不校验HashMap } /** * 是否需要翻译,通过寻找class上的自定义注解 */ public static boolean needTranslate(Configuration configuration, Object o, BiConsumer biConsumer){ //根据对象的 List fieldPropertyList = getFieldPropertyList(o.getClass()); if(!ObjectUtils.isEmpty(fieldPropertyList)){ //如果不为空的话,调用BiConsumer的apply了 //创建元数据对象(为什么要花很大功夫得到mybatis的Configuration?自己写反射不也可以完成吗?因为mybatis可能还有很多其他配置,自己可能写会丢失那些功能,这些配置都在Configuration里了,newMetaObject也会有缓存在其中) MetaObject metaObject = configuration.newMetaObject(o); //多线程处理,fork join fieldPropertyList.parallelStream().forEach(fieldProperty -> { biConsumer.accept(metaObject,fieldProperty); }); return true; } return false; } @Documented @Inherited @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE}) public @interface FieldBind { String type(); //字典类型 String target(); //目标属性 } @Data @AllArgsConstructor static class FieldProperty{ private String name; private FieldBind fieldBind; } /** * 找翻译注解 */ private static List getFieldPropertyList(Class c){ if(invalidClass.contains(c)){ //检查是否合法 return null; } //缓存检查 List fieldProperties = fieldProMaps.get(c); if(fieldProperties!=null) return fieldProperties; //获取到所有的属性 List allField = getAllField(c); //过滤出有FieldBind的属性,并且封装成FieldProperty List collect = allField.stream().filter(i -> { FieldBind annotation = i.getAnnotation(FieldBind.class); return annotation != null; }).map(i -> new FieldProperty(i.getName(), i.getAnnotation(FieldBind.class))) .collect(Collectors.toList()); //空的话,不合法 if(ObjectUtils.isEmpty(collect)) invalidClass.add(c); //不为空,存缓存 else fieldProMaps.put(c,collect); return collect; } /** * 找到所有field(这里没有处理父类的属性,可以自行修改) * @return */ private static List getAllField(Class c){ Field[] declaredFields = c.getDeclaredFields(); return Arrays.stream(declaredFields).filter((var0x) -> { //去除static属性 return !Modifier.isStatic(var0x.getModifiers()); }).filter((var0x) -> { //去除transient属性 return !Modifier.isTransient(var0x.getModifiers()); }).collect(Collectors.toList()); } 加载拦截器

我用的是springBoot,从容器里找到mybatis的sqlSessionFactory,调用增加拦截器的方法即可

//注入字典绑定的数据源,生成拦截器 //(这里的字典数据源也可以放在容器里,通过监听spring的启动,例如Spring的ApplicationReadyEvent //加载数据库和java枚举代码,存入缓存,自由发挥) DemoInterceptor demoInterceptor = new DemoInterceptor(new DemoDataBIndImpl()); //增加拦截器 sqlSessionFactory.getConfiguration().addInterceptor(demoInterceptor) 总结

mybatis提供的拦截器,可以在整个sql语句到映射成java对象过程中做很多事情,不仅仅是翻译枚举,和分页。还可以做例如脱敏之类,全凭想象。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3