使用注解实现数据字典翻译

2022/4/27 23:15:06

本文主要是介绍使用注解实现数据字典翻译,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言

在日常开发中查询单表的情况非常多。这时总会出现表里存的是编码(如部门编号),但却要返回对应的描述(如部门名称)。

通常一般思路是在 Service 进行关联查询或依赖组件完成。比如 Mybatis 中用 join 语句将 sql 写死,比如 JPA 中在实体类属性字段加上@ManyToOne注解,直接将对象组合起来。

private String orgId;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "orgId", insertable = false, updatable = false)
private BaseOrg baseOrg;

上面的方式固然简单直接,但是我觉得还不够快,而且过度依赖组件在以后的修改中也会比较麻烦,接下来就由我来提供一种新的思路。

思路

无论是列表还是单个查询,本质上是先找到编码,再去找对应描述,首要条件就是:顺序不能颠倒,我们不能进行预判。所以我们的任务就像一条流水线一样,得到数据进行查询,再返回填充。如果是列表,那就遍历一遍,时间复杂度O(n)。

而这样一个过程其实是非常模范的,容易提炼出来。我起先的思路是结合Spring的切面来做,可深度考虑后发现切面只能针对方法的调用,而方法的返回值有很多种,单个对象、List以及IPage分页等。放在 set() 方法上也没有办法得到该 set() 对应的实体再填充。后面转换思路写为工具类在所需要的地方进行调用,一切都简单了不少。比如加入到 MP 的分页转换过程中( IPage <PO> to IPage <VO>)。

工具类的思路确定了。我们剩下还需要的。1是查询对应的编码所需要的单表查询Service,2是填充的属性名称(如果是Json动态添加一个JsonElement就不需要在VO再加一个属性,但考虑到我们的业务层或许也需要该字段,就添个属性用来存放)。接下来就开工。

实现

注解

  • @Dict 包含填充目标属性,和调用的service
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
public @interface Dict {
  String target();
  String service();
}
  • 视图对象VO
@Data
@NoArgsConstructor
@ApiModel(value = "员工VO", description = "EmployeeVO")
public class EmployeeVO implements Serializable {
  /** 名称 */
  @ApiModelProperty("名称")
  private String name;
  /** 编号 */
  @ApiModelProperty("编号")
  private String number;
  /** 所属机构代码 */
  @ApiModelProperty("所属机构代码")
  @Dict(target = "orgName", service = "baseOrgService")
  private String orgCode;
  /** 所属机构 */
  @ApiModelProperty("所属机构")
  private String orgName;

  public EmployeeVO(EmployeePO po) {
    this.name = po.getName();
    this.number = po.getNumber();
    this.orgCode = po.getOrgCode();
  }
}
  • 进行翻译的工具类
@Slf4j
public class BeanHelpUtils {
/// 主要代码 ⬇️
  public static <T> void translation(T t)
      throws IntrospectionException, InvocationTargetException, IllegalAccessException {
    Field[] fields = t.getClass().getDeclaredFields();
    for (Field field : fields) {
      if (field.isAnnotationPresent(Dict.class)) {
        String target = field.getAnnotation(Dict.class).target();
        String service = field.getAnnotation(Dict.class).service();
        DictService dictService = SpringContextUtil.getBean(service, DictService.class);
        if (dictService != null) {
          PropertyDescriptor source = new PropertyDescriptor(field.getName(), t.getClass());
          Object invoke = source.getReadMethod().invoke(t);
          if (invoke instanceof String) {
            Object result = dictService.getValue((String) invoke);
            PropertyDescriptor targetResult = new PropertyDescriptor(target, t.getClass());
            targetResult.getWriteMethod().invoke(t, result);
          }
        }
      }
    }
  }
/// 主要代码 ⬆️
  public static <T> void translation(List<T> collect) {
    for (T t : collect) {
      try {
        translation(t);
      } catch (IntrospectionException | InvocationTargetException | IllegalAccessException e) {
        if (log.isInfoEnabled()) log.info(e.getMessage());
        e.printStackTrace();
      }
    }
  }
  /** 分页复制 */
  public static <T, E> IPage<T> pageTransform(IPage<E> page, Function<E, T> sup) {
    if (page == null || page.getRecords() == null) return null;
    List<T> collect = page.getRecords().stream().map(sup).collect(Collectors.toList());
    translation(collect);
    return new Page<T>(page.getCurrent(), page.getSize(), page.getTotal()).setRecords(collect);
  }
}
  • 为了能进行统一调用,写了一个 DictService 字典接口
public interface DictService {
  Object getValue(String key);
}
  • 实现了字典接口的 OrgService
@Service
public class BaseOrgService extends ServiceImpl<BaseOrgMapper, BaseOrgPO> implements DictService {
  @Override
  public Object getValue(String orgCode) {
    BaseOrgPO po =
        baseMapper.selectOne(
            new QueryWrapper<BaseOrgPO>()
                .lambda()
                .eq(BaseOrgPO::getOrgCode, orgCode)
                .last("LIMIT 1"));
    if (po == null) return null;
    return po.getOrgName();
  }
}
  • 获取 Bean 的工具类,使用了 Spring 的 ApplicationContextAware
@Configuration
public class SpringContextUtil implements ApplicationContextAware {

  public static ApplicationContext applicationContext;

  @Override
  public void setApplicationContext(@NonNull ApplicationContext applicationContext)
      throws BeansException {
    SpringContextUtil.applicationContext = applicationContext;
  }

  public static ApplicationContext getApplicationContext() {
    return applicationContext;
  }

  public static String getProperty(String path) {
    return applicationContext.getEnvironment().getProperty(path);
  }

  public static Object getBean(String name) throws BeansException {
    if (applicationContext == null) return null;
    return applicationContext.getBean(name);
  }

  public static <T> T getBean(String name, Class<T> requiredType) throws BeansException {
    if (applicationContext == null) return null;
    return applicationContext.getBean(name, requiredType);
  }
}
  • 在所需要的地方应用
// 调用分页转换,自动翻译
...
IPage<EmployeePO> poPage = employeeMapper.selectPage(page, new QueryWrapper<EmployeePO>().lambda()
                .eq(...
return BeanHelpUtils.pageTransform(poPage, EmployeeVO::new);
// 或直接调用翻译
List<EmployeeVO> records = poPage.getRecords();
BeanHelpUtils.translation(records);
return records;

数据字典缓存

由于我一开始提到的是数据字典,其实数据字典通常是一张或者两张表,用来存放编码和对应值,如:

A表存放:

key value
gender 性别

B表存放:

key value display
gender 1
gender 2

(只留一张 B 表也行,可根据数据复杂度而定)

最后通过单表的 value 值和名称 gender 来进行数据字典表查找。

有时候数据字典会叫别的名字,如标准码、标准代码,多见于专业领域。

由于数据字典表的特性,在写入之后,很少会去修改,非常适合结合Redis来进行缓存,提高查询数据。

我们可以在对应数据字典的 Service 层接入 Spring Cache + Redis 来缓存。

(完)



这篇关于使用注解实现数据字典翻译的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程