政务低代码平台实战③:CommonSQL——一个接口扛起所有CRUD

📅 2026/8/11 20:19:39
政务低代码平台实战③:CommonSQL——一个接口扛起所有CRUD
政务低代码平台实战③CommonSQL——一个接口扛起所有CRUD文章目录政务低代码平台实战③CommonSQL——一个接口扛起所有CRUD背景CommonSQLcontrol的六个核心方法CommonQuery分页查询的完整流程空参数的智能处理自定义SQL的分页CommonSave按行状态分发增删改CommonSaveByseach用SELECT语句做增删改CommonUpate按SQL类型自动分发事务管理前端怎么调用和万能表单的配合决策原则非科班野生程序员深耕政务信息化20年。有了ea01-ea05元数据引擎SQL不用手写了。但光能拼SQL还不够还得有一个统一的入口接收前端请求、调commonSql拼装、执行并返回结果。这篇拆解CommonSQLcontrol怎么用一个Bean扛住所有表的增删改查。最后感谢豆包、智谱、OpenCode决策是我做的代码是我搓的文字是他们总结的。背景政务系统有50张业务表每张表都要查询、新增、修改、删除。如果每张表写一个Controller Service Mapper就是200个Java类。能不能所有表共用一个入口条件具备了——ea01-ea05已经把SQL结构化了commonSql已经能动态拼装SQL。缺的就是一个调度层前端传sqlId 参数进来调度层拿sqlId查元数据调commonSql拼SQL执行返回结果。这就是CommonSQLcontrol。CommonSQLcontrol的六个核心方法bean(idCommonSQL)publicclassCommonSQLcontrolimplementsControl{注册为BeanCommonSQL前端通过/BusinessAction?BusinessCommonSQLActionxxx调用。方法Action参数功能对应commonSql方法CommonQueryCommonQuery分页查询get()getCount()selectSQL()CommonQueryNoTrans—不开事务查询流程内部调用同上CommonUpateCommonUpate按sqlId对应的类型执行增删改根据ea01.eae004分发CommonSaveCommonSave按行状态分发增删改updateSQL/insertSql/DeleteSqlCommonSaveByseachCommonSaveByseach用SELECT语句的元数据做增删改updateSQLbySeach/insertSqlbySeachCommonSaveofbyseach—不开事务的保存流程内部调用同上CommonQuery分页查询的完整流程这是最复杂的方法展示了从前端参数到最终结果的完整链路publicDataCenterCommonQuery(DataCentercenter,...)throwsException{// 第一步开启事务DBUtil.BeginTrans(null,false);// 第二步从前端DataStore取参数DataStoredsparamcenter.getSingleDataStore();StringsqlId(String)dsparam.getParameter(sqlId);// 第三步读ea01判断是否自定义SQLListea01Daoresultea01DBUtil.getDao(CreateSqlMapper.class,getEa01,dao01);Stringeae800resultea01.get(0).getEae800();// 第四步过滤空参数HashMapmapnewHashMap();while(iter.hasNext()){StringvalString.valueOf(entry.getValue());if(val!null!.equals(val)!null.equals(val)){map.put(key,val);}}// 第五步如果前端没传总数查一次COUNTif(count0){if(1.equals(eae800)){countsqlobj.getCountcustom(sqlId,sql,map);// 自定义SQL}else{countsqlobj.getCount(sqlId,map);// 元数据拼装}}// 第六步计算分页边界map.put(minRow,(pagenumber-1)*pagesize);map.put(maxRow,pagenumber*pagesize);// 第七步执行查询DataStoredssqlobj.selectSQL(sqlId,map,common);ds.setReturnCondition(dsparam,count);// 把分页信息设回去// 第八步提交事务DBUtil.EndTrans();returndc;}前端只需要发一个请求vardsnewbrowise.ds.DataStore(common);ds.setParameter(sqlId,T_LEAVE_s);ds.setParameter(proc_inst_id_,12345);ds.setParameter(leave_type,年假);varURLwebRootDir/BusinessAction?BusinessCommonSQLActionCommonQuery;browise.Action.requestData({url:URL,sync:false,load:function(dc1){callback(dc1);}},dc,true);后端自动完成判断是否自定义SQL → 过滤空参数 → COUNT查询 → 计算分页 → 拼SELECT → PreparedStatement执行 → 返回DataStore。空参数的智能处理if(val!null!.equals(val)!null.equals(val)!\null\.equals(val)){map.put(key,val);}前端传了空值的参数直接丢弃。对应到SQL层面如果leave_type参数为空commonSql拼WHERE条件时发现map里没有这个key对应的条件就不拼。不需要前端传查询全部的标志。自定义SQL的分页自定义SQL的分页特殊处理——把用户写的SQL包一层// Oracleselect * from (select row_1.*, rownum as rownum_ from (用户SQL) row_1) row_ where row_.rownum_ ? and row_.rownum_ ?// MSSQLselect * from(select cte1.*,row_number() over (order by xxx desc) rownum_ from(用户SQL) as cte1) as cte where rownum_ ? and rownum_ ?分页参数minRow和maxRow作为最后两个?绑定。CommonSave按行状态分发增删改CommonSave的设计很巧妙——前端传一个DataStore里面包含行数据带状态标记以及三个参数up、ins、del指定用哪个sqlId做更新/新增/删除publicstaticDataCenterCommonSave(DataCentercenter,...)throwsException{Iteratoritercenter.getBody().getDatastores().entrySet().iterator();while(iter.hasNext()){DataStoreval(DataStore)entry.getValue();StringupSqlIdString.valueOf(val.getParameter(up));// 更新用的sqlIdStringinsSqlIdString.valueOf(val.getParameter(ins));// 新增用的sqlIdStringdelSqlIdString.valueOf(val.getParameter(del));// 删除用的sqlId// 按行状态分发if(!.equals(upSqlId)){// 校验要求更新但sqlId对应的ea01不是UPDATE类型报错resultea01system_cache.get1(upSqlId-ea01);if(!2.equals(sqlType))thrownewException(要求更新但不是更新语句!);update(sqlobj,val,upSqlId);}if(!.equals(insSqlId)){if(!4.equals(sqlType))thrownewException(要求插入但不是插入语句!);insert(sqlobj,val,insSqlId);}if(!.equals(delSqlId)){if(!3.equals(sqlType))thrownewException(要求删除但不是删除语句!);delete(sqlobj,val,delSqlId);}}}行状态来自BaseDao的状态机row.isInsert() true → 新增行row.isModefiy() true → 修改行删除列表ds.getRowset().getDelete()→ 删除行每个DataStore可以配不同的sqlId做不同的操作。一个Grid的数据可以用upT_LEAVE_uinsT_LEAVE_idelT_LEAVE_d三个sqlId对应三种操作。CommonSaveByseach用SELECT语句做增删改这个方法更巧妙——用SELECT语句的元数据ea01.eae004“1”来做增删改。为什么因为很多时候只配了SELECT的SQL元数据不想再单独配INSERT/UPDATE/DELETE。commonSql的updateByseach()和insertbySeach()方法从SELECT的ea03取字段列表来拼INSERT/UPDATE语句。还有一个特殊逻辑——非新增行也尝试插入如果COUNT0就插入否则跳过。这是为了兼容前端Grid的有则更新无则新增场景publicstaticvoidinsertbyseach(commonSql sqlobj,DataStoreds,Stringsqlid){for(inti0;ids.getRowset().getPrimary().size();i){Rowrowds.getRowset().getrow(i);if(row.isInsert()){sqlobj.insertSqlbySeach(null,sqlid,hvalue);}else{// 非新增行查一下有没有没有就插入intcountsqlobj.getCount(sqlid,hvalue);if(count0){sqlobj.insertSqlbySeach(null,sqlid,hvalue);}}}}CommonUpate按SQL类型自动分发CommonUpate从前端收到value值DataStore和where条件DataStore根据每行数据对应的sqlId的ea01类型自动分发if(2.equals(sqlType)){// ea01是UPDATE → 执行更新sqlobj.updateSQL(null,sqlId,hwhere,hvalue);}elseif(4.equals(sqlType)){// ea01是INSERT → 执行插入sqlobj.insertSql(null,sqlId,hvalue);}elseif(3.equals(sqlType)){// ea01是DELETE → 执行删除sqlobj.DeleteSql(null,sqlId,hwhere);}elseif(5.equals(sqlType)){// ea01是存储过程 → 调用存储过程sqlobj.callSql(null,sqlId,value,result);// 存储过程必须有 prm_appCode 和 prm_errMsg 出参if(Integer.parseInt(String.valueOf(result.get(prm_appCode)))-1){DBUtil.rollback();thrownewutilException(调用失败错误原因result.get(prm_errMsg),-9000);}}存储过程的支持是个加分项——政务系统有些复杂业务逻辑比如社保缴费用存储过程实现ea01.eae004“5”ea03存入参ea05存出参。事务管理所有写操作都有统一的事务管理try{DBUtil.BeginTrans(null,false);// ... 增删改操作 ...DBUtil.EndTrans();// 提交}catch(Exceptione){// 异常时不显式rollback靠finallydc.setCode(-1);dc.setDetail(e.getMessage());}finally{DBUtil.rollback();// 如果已提交则rollback是空操作否则回滚}DBUtil.rollback()是安全的——如果事务已经提交调用rollback什么都不做如果还没提交就回滚。这个finally里兜底rollback的模式保证了不会出现事务泄漏。前端怎么调用前端调用方式非常统一只变两个东西sqlId和参数。查询vardsnewbrowise.ds.DataStore(common);ds.setParameter(sqlId,T_LEAVE_s);ds.setParameter(leave_type,年假);// 分页信息由Grid的PagingManager自动设置varURLwebRootDir/BusinessAction?BusinessCommonSQLActionCommonQuery;browise.Action.requestData({url:URL,load:function(dc1){vardsdc1.getSingleDataStore();browise.byId(grid).getBinding().setDataStore(ds);}},dc,true);保存vardsbrowise.byId(grid).getBinding().getDataStore();ds.setParameter(up,T_LEAVE_u);// 更新用这个sqlIdds.setParameter(ins,T_LEAVE_i);// 新增用这个sqlIdds.setParameter(del,T_LEAVE_d);// 删除用这个sqlIdvarURLwebRootDir/BusinessAction?BusinessCommonSQLActionCommonSave;browise.Action.requestData({url:URL,load:function(dc1){alert(dc1.getTitle());// 保存成功!}},dc,true);一张新表从建到用到查询全程不用写后端代码。和万能表单的配合万能表单onlyFrom.jsp在运行时调allbytaskid接口一次性拿6组数据其中就包括通过CommonSQL查询的业务表数据。allbytaskid内部调CommonSQLcontrol.CommonQuery传sqlId表名_sproc_inst_id_流程实例ID拿到表单数据绑到前端控件上。保存时调CommonSaveofbyseach不开事务版本因为流程引擎自己管事务传Grid数据和sqlId自动完成增删改。完整链路建表DDL引擎→ 自动注册ea01-ea05 → 表单设计器绑定sqlId → 用户打开待办 → allbytaskid → CommonQuery(sqlId) → 前端渲染 → 用户填写 → 提交 → CommonSaveofbyseach(sqlId) → 自动拼INSERT/UPDATE/DELETE决策原则一个入口统一所有数据操作。50张表每张表4种操作不需要200个接口。前端只需要知道sqlId后端自动从元数据生成SQL、自动分页、自动绑定参数、自动管理事务。新增一张表只需要在ea01-ea05里加几行数据前端改一下sqlId参数后端不用动。你项目里是怎么统一CRUD接口的是每个表一个接口还是通用接口欢迎评论区聊聊。系列导航总纲[政务低代码平台实战——从元数据引擎到可视化设计器的五个关键决策]上一篇[政务低代码平台实战②运行时DDL引擎前端拖完字段后端直接建]下一篇[政务低代码平台实战④可视化表单设计器——拖拽、合并拆分、代码生成]作者许彰午| 非科班野生程序员深耕政务信息化20年标签#Java #低代码 #通用CRUD #动态SQL #MyBatis #政务信息化