序言
最近公司里有一个新的需求,需要导出数据库元数据表中存储的表的 DDL 语句,而在元数据表中数据源的类型庞大,少则十几种多则达到几十种,各种数据库类型都有,比如常见就有 MySQL
PostgreSQL
Oracle
SQL Server
等等,而不同的数据库其数据类型和语法都存在一些差异,如果在代码中去处理工作量就太大了。所以我想到使用模版引擎来处理,现在市面上出现了一个新的模版引擎 Beetl。本篇文章就从这个模版引擎来入手介绍如何实现功能。代码已经提交到了我的 GitHub 仓库
Beetl
Beetl
的使用方式和 Thymeleaf
、Freemarker
差不多,而且其语法简单,性能也优于这两者,目前在国内已经有一定的用户群体。话不多说,具体的使用方式非常简单,大家看一下官网就会了,下面来说功能怎么实现
代码
使用前在SpringBoot中使用的时候需要先导入依赖,我测试的 SpringBoot 的版本为 3.2.1
xml
<dependency>
<groupId>com.ibeetl</groupId>
<artifactId>beetl-spring-boot-starter-classic</artifactId>
<version>3.19.1.RELEASE</version>
</dependency>
创建一下库表,因为模版文件是存储在数据库中,方便后期维护
sql
create table ddl_template
(
id int auto_increment
primary key,
name varchar(255) not null,
description varchar(255) null,
created_at timestamp not null,
updated_at timestamp null,
created_by varchar(255) null,
updated_by varchar(255) null,
version float not null,
db_type varchar(255) not null,
template text not null
)
comment 'ddl模板表' charset = utf8mb4;
表创建完成后,我事先往这个表中添加了三条数据,分别代表 MySQL、Oracle、PgSQL 的DDL 模版

MySQL:
sql
drop table if exists `${table.tableCode}`;
create table ${table.tableCode}(
<%
for(field in fieldList){
var code = field.code;
var comment = field.comment;
var type = field.type;
var len = field.len;
var scale = field.scale;
var notNull = field.notNull;
var autoIncrement = field.autoIncrement;
var defaultValue = field.defaultValue;
var last = fieldLP.last;
if (len != null) {
if (len > 0) {
var temp = "(" + len;
if (scale != null) {
if (scale > 0) {
temp = temp + "," + scale;
}
}
temp = temp + ")";
type = type + temp;
}
}
%>
`${field.code}` ${type}${autoIncrement ? " auto_increment":""}${notNull ? " not null":" null"}${defaultValue != null ? " default '" + defaultValue + "'" : "" }
${comment != null ? " comment '" + comment + "'":""}${!last ? ",":""}
<%
}
%>
<%
if (primaryKey != null){
%>
,primary key (${primaryKey})
<%
}
%>
) comment = '${table.tableComment}';
Oracle:
sql
create table "${table.tableCode}"(
<%
for(field in fieldList){
var code = field.code;
var comment = field.comment;
var type = field.type;
var len = field.len;
var scale = field.scale;
var primaryKey = field.primaryKey;
var notNull = field.notNull;
var defaultValue = field.defaultValue;
var last = fieldLP.last;
if (len != null && type != 'date') {
if (len > 0) {
var temp = "(" + len;
if (scale != null) {
if (scale > 0) {
temp = temp + "," + scale;
}
}
temp = temp + ")";
type = type + temp;
}
}
if (type == 'date') {
defaultValue = null;
notNull = false;
}
%>
"${field.code}" ${type}${defaultValue != null ? " default '" + defaultValue + "'" : "" }${notNull ? " not null":" null"}${!last ? ",":""}
<%
}
%>
<%
if (primaryKey != null){
%>
,primary key ("${primaryKey}")
<%
}
%>
);
comment on table "${table.tableCode}" is '${table.tableComment}';
<%
for(field in fieldList){
var code = field.code;
var comment = field.comment;
var last = fieldLP.last;
%>
comment on column "${table.tableCode}"."${code}" is '${comment}';
<%
}
%>
PgSQL:
SQL
drop table if exists "${table.tableCode}";
create table "${table.tableCode}"(
<%
for(field in fieldList){
var code = field.code;
var comment = field.comment;
var type = field.type;
var len = field.len;
var scale = field.scale;
var notNull = field.notNull;
var autoIncrement = field.autoIncrement;
var defaultValue = field.defaultValue;
var last = fieldLP.last;
if (len != null) {
if (len > 0) {
var temp = "(" + len;
if (scale != null) {
if (scale > 0) {
temp = temp + "," + scale;
}
}
temp = temp + ")";
type = type + temp;
}
}
%>
"${code}" ${type}${autoIncrement ? " generated always as identity" : ""}${notNull ? " not null" : ""}${defaultValue != null ? " default '" + defaultValue + "'" : "" }
${!last ? "," : ""}
<%
}
%>
<%
if (primaryKey != null){
%>
,primary key (${primaryKey})
<%
}
%>
);
comment on table "${table.tableCode}" is '${table.tableComment}';
<%
for(field in fieldList){
var code = field.code;
var comment = field.comment;
if (comment != null) {
%>
comment on column "${table.tableCode}"."${code}" is '${comment}';
<%
}
}
%>
准备工作完成后,接下来开发代码,具体代码如下:
java
@Bean
public GroupTemplate groupTemplate() throws Exception {
StringTemplateResourceLoader loader = new StringTemplateResourceLoader();
org.beetl.core.Configuration cfg = org.beetl.core.Configuration.defaultConfiguration();
GroupTemplate gt = new GroupTemplate(loader, cfg);
return gt;
}
这里使用的 StringTemplateResourceLoader
表示从字符串中来加载模板,也有其他的 ResourceLoader
,比如 FileResourceLoader
表示从文件中加载模版,具体的看官网就可以了
java
/**
* 单张表的ddl语句创建
*
* @return
*/
@PostMapping("/createDdl")
public void createDdl(@RequestParam("version") String version,@RequestParam("dbType") String dbType) {
ddlTemplateService.createDdl(version, dbType);
}
java
@Override
public void createDdl(String version, String dbType) {
DbEnum dbEnum = DbEnum.fromString(dbType);
Assert.notNull(dbType, "数据库类型不支持");
switch (dbEnum) {
case MYSQL:
generateMysqlDdl(version, dbType, buildMySQLData());
break;
case ORACLE:
generateOracleDdl(version, dbType, buildOracleData());
break;
case POSTGRESQL:
generatePostgresqlDdl(version, dbType, buildPostgresqlData());
break;
}
}
这里支持的数据库类型还比较少,大家可以后面扩充
java
/**
* 这里只是模拟一下,实际情况应该是从数据库中查询的对应的表及字段
*
* @return
*/
private Map<String, Object> buildMySQLData() {
MySQLTable tableVO = MySQLTable.builder().tableCode("t_user").tableComment("用户表").build();
List<MySQLField> fieldVOList = List.of(
MySQLField.builder().code("id").comment("主键").type("int").len(11).scale(0.0).autoIncrement(true).notNull(true).build(),
MySQLField.builder().code("name").comment("姓名").type("varchar").len(50).scale(0.0).autoIncrement(false).defaultValue("").notNull(true).build(),
MySQLField.builder().code("age").comment("年龄").type("int").len(11).scale(0.0).autoIncrement(false).notNull(true).build(),
MySQLField.builder().code("email").comment("邮箱").type("varchar").len(50).scale(0.0).autoIncrement(false).defaultValue("").notNull(true).build(),
MySQLField.builder().code("create_time").comment("创建时间").type("date").len(0).scale(0.0).autoIncrement(false).notNull(true).notNull(true).build()
);
return Map.of("table", tableVO, "fieldList", fieldVOList, "primaryKey", "id");
}
private Map<String, Object> buildOracleData() {
OracleTable tableVO = OracleTable.builder().tableCode("t_user").tableComment("用户表").build();
List<OracleField> fieldVOList = List.of(
OracleField.builder().code("id").comment("主键").type("NUMBER").len(11).scale(0.0).autoIncrement(true).notNull(true).build(),
OracleField.builder().code("name").comment("姓名").type("varchar").len(50).scale(0.0).autoIncrement(false).defaultValue("").notNull(true).build(),
OracleField.builder().code("age").comment("年龄").type("NUMBER").len(11).scale(0.0).autoIncrement(false).notNull(true).build(),
OracleField.builder().code("email").comment("邮箱").type("varchar").len(50).scale(0.0).autoIncrement(false).defaultValue("").notNull(true).build(),
OracleField.builder().code("create_time").comment("创建时间").type("date").len(0).scale(0.0).autoIncrement(false).notNull(true).notNull(true).build()
);
return Map.of("table", tableVO, "fieldList", fieldVOList, "primaryKey", "id");
}
private Map<String, Object> buildPostgresqlData() {
PostgresqlTable tableVO = PostgresqlTable.builder().tableCode("t_user").tableComment("用户表").build();
List<PostgresqlField> fieldVOList = List.of(
PostgresqlField.builder().code("id").comment("主键").type("integer").autoIncrement(true).notNull(true).build(),
PostgresqlField.builder().code("name").comment("姓名").type("varchar").len(50).scale(0.0).autoIncrement(false).defaultValue("").notNull(true).build(),
PostgresqlField.builder().code("age").comment("年龄").type("int").autoIncrement(false).notNull(true).build(),
PostgresqlField.builder().code("email").comment("邮箱").type("varchar").len(50).scale(0.0).autoIncrement(false).defaultValue("").notNull(true).build(),
PostgresqlField.builder().code("create_time").comment("创建时间").type("timestamp").len(0).scale(0.0).autoIncrement(false).notNull(true).notNull(true).build()
);
return Map.of("table", tableVO, "fieldList", fieldVOList, "primaryKey", "id");
}
private void generateMysqlDdl(String version, String dbType, Map<String, Object> data) {
try {
// 可以考虑将模版文件缓存
Template template = groupTemplate.getTemplate(queryByUnique(version, dbType).getTemplate());
template.binding(data);
String render = template.render();
log.info("生成MysqlDDL语句成功");
FileUtil.writeUtf8String(render, "D:/ddl.sql");
} catch (Exception e) {
throw new RuntimeException("生成DDL语句失败", e);
}
}
private void generateOracleDdl(String version, String dbType, Map<String, Object> data) {
try {
// 可以考虑将模版文件缓存
Template template = groupTemplate.getTemplate(queryByUnique(version, dbType).getTemplate());
template.binding(data);
String render = template.render();
log.info("生成OracleDDL语句成功");
FileUtil.writeUtf8String(render, "D:/ddl.sql");
} catch (Exception e) {
throw new RuntimeException("生成DDL语句失败", e);
}
}
private void generatePostgresqlDdl(String version, String dbType, Map<String, Object> data) {
// 可以考虑将模版文件缓存
Template template = groupTemplate.getTemplate(queryByUnique(version, dbType).getTemplate());
template.binding(data);
String render = template.render();
log.info("生成PgSQLDDL语句成功");
FileUtil.writeUtf8String(render, "D:/ddl.sql");
}
我这里运行后会分别针对不同的数据库类型来生成对应的表 DDL 语句,效果如下:
MySQL
SQL
drop table if exists `t_user`;
create table t_user(
`id` int(11) auto_increment not null
comment '主键',
`name` varchar(50) not null default ''
comment '姓名',
`age` int(11) not null
comment '年龄',
`email` varchar(50) not null default ''
comment '邮箱',
`create_time` date not null
comment '创建时间'
,primary key (id)
) comment = '用户表';
Oracle
sql
create table "t_user"(
"id" NUMBER(11) not null,
"name" varchar(50) default '' not null,
"age" NUMBER(11) not null,
"email" varchar(50) default '' not null,
"create_time" date null
,primary key ("id")
);
comment on table "t_user" is '用户表';
comment on column "t_user"."id" is '主键';
comment on column "t_user"."name" is '姓名';
comment on column "t_user"."age" is '年龄';
comment on column "t_user"."email" is '邮箱';
comment on column "t_user"."create_time" is '创建时间';
PgSQL
SQL
drop table if exists "t_user";
create table "t_user"(
"id" integer generated always as identity not null
,
"name" varchar(50) not null default ''
,
"age" int not null
,
"email" varchar(50) not null default ''
,
"create_time" timestamp not null
,primary key (id)
);
comment on table "t_user" is '用户表';
comment on column "t_user"."id" is '主键';
comment on column "t_user"."name" is '姓名';
comment on column "t_user"."age" is '年龄';
comment on column "t_user"."email" is '邮箱';
comment on column "t_user"."create_time" is '创建时间';
这三个 DDL 语句在对应的数据库里面是可以运行



总结
这里采用数据库存储模版的方式比在代码中硬编码维护性要好,不然模版有改动的话,还需要去修改代码重新打包,总体下来并不是很难。