分享公司Basecode的用法
主题
公司在basecode的用法上是比较有新意的,所以准备记录分享下公司的用法.
说明
basecode公司的一个主要用途就是用于一些基础的代码表,参数表的前台操作.这些表有很多,用spring data jpa(hibernate)的时候,又不想为每个表写个实体,因为太多了,每个实体还需要dao和service等等..这些表用法很单一,主要用于前台表单的下拉框,比如证件种类有:身份证,军官证,学生证....这些证件的代码和名称等信息就是存在一个代码表里的.前台下拉的时候可能会列出所有值,也可能会有一些过滤.
这种用法下需要前后台配合,前台我们使用的是miniui,需要对miniui现有的组件进行一些封装来实现自动查询数据库,后台也需要有对应的查询的逻辑..这里主要介绍后台的一些封装.
用途
主要用于前台下拉框自动填充数据库里不同参数表,代码表的代码的参数的值(比如中国行政区划地区代码和对应的代码值),开发过程中不需要自己手写JS或者后台代码,全部都有封装.只需要配置一下即可实现前台下拉框填充后台数据库里的代码表的值.
主要实现原理
1.配置表:因为没有实体去对应代码表或者参数表,所以肯定要有个配置表去记录有哪些代码表,哪些参数表,应该怎么去不同的表中根据何种逻辑去取值.这张表我们简称def表吧.
2.代码逻辑:有了配置表以后我们相当于有了规则,知道根据配置的规则应该如何去找数据.那么还需要有一套代码逻辑去实现这个规则.这套代码逻辑其实不复杂.就是利用先自己根据不同情况拼接不同的SQL,然后通过JPA的执行原生SQL的方法去查询各个配置的表的数据.
具体实现
配置表
字段很多,说明配置还是比较灵活的.不过大部分情况下大部分字段是用不到的.我介绍一下最主要的用法.
code_type:像ID一样,为了找到一条配置
value_field:表明下拉框的显示的值对应的代码的对应SQL查出来的哪一列
name_field:表明下拉框的显示的值对应SQL查出来的哪一列
value_field和name_field就是前台下拉框组件里显示值和传给后台的参数值对应数据库里的SQL查询结果的哪一列.比如下拉框显示值'有效'对应的代码是'Y'.或者说就是前台下拉框的key和value.
parent_field和isLeaf_field和enabled_field用于构建树组件.树组件和普通下拉框相比主要多了上级节点属性(parent_field)和当前节点是否有下级节点(isLeaf_field)属性.
Table_name就是要查哪张表的表名,或者直接写SQL查询也一样.
FilterSQL就是前台传给后台一些参数的情况下根据参数动态在Table_name后拼接where条件,达到过滤查询结果的目的.
code_provider和bussinessdata_provider就是不走一般的查询逻辑,自己定义比较复杂的查询逻辑的时候用到.相当于一般情况下会有个默认的provider,你不用他默认的查询逻辑,就需要自己配置.
实现
光有配置没有什么用,我们来看看主要实现吧.
首先,既然我们把这么多不同业务场景的相同需求抽象出来做了一个统一处理,那我们肯定要有个统一入口.这个入口Controller我们就叫他basecode controller好了.
前面配置表中提到一条配置相当于是一种查数据的方式,对应一种下拉框取值的方式.那问我这个下拉框要怎么取值,相当于是问我配置表中怎么确定一条配置数据咯.
配置表中的主键,codeType能确定一条数据,所以每个下拉框向后台发送请求查找数据的时候肯定要把这个codeType传过啦.(下拉框这个请求是在页面加载的时候就会发起的,是公司封装了miniui的组件,这篇文章主要涉及后台basecode,所以前台的封装就暂时不说了.后面会再写文章分享的).
controller中其实也没做什么操作,就只是组装一下和codeType同时传过来的,用于数据过滤的参数,放到paramMap里和codeType一起传给codeHelper,委托codeHelper去做具体的查询与过滤数据.
@Override
public List<?> getCodeList(String codeType, Map<String, ?> paramMap) {
List<BaseCodeDTO> result = null;
try {
BaseCodeDefDTO defDto = codeDefService.getCodeDefByCodeType(codeType);
if (defDto != null) {
result = this.getProvider(defDto).getCodeList(defDto, paramMap);
}
} catch (Exception ex) {
LOGGER.info(ex.getMessage(), ex);
throw new RuntimeException(ex);// NOSONAR
}
return result;
}
从getCodeList方法可以看出,第5行先得到这个def表对应的java对象(jpa的映射方法).然后L7得到def表配置的Porvider(codeProvider成员域).再委托Provider去查找数据.所以说到底还是通过Provider去查找数据.
我们先来看看怎么getProvider的.
private UnifiedCodeProvider getProvider(BaseCodeDefDTO defDto) {
String providerDef = StringUtils.isNotEmpty(defDto.getCodeProvider()) ? defDto.getCodeProvider()
: "defaultCodeProvider";
Object provider = SpringContextUtil.getBean(providerDef);
if (provider instanceof UnifiedCodeProvider) {
provider = (UnifiedCodeProvider) provider;
} else {
provider = (UnifiedCodeProvider) SpringContextUtil.getBean("defaultCodeProvider");
}
if (provider == null) {
// TODO:异常抛出修改
throw new RuntimeException("basecode provider:" + defDto.getProvider() + " is not exist! ");// NOSONAR
}
return (UnifiedCodeProvider) provider; }
从上面的代码中可以看出,如果在def里配置了provider,那就从Spring的context中根据bean名字取出provider,同时也说明配置的provider是需要在项目启动的时候由Spring加载的.
如果没配置,那就用公共的provider,这个provider的名字叫做defaultCodeProvider.
取到provider以后我们就可以getCodeList了.我们主要看看公共的provider吧,因为绝大多数情况下用的是这个provider.
@Override
public List<BaseCodeDTO> getCodeList(BaseCodeDefDTO defDto, Map<String, ?> paramMap) {
// 重组参数
Map<String, Object> realMap = new HashMap<String, Object>();
if (paramMap != null && !paramMap.isEmpty()) {
for (Entry<String, ?> entry : paramMap.entrySet()) {
if ("basevalue".equalsIgnoreCase(entry.getKey())) {
realMap.put(defDto.getParentField(), entry.getValue());
} else if ("value".equalsIgnoreCase(entry.getKey())) {
realMap.put(defDto.getValueField(), entry.getValue());
} else if ("label".equalsIgnoreCase(entry.getKey())) {
realMap.put(defDto.getNameField(), entry.getValue());
} else if ("isleaf".equalsIgnoreCase(entry.getKey())) {
realMap.put(defDto.getIsleafField(), entry.getValue());
} else if ("swjg".equalsIgnoreCase(entry.getKey())) {
realMap.put(defDto.getSwjgField(), entry.getValue());
} else if ("enabled".equalsIgnoreCase(entry.getKey())) {
realMap.put(defDto.getEnabledField(), entry.getValue());
} else if ("parent".equalsIgnoreCase(entry.getKey())) {
realMap.put(defDto.getParentField(), entry.getValue());
} else {
realMap.put(entry.getKey(), entry.getValue());
}
}
}
return codeService.getCodeListParamMap(defDto, realMap);
}
getCodeList里对传过来的Map里的参数进行了一些加工,如果map里有key是"value"呀,"label"呀,"parent"呀类似这些值的话会被替换成def里配置的字符串,相当于是修改了map里的key,替换成了数据库里配置的值,除此之外的key原封不动的放进新的map里.
然后委托codeService去查数据
public List<BaseCodeDTO> getCodeListParamMap(BaseCodeDefDTO codeModel, Map<String, Object> param) {
List<BaseCodeDTO> result = null;
try {
String baseSql = this.buildBaseSql(codeModel, false);
List<Object> paramList = new ArrayList<Object>();
String whereCause = this.buildWhereCauseFromMap(codeModel, param, paramList);
if (param == null) {
param = new HashMap<String, Object>();// NOSONAR
}
StringBuilder sqlbuffer = new StringBuilder();
sqlbuffer.append(baseSql).append(" WHERE ").append(whereCause).append(" ")
.append(StringUtils.isNotEmpty(codeModel.getOrderby()) ? codeModel.getOrderby() : "");
Map<String, Object> paramMap = new HashMap<String, Object>();
if (param != null && !param.isEmpty()) {
for (Entry<String, Object> entry : param.entrySet()) {
if (!"filterParam".equalsIgnoreCase(entry.getKey())
&& !"initParam".equalsIgnoreCase(entry.getKey())) {
paramMap.put(entry.getKey(), entry.getValue());
}
}
}
result = navtiveRepository.executeQuerySql(sqlbuffer.toString(), paramMap, BaseCodeDTO.class);
} catch (Exception ex) {
LOGGER.info(ex.getMessage(), ex);
throw new RuntimeException(ex);// NOSONAR
}
return result; }
getCodeListParamMap方法有点长,但是核心就是4行代码:
L4:先拼接出要查的代码表或者参数表的基本的查询SQL,这里是通过调用buildBaseSql方法做到的.
L6:根据传过来的Map里的参数拼接得到where条件字符串,这里是通过调用buildWhereCauseFromMap方法.
L11:L4+L6得到完整SQL语句,如果有配置order by的话,同时拼接order by.
L22:调用公司对JPA原生SQL的封装的类来执行SQL(之前分享过这部分封装).
private String buildBaseSql(BaseCodeDefDTO codeModel, boolean distinckSign) {
StringBuilder sqlBuffer = new StringBuilder();
if (codeModel.getValueField() == null) {
throw new RuntimeException("代码模型未设置值字段CodeField");// NOSONAR
}
if (codeModel.getNameField() == null) {
throw new RuntimeException("代码模型未设置名称字段NameField");// NOSONAR
}
if (codeModel.getTableName() == null) {
throw new RuntimeException("代码模型未设置TABLE");// NOSONAR
} sqlBuffer.append(" SELECT ");
if (distinckSign) {
sqlBuffer.append(" distinct ");
}
sqlBuffer.append(codeModel.getValueField()).append(" as value ,").append(codeModel.getNameField());
sqlBuffer.append(" as label ,").append(codeModel.getParentField() != null ? codeModel.getParentField() : "''");
sqlBuffer.append(" as parent, ");
sqlBuffer.append(codeModel.getEnabledField() != null ? codeModel.getEnabledField() : "''");
sqlBuffer.append(" as enabled, ");
sqlBuffer.append(codeModel.getIsleafField() != null ? codeModel.getIsleafField() : "'false'");
sqlBuffer.append(" as isleaf, ").append(codeModel.getSwjgField() != null ? codeModel.getSwjgField() : "''");
sqlBuffer.append(" as swjgdm ").append(" from( ").append(codeModel.getTableName()).append(" ) t ");
return sqlBuffer.toString(); }
buildBaseSql方法就是根据def表里的table_name列构造出了一个select语句.这个select语句select的内容是统一的.都是select value,label,parent等内容.因为前端下拉框组件需要的就是这么几个内容,所以这里做到了统一处理.
同时def表里table列可以配置SQL或者Table name的原因在于这里是在table列的值会被当做子查询(L24),所以写SQL或者直接写表名都是可以的.
@SuppressWarnings("unchecked")
private String buildWhereCauseFromMap(BaseCodeDefDTO codeModel, Map<String, Object> param, List<Object> paramList)
throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
StringBuilder sqlBuffer = new StringBuilder(" 1=1 ");
if (param != null && param.size() > 0) {
Map<String, Object> scenarioParamMap = new HashMap<String, Object>();
for (Entry<String, Object> entry : param.entrySet()) {
Object value = entry.getValue();
if (value != null) {
if ("filterParam".equalsIgnoreCase(entry.getKey()) || "initParam".equalsIgnoreCase(entry.getKey())) {
if (StringUtils.isNotEmpty(codeModel.getFiltersql())) {
sqlBuffer.append(" AND ");
try {
Map<String, Object> map = (Map<String, Object>) value;
boolean hasMultipleScenarios = map.containsKey("SCENARIO");
if (hasMultipleScenarios) {
Map<String, String> dataMap = new HashMap<String, String>();
dataMap.put("SCENARIO", (String) map.get("SCENARIO"));
map.remove("SCENARIO");
StringWriter sw = new StringWriter();
processor.processPlainText(codeModel.getFiltersql(), dataMap, sw);
sqlBuffer.append(sw.toString());
} else {
sqlBuffer.append(codeModel.getFiltersql());
}
scenarioParamMap.putAll(map);
} catch (Exception e) {
LOGGER.info(e.getMessage(), e);
throw new RuntimeException(e);// NOSONAR
}
}
} else {
sqlBuffer.append(" AND ");
sqlBuffer.append(entry.getKey()).append(" = :" + entry.getKey());
paramList.add(value);
}
}
}
param.putAll(scenarioParamMap);
}
return sqlBuffer.toString(); }
buildWhereCauseFromMap方法略复杂.
除去各种校验以外核心代码为:
if ("filterParam".equalsIgnoreCase(entry.getKey()) || "initParam".equalsIgnoreCase(entry.getKey())) {
if (StringUtils.isNotEmpty(codeModel.getFiltersql())) {
sqlBuffer.append(" AND ");
try {
Map<String, Object> map = (Map<String, Object>) value;
boolean hasMultipleScenarios = map.containsKey("SCENARIO");
if (hasMultipleScenarios) {
Map<String, String> dataMap = new HashMap<String, String>();
dataMap.put("SCENARIO", (String) map.get("SCENARIO"));
map.remove("SCENARIO");
StringWriter sw = new StringWriter();
processor.processPlainText(codeModel.getFiltersql(), dataMap, sw);
sqlBuffer.append(sw.toString());
} else {
sqlBuffer.append(codeModel.getFiltersql());
}
scenarioParamMap.putAll(map);
} catch (Exception e) {
LOGGER.info(e.getMessage(), e);
throw new RuntimeException(e);// NOSONAR
}
}
} else {
sqlBuffer.append(" AND ");
sqlBuffer.append(entry.getKey()).append(" = :" + entry.getKey());
paramList.add(value);
}
里面有2条分支:
1.L23 else路线:如果前面param的map里没有filterParam或者initParam的key的话就直接把map里和key作为参数绑定拼接到SQL上,比如yxbz=:yxbz,后面jpa执行原生SQL的时候会把:yxbz替换成具体的值.
2.L1 filterParam和initParam路线:
这里就比较复杂了.
所谓initParam就是组件第一次加载的时候需要显示的数据需要用到的参数.后续组件再发起请求的时候不会再传这个参数.
所谓filterParam就是组件每次加载的时候需都要用到的参数.
initParam这种用法经常用在树组件上,比如第一次加载树组件的时候可能需要N个过滤条件,所以需要在initParam里传递N个过滤的参数,后续加载只需要加载你点击的那个父节点下的子节点就行了这个时候initParam就什么参数都不用传递了.再传递一个parant参数表示父节点的值就行了.所以可能2次传过来的参数是不同的.
而filterParam是每次都会传的.
如果走这条路线的话def表里肯定会配置filterSQL,即where里要拼接的过滤的SQL.
filterSQL可以配合SCENARIO来使用,所谓SCENARIO就是在不同场景下拼接不同的SQL,具体拼接什么SQL.是根据传递过来的参数Map来生成相应的字符串SQL.主要实现是通过freemarker来解析的(可能公司架构师觉得这里用freemarker解析生成字符串比较简单吧,至少不需要自己去写解析方法.....),所以这里依赖freeMarker.
来看个def表里filterSQL的例子:
<#if SCENARIO=="ZSXMCLAUSE"> YXBZ='Y' and XYBZ='Y' and ZSXM_DM=:ZSXMDM <#elseif SCENARIO== "YXBZ"> YXBZ='Y' and XYBZ='Y' <#elseif SCENARIO== "SJZSPM"> YXBZ='Y'</#if>
比如的filterSQL配置了3种场景,如果传过来的SCENARIO是ZSXMCLAUSE那就只拼接YXBZ='Y' and XYBZ='Y' and ZSXM_DM=:ZSXMDM 字符串
如果是SCENARIO是YXBZ那就只拼接YXBZ='Y' and XYBZ='Y'...
如果是.......
稍微总结一下的话就是说:
initParam可以让组件在第一次和后续的请求传递不同的参数.
filterParam是组件每次请求都会传递参数
initParam和filterParam配合SCENARIO可以多个miniui组件公用一条def表配置但是拼接出不同的where条件SQL.
当然initParam和filterSQL也可以不配合 SCENARIO 来使用,这种用法下就直接把filterParam或者initParam里面的变量通过JPA的Query的setParamter方法绑定到SQL中去.但是只有一种SCENARIO的时候可以把filterSQL直接放到table_name里.反正只有一种情景..所以说一般用到filterParam的时候都会有N个SCENARIO,不同组件选择不同的SCENARIO .
这段代码写的比较一般...本来有个缺陷就是当传了SCENARIO过来走filterParam和initParam路线的时候其他的key和value会被忽略,导致部分绑定的参数没有被设值.
所以后来修改了代码,就是增加了各种remove和addAll什么的调用...然后在getCodeListParamMap方法里再次判断param Map是否非空.....显得代码逻辑很混乱..当然....能用就行了....
有了拼接的SQL,就只要调用JPA去执行原生SQL就可以了.
小结
公司的basecode功能展示了不通过JPA映射,通过配置来实现不同表通过统一规则查数据的一种方式.还是比较方便的.
除此之外basecode还有些其他的功能,比如通过name找code,通过code找name等等.....都是通过def表来实现的...不过其他的功能都是比较简单的,即使不通过这套代码也可以很方便的实现...所以我就不再仔细介绍了..
分享公司Basecode的用法的更多相关文章
- 记录分享公司Spring data相关配置
起因 公司Spring Data相关的配置还是有点意思的,不过蛮复杂的...XML很多...我不太喜欢强行记住....所以我打算记录一下配置的大致思路和结构,以后可能会有用.... 总体结构 总体结构 ...
- 好程序员web前端分享javascript关联数组用法总结
好程序员web前端分享javascript关联数组用法总结,有需要的朋友可以参考下. Hash关联数组定义 代码如下 // 定义空数组 myhash = { } // 直接定义数组 myhash = ...
- 分享公司DAO层数据库结果映射到对象的方法
主题 前面写过一篇文章,分享了公司是怎么动态封装SQL查询条件的(http://www.cnblogs.com/abcwt112/p/5874401.html). 里面提到数据库查询结果二维数组最后是 ...
- 分享公司DAO层动态SQL的一些封装
主题 公司在DAO层使用的框架是Spring Data JPA,这个框架很好用,基本不需要自己写SQL或者HQL就能完成大部分事情,但是偶尔有一些复杂的查询还是需要自己手写原生的Native SQL或 ...
- 分享公司Entity与DTO之间数据拷贝的方法
主题 最早以前自学java web的时候,数据库查询出来一个Entity对象(CMP对象).就直接传给前台展示了.并没有用到DTO对象,开始并没有觉得有什么不好...后来发现还是需要一些DTO对象来专 ...
- 分享vue中 slot用法
//slot默认用法 //slot带参数name用法
- tomcat原理(三)结合公司tomcat的用法的在理解
一,server.xml文件 <?xml version="1.0" encoding="UTF-8"?> <Server port=&quo ...
- 微信小程序分享转发用法大全——自定义分享、全局分享、组合分享
官方提供的自定义分享 使用隐式页面配置函数实现的全局分享-推荐 使用隐式路由实现的全局分享-不推荐,仅供了解隐式路由 前言: 目前微信小程序只开放了页面自定义分享的API,为了能够更灵活的进行分享配置 ...
- C# WinForm PropertyGrid用法
关于C# PropertyGrid的用法没有找到,找到一个C++的用法.模仿着使用了一下,感觉挺不错,分享一下.基本用法:拖个PropertyGrid,绑定一个属性类就行了. 大气象 Code hig ...
随机推荐
- 多值(in),范围值(between..and)
多值检测 关键词[in] 查出年龄是23,24,28 的人员信息 select * from T_Employee where FAge in (23,25,28) in 后面如果跟子查询, ...
- CentOS安装JDK-1.7
注:以下所有操作均在CentOS 6.5 x86_64位系统下完成. #准备工作# 准备用rpm下载前,看系统是否已经安装有JDK,如果没有则进入正式安装步骤. # rpm -qa | grep jd ...
- [转]ASP.NET Core 中的那些认证中间件及一些重要知识点
本文转自:http://www.qingruanit.net/c_all/article_6645.html 在读这篇文章之间,建议先看一下我的 ASP.NET Core 之 Identity 入门系 ...
- kettle中参数和变量的区别
图一: 图二: 何时使用'?'何事使用${}应当根据情况: 在图二中使用的是${}因为此时没有"作为参数的字段",所以只能用el表达式直接获取其值,在图一中有"作为参数的 ...
- HTML 学习笔记 (canvas 基础)
1.什么是Canvas canvas就是一个画布,可以进行画任何的线,图形,填充等一系列操作.这一切都是用Js操作的,另外Canvas不仅仅提供简单的二维矢量绘图,也提供了三维的绘图,以及图片处理等一 ...
- pcl曲面重建模块-poisson重建算法示例
poisson曲面重建算法 pcl-1.8测试通过 #include <iostream> #include <pcl/common/common.h> #include &l ...
- AppBoxPro - 细粒度通用权限管理框架(可控制表格行内按钮)源码提供下载
特别声明: 提供的源代码已经包含了 AppBoxPro 的全部源代码,用 VS2012 打开项目后,直接 Ctrl+F5 可以运行起来(默认使用VS自带的LocalDB数据库). FineUIPro是 ...
- 如何在Microsoft Edge浏览器中添加一个Hello World插件
注:本文提到的代码示例下载地址> How to add a Hello World extension to Microsoft Edge Microsoft Edge 随着Win 10一起推出 ...
- Android开发之应用程序的安装
这里介绍的是用XUtils下载apk文件,然后进行安装. 首先用HttpUtils下载文件(记得获取SD卡的读写权限和联网的权限): /** * 下载Apk */ private void downL ...
- Android 引导页公共方法LeaderPager
SimpAndroidFarme是近期脑子突然发热想做的android快速开发的框架,目标是模块化 常用的控件,方便新手学习和使用.也欢迎老鸟来一起充实项目:项目地址 引导页是我们开发app很常用的功 ...