一、前言
实际开发工作中,往往会对系统进行领域模型的设计,设计系统的可扩展性、健壮性等。同时也避免不了对数据的基本管理,比如增删改查、接口的定义与实现等。有些工作其实是类似的重复的,与业务逻辑关系不大的,这些工作往往可以通过系统自动完成,进而可以将人力成本主要集中在领域模型的设计与实现上。
为了实现这个小目标,调研了业界用的比较好的框架,用得比较广泛的就是MybatisGenerator、MyBatis-Plus BaseMapper。这些框架的核心能力都是减少持久层功能的开发工作量。相信用过的同学都应该知道,实际使用过程中,应该是像下面这样的。
1.1 MybatisGenerator生成代码的使用方式
private ProductParam buildSearchParam(ProductQueryParam productQueryParam){
ProductParam param = new ProductParam();
ProductParam.Criteria cr = param.createCriteria();
if(productQueryParam.getUserId() != null){
cr.andUserIdEqualTo(productQueryParam.getUserId());
}
if(productQueryParam.getUserId() != null){
cr.andUserIdEqualTo(productQueryParam.getUserId());
}
if(productQueryParam.getName() != null){
cr.andNameEqual(productQueryParam.getName());
}
if(productQueryParam.getUserId() != null){
cr.andUserIdEqualTo(productQueryParam.getUserId());
}
if(productQueryParam.getUserId() != null){
cr.andUserIdEqualTo(productQueryParam.getUserId());
}
if(productQueryParam.getUserId() != null){
cr.andUserIdEqualTo(productQueryParam.getUserId());
}
if(productQueryParam.getUserId() != null){
cr.andUserIdEqualTo(productQueryParam.getUserId());
}
if(productQueryParam.getUserId() != null){
cr.andUserIdEqualTo(productQueryParam.getUserId());
}
if(productQueryParam.getUserId() != null){
cr.andUserIdEqualTo(productQueryParam.getUserId());
}
return param;
}
1.2 MyBatis-Plus BaseMapper的使用方式
public Condition buildCondition(ProductQueryParam productQueryParam){
Condition condition = new Condition(ProductDO.class);
condition.orderBy("gmtCreate").desc();
Criteria criteria = condition.createCriteria();
if(productQueryParam.getUserId() != null){
criteria.andEqualTo("userId", productQueryParam.getUserId());
}
if(productQueryParam.getUserName() != null){
criteria.andEqualTo("userName", productQueryParam.getUserName());
}
if(productQueryParam.getUserId() != null){
criteria.andEqualTo("userId", productQueryParam.getUserId());
}
if(productQueryParam.getUserId() != null){
criteria.andEqualTo("userId", productQueryParam.getUserId());
}
if(productQueryParam.getUserId() != null){
criteria.andEqualTo("userId", productQueryParam.getUserId());
}
if(productQueryParam.getUserId() != null){
criteria.andEqualTo("userId", productQueryParam.getUserId());
}
if(productQueryParam.getUserId() != null){
criteria.andEqualTo("userId", productQueryParam.getUserId());
}
}
可见两种使用方式都不够优雅,都需要大量的繁琐的构造Condition的代码块,可读性也不够友好。MyBatis-Plus BaseMapper构造条件中甚至还有很多魔法值,非常不利于代码的维护。
如何让开发人员优雅地构造出Condition,甚至像构造POJO一样简单地构造出Condition,优雅地与持久层进行交互,这就是本开发框架要解决的问题。
二、低代码数据持久层框架介绍
2.1 总体架构图
2.2 整体架构理念
1)对开发者开放的这一层,由DataService、DataParam、DataObject组成,开发者只需要关心业务逻辑对应的语义条件,将语义条件传达给DataParam,整个传达的过程非常简单,像构造一个简单的POJO一样简单。然后通过DataService就可以完成各种存储操作了。具体底层是怎么使用语义条件的,开发者无需关心。
2)无论存储的具体实现是基于何种软件,mysql、mogodb、opensearch等,对用户开发者而言都是透明的。开发者都只需要面向语义编程即可。
3)对于系统接口方法的解释说明,代码本身的语义化强于代码注释,代码注释强于外部的文档说明。过去我们需要不断地构造复杂冗余的Mapper操作条件,或者尽管把一些常用的接口封装好了可以重复使用,但是当另一个人来使用这些封装好的接口时,看到入参中的一个个字段时也还是一脸懵逼,因为不知道这些字段背后到底是支持equal查询、notEqual查询或者还是Like查询等等,使用者必须把整个方法到具体的sql实现看一遍才能放心使用。不排除有人能够把注释写得很清晰,但是随着系统迭代,注释的可靠性也会不断降低。对使用者很不友好,开发效率很低。使用本套框架,这些问题都将一去不复返。
三、如何实现低代码数据持久层框架
使用本开发工具可以非常快速简单地实现一个面向语义的低代码数据持久层编程框架。
3.1 在需要使用本框架的应用中引入相关依赖
<dependency>
<artifactId>fast-frame</artifactId>
<groupId>com.rhc</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
3.2 在应用的application.properties配置文件中进行相关配置
一次配置,多表复用
#################################
## 低代码数据持久层编程框架相关配置 ##
#################################
connect.driver=com.mysql.jdbc.Driver
connect.url=jdbc:mysql://xxx:3306/aaa
connect.userId=xxx
connect.password=xxx
#需要自动生成代码的表名,多个表名用英文逗号分割
table.name=common_tenant,common_log,common_notice
# 重复执行时是否覆盖老的
sql.overwrite=true
## 配置相关文件生成的目标地址
## sqlMap的xml配置文件既可以放在resource资源目录下面,也可以放在源码目录下
## 当目录中含有'com'时,则默认放在源码目录下,否则默认放在resource资源目录下
#sql.xml.targetPackage=META-INF.sqlmap
sql.xml.targetPackage=com.rhc.exam4.sqlmap
# 配置生成的dataObject文件存放的package
data.object.package=com.rhc.exam4.data.internal.dataobject
# 配置查询条件的后缀名
criteria.suffix=Criteria
# 配置生成的Mapper文件存放的package
data.mapper.package=com.rhc.exam4.data.internal.dao
# 配置负责语义化转化的相关文件存放的package
criteria.convertor.package=com.rhc.exam4.data.internal.transfer
# 配置负责语义化转化的相关文件的后缀名
criteria.convertor.suffix=Transfer
# 配置数据层服务入参相关文件存放的package
data.param.package=com.rhc.exam4.data.param
# 配置数据层服务入参的后缀名
data.param.suffix=DataParam
# 配置数据层服务相关文件存放的package
data.service.package=com.rhc.exam4.data.service
data.service.suffix=DataService
data.service.impl.package=com.rhc.exam4.data.service.impl
data.service.impl.suffix=DataServiceImpl
3.3 开始执行生成数据持久层编程框架
public static void main(String[] args) {
EffectiveDataBootStrap.runAuto();
}
3.4 生成代码的效果
在配置的相关目录下就可以找到自动生成的数据持久层框架相关代码了,如下图所示。
3.5 实际使用
开发使用者,大部分情况下只需要关心上图红框部分的接口,直接引入数据服务使用即可,代码简洁易读。可以满足99%的数据库交互场景。少部分不支持的复杂场景,开发者也是可以像平常写代码一样进行扩展。
CommonLogDbParam commonLogDbParam = new CommonLogDbParam();
commonLogDbParam.setIdIn(Arrays.asList(32L,43L));
commonLogDbParam.setOperatorLike("operator");
commonLogDbParam.setGmtModifyLessThanOrEqualTo(new Date());
commonLogDbParam.setGmtCreateGreaterThanOrEqualTo(new Date());
List<CommonLog> result = commonLogDataServ.select(commonLogDbParam);
四、低代码开发工具的实现原理
4.1 实现原理图
4.2 实现说明
整套工具实现低代码的内核用的是MybatisGenerator。 基于上述理念以及插件的扩展能力实现了整套数据层服务的低代码编程能力。
这套低代码开发工具的实现遵从以下理念:
1)dataService层与Mapper层的适配
2)mapper层与具体存储介质的连接适配
3)查询条件语义化,见名知义
4)整体框架代码自动生成,类文件路径可配置,模型后缀可配置
5)插件可插拔编程模式
6)支持多种数据库连接驱动