我正在做一个开源的低代码平台:https://gitee.com/IElwin/ezlcp-java,了解代代码平台的都知道,会有自定义数据源、自定义表(实体)、自定义表单、自定义报表等功能。
关于自定义数据源,如果只支持一种数据库的话,开发起来就会容易些。目前很多平台是支持多种数据库的,如:MySQL
、Oracle
、PostgreSQL
、SQL Server
,还有的会支持国产的数据库,如:达梦
、人大金仓
等。
如果表结构是固定的,我们可以使用MyBatis
、Hibernate
等ORM
工具类库来操作表;但是需求中表结构是用户自定义的,可能随时变动,使用ORM工具就不适合了。那怎么实现这些用户自定义动态表的创建、修改表结构、查询、插入等操作呢?接下来比较一下各实现方案:
方法一:使用JdbcTemplate全手写SQL
当然直接用jdbc来实现也是可以的,但用spring的JdbcTemplate
会更优雅一些,可以少写一些代码。我看过一个产品的源码就是用这种方式来实现的。使用的是策略设计模式,定义一个数据操作的接口,每一种数据库就写一个实现该接口的操作类。当进行数据库操作时,会根据DruidDataSource
数据源的getDbType
方法来获取数据库类型,找到相应的操作类Bean,再执行相应的方法。这是最基本也是最灵活的方案,但是会有很多代码量,也需要花大量的时间来测试。之前用过的那个产品在MySQL数据库下运行正常,切换到Oracle数据源时一大堆问题出现。我就想有没有这方面开源组件,节省开发时间和测试时间。
方法二:使用AnyLine
AnyLine
是国内的开源java组件,官网地址:http://doc.anyline.org/,它底层的实现方式类似于第一种方法。使用时,如果要支持某一种数据,就引用该数据库的相关的Jar包,官方文档中写的支持多种数据库,还有些国产数据库也支持。完全开源免费的,也可以切换数据源,所以我就试用了一下,测试主要代码如下:
service.setDataSource(dsName);
Table table = service.metadata().table(null, null, tableName);
if (null != table) {
service.ddl().drop(table);
}
table = new Table(tableName);
table.setComment("测试创建的表");
table.addColumn("id", "varchar(64)").setPrimaryKey(true).setComment("主键");
table.addColumn("name", "varchar(50)").setComment("姓名");
table.addColumn("age", "int").setComment("年龄");
table.addColumn("create_time", "datetime").setComment("创建时间");
table.addColumn("create_by", "varchar(64)").setComment("创建人");
service.ddl().create(table);
//插入数据
DataRow row = new DataRow();
row.put("id","101");
row.put("name","张三");
String now= DateUtils.getTime();
row.put("age",60);
row.put("create_time",now);
service.insert(tableName, row);
//查询数据
DataSet set = service.querys(tableName);
service.setDefaultDataSource();
pom.xml文件相关内容如下:
<!-- 每一种数据库要引用一个类库,还要引入该数据库的jdbc驱动程序 -->
<dependency>
<groupId>org.anyline</groupId>
<artifactId>anyline-data-jdbc-postgresql</artifactId>
<version>8.6.3-20230625</version>
</dependency>
<dependency>
<groupId>org.anyline</groupId>
<artifactId>anyline-data-jdbc-oracle</artifactId>
<version>8.6.3-20230625</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.6.0</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>23.2.0.0</version>
</dependency>
就是先切换到指定的数据源,然后创建表,插入一条数据,再查询该表,最后切换回默认数据源。日期类型在MySQL
数据库下,直接传new Date() 或者时间字符串都是可以的;但切换到PostgreSQL
两种类型值都会报错了。说明AnyLine
也不是很稳定,还需要测试以及对某种数据库特殊处理才可以。
方法三: 使用jDialects
jDialects
项目地址:https://gitee.com/drinkjava2/jdialects,也是国内的免费开源组件,并不是很出名,但非常轻量极。官方的介绍:jDialects是一个Java数据库方言工具,支持80多种数据库方言,具有分页、函数变换、类型变换、DDL生成、JPA注解解析等功能,它通常与JDBC工具组合使用。
pom.xml引入:
<dependency>
<groupId>com.github.drinkjava2</groupId>
<artifactId>jdialects</artifactId>
<version>5.0.13.jre8</version>
</dependency>
因为这个类库只负责生成SQL,至于SQL的执行还是需要引入相关的JDBC驱动程序的。测试代码如下:
var ds = DruidUtils.getDataSource(dsId);
var template = new JdbcTemplate(ds);
TableModel t = new TableModel(tableName);
t.comment("测试创建的表");
t.column("id").VARCHAR(64).pkey().comment("主键");
t.column("name").VARCHAR(50).comment("姓名");
t.column("age").INTEGER().comment("年龄");
t.column("create_time").TIMESTAMP().comment("创建时间");
t.column("create_by").VARCHAR(64).comment("创建人");
Dialect dialect = Dialect.guessDialect(ds);
String[] ddlArray = dialect.toDropAndCreateDDL(t);
for (String ddl : ddlArray) {
template.execute(ddl);
}
String now = DateUtils.getTime();
String sql = "insert into " + tableName + " (id,name,age,create_time) values('101','李明',18,'" + now + "')";
template.execute(sql);
sql = "select * from " + tableName;
JsonResult result = JsonResult.getSuccessResult("common.handleSuccess");
var data = template.queryForList(sql);
测试了MySQL
和PostgreSQL
两种数据库都是可以正常运行的,所以就决定是你了。
方法四: 使用jOOQ
jOOQ
官网:https://www.jooq.org/,看了教程觉得用起来很简单的;但是它是一个商用软件,虽然也有开源版本。开源版本只支持MySQL
、PostgreSQL
还有一些小众数据库,而且只支持特定的版本(比如MySQL
只支持8.0.31版,其他旧版本不支持),觉得开源版本太鸡肋,决定放弃试用。如果你不在乎钱的话可以使用,商业版也可以免费试用30天。
总结
最终的决定采用的方案是:先定义统一的操作接口(方法一、二中都有接口),然后建立一个默认的实现类(调用jDialects,应该可以兼容大多数常用的数据库);对于jDialects
未实现的或者出错的数据库,再新建一个实现类(可继承默认实现类)来处理。