博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
fhs-framework springboot mybatis 解决表关联查询问题的关键方案-翻译服务
阅读量:4071 次
发布时间:2019-05-25

本文共 15185 字,大约阅读时间需要 50 分钟。

简介

    开发中会经常遇到这样的场景:比如在成绩表有一个student_id,如果我要查看成绩列表需要学生的名称,一般写法就是使用join,现在大部分ORM框架对于表关联支持并不是很友好,所以很多时候我们都要自己写SQL去实现。

   翻译服务即:通过id,将对应的title/name 字段翻译出,装载到VO中用于前端展示的技术.

1 FHS 提供的翻译服务如何使用?

     a  定义翻译数据源.

        

@Service@DataSource("base_business")@AutoTrans(namespace = BaseTransConstant.ORG, fields = "name", useRedis = true, defaultAlias = "org")public class UcenterMsOrganizationServiceImpl extends BaseServiceImpl
implements UcenterMsOrganizationService {

      通过以上定义,我们就能知道,组织机构的表,对外开放组织机构名称这个字段给其他的业务使用.

      b  标记业务pojo orgid字段使用此翻译数据源.

          

/**     * 所属机构     */    @NotNull(message = "所属机构字段不可为null", groups = {Update.class, Delete.class})    @Length(message = "所属机构字段的长度最大为32", groups = {Add.class, Update.class}, max = 32)    @Column(name = "organization_id")    @Trans(type = TransType.AUTO_TRANS,key = BaseTransConstant.ORG )    private String organizationId;

    c   调用transService的trans相关方法即可.

         

public V d2v(D d) {        try {            if (d == null) {                return null;            }            V vo = voClass.newInstance();            BeanUtils.copyProperties(d, vo);            transService.transOne(vo);            return vo;        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }        return null;    } public List
dos2vos(List
dos) { List
vos = ListUtils.copyListToList(dos, this.getVOClass()); transService.transMore(vos); return vos; }

     c VO中需要有getTransMap 方法返回一个map,翻译服务会吧翻译的结果放到此map中.

     d 翻译后的结果示例:

    

{            "between": {},            "createTime": "2019-03-25 00:00:00",            "createUser": "1",            "dataPermissin": {},            "dataPermissions": "{\"parkIds\":\"0d601eb23f0e11e99571d02788407b5e\"}",            "groupCode": null,            "inFilter": {},            "isDelete": null,            "isEnable": 1,            "methods": null,            "organizationId": "001",            "pkey": 1,            "remark": "12123",            "roleId": 1,            "roleName": "1231",            "state": null,            "transMap": {                "orgName": "机构",                "isEnableName": "启用",                "createUserUserName": "admin22"            },            "updateTime": "2019-03-29 00:00:00",            "updateUser": "62b5870c510c4e9da3f72460001c42fa"        },

      2  翻译服务实现

                 A 定义注解

                              自动翻译注解,把一个service当做数据源的注解,用于service实现类之上,比如上面的ORGservice

@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.TYPE})public @interface AutoTrans{    /**     *  命名空间     * @return     */    String namespace();    /**     * 字段集合     * @return     */    String[] fields();    /**     * 是否使用缓存翻译     * @return  默认为true 如果是false的话     */    boolean useCache() default true;    /**     * 是否使用redis存放缓存     * @return 默认false     */    boolean useRedis() default false;    /**     * 默认的别名     * @return     */    String defaultAlias() default "";}

          字段翻译注解,定义这个字段使用哪个翻译,比如fhs提供了auto(自动翻译),和wordbook(字典)

@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.FIELD })public @interface Trans {    /**     * 获取翻译类型,比如 wordbook 是字典     * @return 类型     */    String type();    /**     * 字段 比如  要翻译男女 上面的type写wordbook 此key写sex即可     * @return     */    String key() default "";}

         翻译类型集合,用于标记到 pojo上,代表我使用了哪些类型的翻译.

@Retention(RetentionPolicy.RUNTIME)@Target({ ElementType.TYPE})public @interface TransTypes {    /**     * 获取需要翻译的类型     * @return     */    String[] types();}

           B 定义翻译服务类

                    主要负责根据翻译的类型决定调用哪个ITransTypeService的实现类去翻译某些字段.

@Service("transService")public class TransService {    /**     * key type  val是对应type的service     */    private static Map
transTypeServiceMap = new HashMap
(); /** * 注册一个trans服务 * * @param type 类型 * @param transTypeService 对应的trans接口实现 */ public static void registerTransType(String type, ITransTypeService transTypeService) { transTypeServiceMap.put(type, transTypeService); } /** * 翻译一个字段 * * @param obj 需要翻译的对象 */ public void transOne(VO obj) { if (obj == null) { return; } ClassInfo info = ClassManager.getClassInfoByName(obj.getClass()); String[] transTypes = info.getTransTypes(); if (transTypes == null) { return; } List
transFieldList = null; for (String type : transTypes) { transFieldList = info.getTransField(type); if (transFieldList == null || transFieldList.size() == 0) { continue; } transTypeServiceMap.get(type).transOne(obj, transFieldList); } } /** * 翻译多个 字段 * * @param objList 需要翻译的对象集合 * @param objList 需要翻译的字段集合 */ public void transMore(List
objList) { if (objList == null || objList.size() == 0) { return; } Object object = objList.get(0); ClassInfo info = ClassManager.getClassInfoByName(object.getClass()); String[] transTypes = info.getTransTypes(); if (transTypes == null) { return; } List
transFieldList = null; for (String type : transTypes) { transFieldList = info.getTransField(type); if (transFieldList == null || transFieldList.size() == 0) { continue; } transTypeServiceMap.get(type).transMore(objList, transFieldList); } }}

            D 自动翻译实现

                springboot启动成功后,扫描service包,被autotrans注解标记的service,解析autotrans的内容,然后把数据库中数据缓存到内存和redis中,当业务需要翻译的时候,根据id把缓存的数据拿出来,放到transmap中.

 

    

/** * 本接类使用需要配合Autotrans 注解和autoTransAble的实现类 * * @Description: 自动翻译服务 * @Author: Wanglei * @Date: Created in 10:14 2019/10/15 */@Data@Servicepublic class AutoTransService implements ITransTypeService, InitializingBean, ApplicationListener
{ public static final Logger LOGGER = LoggerFactory.getLogger(AutoTransService.class); /** * service的包路径 */ @Value("${fhs.autotrans.package:com.*.*.service.impl}") private String[] packageNames; /** * 翻译数据缓存map */ private Map
> cacheMap = new HashMap<>(); /** * 缓存 默认时间:半个小时 */ @CreateCache(expire = 1800, name = "trans:cache:", cacheType = CacheType.REMOTE) private Cache
> transCache; /** * 基础服务 */ private Map
baseServiceMap = new HashMap<>(); /** * 配置 */ private Map
transSettMap = new HashMap<>(); /** * 如果直接去表里查询,放到这个cache中 */ private ThreadLocal
>> threadLocalCache = new ThreadLocal<>(); /** * 翻译字段配置map */ private Map
transFieldSettMap = new HashMap<>(); @Override public void transOne(VO obj, List
toTransList) { Trans tempTrans = null; for (Field tempField : toTransList) { TransFieldSett transFieldSett = transFieldSettMap.containsKey(tempField) ? transFieldSettMap.get(tempField) : new TransFieldSett(tempField); tempTrans = transFieldSett.getTrans(); String namespace = transFieldSett.getNamespace(); String alias = transFieldSett.getAlias(); if (transSettMap.containsKey(namespace) && CheckUtils.isNullOrEmpty(alias)) { alias = transSettMap.get(namespace).defaultAlias(); } String pkey = ConverterUtils.toString(ReflectUtils.getValue(obj, tempField.getName())); if (StringUtils.isEmpty(pkey)) { continue; } Map
transCache = null; // 主键可能是数组 pkey = pkey.replace("[", "").replace("]", ""); if (pkey.contains(",")) { String[] pkeys = pkey.split(","); transCache = new HashMap<>(); Map
tempTransCache = null; for (String tempPkey : pkeys) { tempTransCache = getTempTransCacheMap(namespace, ConverterUtils.toInteger(tempPkey)); if (tempTransCache == null) { LOGGER.error("auto trans缓存未命中:" + namespace + "_" + tempPkey); continue; } // 比如学生表 可能有name和age 2个字段 for (String key : tempTransCache.keySet()) { transCache.put(key, transCache.containsKey(key) ? transCache.get(key) + "," + tempTransCache.get(key) : tempTransCache.get(key)); } } } else { transCache = getTempTransCacheMap(namespace, pkey); if (transCache == null) { LOGGER.error("auto trans缓存未命中:" + namespace + "_" + pkey); continue; } } if (!CheckUtils.isNullOrEmpty(alias)) { Map
tempMap = new HashMap<>(); Set
keys = transCache.keySet(); for (String key : keys) { tempMap.put(alias + key.substring(0, 1).toUpperCase() + key.substring(1), transCache.get(key)); } transCache = tempMap; } Map
transMap = obj.getTransMap(); Set
keys = transCache.keySet(); for (String key : keys) { if (CheckUtils.isNullOrEmpty(transMap.get(key))) { transMap.put(key, transCache.get(key)); } } } } @Override public void transMore(List
objList, List
toTransList) { threadLocalCache.set(new HashMap<>()); // 由于一些表数据比较多,所以部分数据不是从缓存取的,是从db先放入缓存的,翻译完了释放掉本次缓存的数据 for (Field tempField : toTransList) { tempField.setAccessible(true); Trans tempTrans = tempField.getAnnotation(Trans.class); String namespace = tempTrans.key(); // 如果是 good#student 翻译出来应该是 goodStuName goodStuAge customer#customer customerName if (namespace.contains("#")) { namespace = namespace.substring(0, namespace.indexOf("#")); } if (!this.baseServiceMap.containsKey(namespace)) { LOGGER.warn("namesapce对应的service没有标记autotrans:" + namespace); continue; } AutoTrans autoTransSett = this.transSettMap.get(namespace); if (autoTransSett.useCache()) { continue; } Set
ids = new HashSet<>(); objList.forEach(obj -> { try { ids.add(tempField.get(obj)); } catch (IllegalAccessException e) { e.printStackTrace(); } }); List
dbDatas = baseServiceMap.get(namespace).findByIds(new ArrayList<>(ids)); for (VO vo : dbDatas) { threadLocalCache.get().put(namespace + "_" + vo.getPkey(), createTempTransCacheMap(vo, autoTransSett)); } } objList.forEach(obj -> { this.transOne(obj, toTransList); }); threadLocalCache.set(null); } @Override public void afterPropertiesSet() throws Exception { TransService.registerTransType("auto", this); TransMessageListener.regTransRefresher("auto", this::refreshCache); } public void init(ApplicationReadyEvent applicationReadyEvent) { //spring容器初始化完成之后,就会自行此方法。 Set
> entitySet = scan(AutoTrans.class, packageNames); // 遍历所有class,获取所有用@autowareYLM注释的字段 if (entitySet != null) { for (Class
entity : entitySet) { // 获取该类 Object baseService = SpringContextUtil.getBeanByClass(entity); if (!(baseService instanceof AutoTransAble)) { LOGGER.warn("AutoTrans 只能用到实现AutoTransAble的类上,不能用到:" + baseService.getClass()); continue; } AutoTrans autoTransSett = entity.getAnnotation(AutoTrans.class); this.baseServiceMap.put(autoTransSett.namespace(), (AutoTransAble) baseService); this.transSettMap.put(autoTransSett.namespace(), autoTransSett); } } refreshCache(new HashMap<>()); } /** * 刷新缓存 * * @param messageMap 消息 */ public void refreshCache(Map
messageMap) { //这里必须能拿到namespace 拿不到,就当作全部刷新 String namespace = messageMap.get("namespace") != null ? messageMap.get("namespace").toString() : null; if (namespace == null) { Set
namespaceSet = this.transSettMap.keySet(); namespaceSet.forEach(temp -> { refreshOneNamespace(temp); }); } else { try { Thread.sleep(1000); } catch (InterruptedException e) { LOGGER.error("刷新缓存错误:", e); } refreshOneNamespace(namespace); } } /** * 刷新一个namespace下的所有的缓存 * * @param namespace namespace */ public void refreshOneNamespace(String namespace) { LOGGER.info("开始刷新auto-trans缓存:" + namespace); if (!this.transSettMap.containsKey(namespace)) { LOGGER.info("本系统无需刷新此缓存namespace:" + namespace); return; } List
vos = this.baseServiceMap.get(namespace).select(); if (vos == null || vos.isEmpty()) { return; } Object pkeyVal = null; String fielVal = null; Map
tempCacheTransMap = null; VO po = null; AutoTrans autoTrans = this.transSettMap.get(namespace); //不适用缓存的不做缓存 if (!autoTrans.useCache()) { return; } for (int i = 0; i < vos.size(); i++) { po = vos.get(i); pkeyVal = po.getPkey(); cacheMap.put(namespace + "_" + pkeyVal, createTempTransCacheMap(po, autoTrans)); if(autoTrans.useCache()){ this.transCache.put(namespace + "_" + pkeyVal, createTempTransCacheMap(po, autoTrans)); } } LOGGER.info("刷新auto-trans缓存完成:" + namespace); } /** * 创建一个临时缓存map * * @param po po * @param autoTrans 配置 * @return */ private Map
createTempTransCacheMap(Object po, AutoTrans autoTrans) { String fielVal = null; Map
tempCacheTransMap = new LinkedHashMap<>(); for (String field : autoTrans.fields()) { fielVal = ConverterUtils.toString(ReflectUtils.getValue(po, field)); tempCacheTransMap.put(field, fielVal); } return tempCacheTransMap; } /** * 获取用于翻译的缓存 * * @param namespace namespace * @param pkey 主键 * @return 缓存 */ private Map
getTempTransCacheMap(String namespace, Object pkey) { AutoTrans autoTrans = this.transSettMap.get(namespace); //如果内存缓存中有,则优先用内存缓存 if (cacheMap.containsKey(namespace + "_" + pkey)) { return cacheMap.get(namespace + "_" + pkey); } //如果注解为空,代表可能是其他的服务提供的翻译,尝试去redis获取缓存 else if (autoTrans == null) { Map
redisCacheResult = this.transCache.get(namespace + "_" + pkey); //如果获取到了返回 if (redisCacheResult != null) { return redisCacheResult; } //redis获取不到返回空map return new HashMap<>(); } else { if (autoTrans == null) { LOGGER.warn("namespace对应的service没有使用autotrans注解标记:" + namespace); return new HashMap<>(); } //如果强调使用缓存,则可能是还没刷新进来,直接返回空map,前端在刷新一下就好了 if (autoTrans.useCache()) { return new HashMap<>(); } if (this.threadLocalCache.get() == null) { VO vo = this.baseServiceMap.get(namespace).selectById(pkey); return createTempTransCacheMap(vo, autoTrans); } return this.threadLocalCache.get().get(namespace + "_" + pkey); } } /** * 翻译单个的key * * @param namespace namespace * @param pkeyVal 主键 * @return */ public String transKey(String namespace, String pkeyVal) { Map
tempCacheTransMap = cacheMap.get(namespace + "_" + pkeyVal); if (tempCacheTransMap == null) { LOGGER.error("auto trans缓存未命中:" + namespace + "_" + pkeyVal); } else { for (String key : tempCacheTransMap.keySet()) { return tempCacheTransMap.get(key); } } return null; } /** * 类扫描器 * * @param annotationClass 注解 * @param packageNames 包 * @return 符合条件的类 */ public static Set
> scan(Class
annotationClass, String[] packageNames) { TypeFilter entityFilter = AnnotationTypeFilterBuilder.build(annotationClass); SpringClassScanner entityScanner = new SpringClassScanner.Builder().typeFilter(entityFilter).build(); for (String packageName : packageNames) { entityScanner.getScanPackages().add(packageName); } Set
> entitySet = null; try { entitySet = entityScanner.scan(); } catch (ClassNotFoundException | IOException e) { LOGGER.error("包扫描错误", e); // log or throw runTimeExp throw new RuntimeException(e); } return entitySet; } @Override public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { init(applicationReadyEvent); }}/** * 被翻译的字段的实体 */@Dataclass TransFieldSett { /** * trans注解 */ private Trans trans; /** * 命名空间 */ private String namespace; /** * 别名 */ String alias; public TransFieldSett(Field transField) { transField.setAccessible(true); trans = transField.getAnnotation(Trans.class); namespace = trans.key(); // 如果是 good#student 翻译出来应该是 goodStuName goodStuAge customer#customer customerName if (namespace.contains("#")) { alias = namespace.substring(namespace.indexOf("#") + 1); namespace = namespace.substring(0, namespace.indexOf("#")); } }}

全部源码地址 

开源项目地址:https://gitee.com/fhs-opensource/fhs-framework

fhs framework qq群:976278956

转载地址:http://dhwni.baihongyu.com/

你可能感兴趣的文章
内存回收专题
查看>>
[资料] 史上最强的伯克利大学1024线飞龙AI下载地址,有没有人有兴趣来测试一手?...
查看>>
Discuz多人斗地主积分版,消耗论坛积分的斗地主
查看>>
discuz X2斗地主积分版插件安装方法(用户版)
查看>>
ASP.NET程序也能像WinForm程序一样运行
查看>>
听到两个程序员聊天——A:“借我1K块。”
查看>>
轻松搭建一个Windows SVN服务器
查看>>
Discuz X2多人斗地主[消耗论坛积分]小体积版本,仅25MB!
查看>>
大型多人在线MMO RPG游戏最重要的二个职位
查看>>
NVIDIA_Fermi_GPU架构简单解析(转)
查看>>
以前看过一个压缩过的.exe,运行会播放长达半小时的动画,却只有60KB,个人认为其中的原理...
查看>>
给vs2012轻松换肤
查看>>
socket短时间内重连需注意的问题
查看>>
关于线程和线程栈
查看>>
VisualSvn Server安装和使用
查看>>
几种软件常用授权方式总结
查看>>
liunx立即关机命令是什么?
查看>>
Win7输入法消失和不能切换的办法了
查看>>
Unity火爆插件Behavior Designer行为树插件学习
查看>>
Socket服务器整体架构概述
查看>>