如何写一个代码生成器
大约 6 分钟
代码生成器是一种工具,它可以根据特定的输入(如数据库模式、UML模型或其他规格)自动产生源代码。这在软件开发中可以节省大量时间,特别是当需要处理大量的样板代码时。
例如,如果你正在构建一个基于数据库的应用,你可以使用一个代码生成器来创建CRUD(创建、读取、更新、删除)操作的全部代码,或者生成数据访问对象(DAOs)。同样,如果你有一个复杂的类结构定义,代码生成器可以帮助你快速生成所有的类文件。
数据准备
获取数据库表元数据
如何获取某个数据库下的所有表的信息呢?Jdbc驱动给我们提供了与数据库无关的获取元数据的方法,首先使用Jdbc链接数据库
conn=DriverManager.getConnection(props.getProperty("url"),props.getProperty("uname"),props.getProperty("pwd"));
- 获取连接元数据
DatabaseMetaData metaData = conn.getMetaData();
- 获取表名称
ResultSet tableNameResultSet = metaData.getTables(database, null, null, null);
while (tableNameResultSet.next()){
//获取表名
String tableName = tableNameResultSet.getString("TABLE_NAME");
// 获取表注释
String tableComment = tableNameResultSet.getString("REMARKS");
{
下面是getTables返回的相关信息:
TABLE_CAT String => 表类别(可为 null)
TABLE_SCHEM String => 表模式(可为 null)
TABLE_NAME String => 表名称
TABLE_TYPE String => 表类型。典型的类型是 "TABLE"、"VIEW"、"SYSTEM TABLE"、"GLOBAL TEMPORARY"、"LOCAL TEMPORARY"、"ALIAS" 和 "SYNONYM"。
REMARKS String => 表的解释性注释
TYPE_CAT String => 类型的类别(可为 null)
TYPE_SCHEM String => 类型模式(可为 null)
TYPE_NAME String => 类型名称(可为 null)
SELF_REFERENCING_COL_NAME String => 有类型表的指定 "identifier" 列的名称(可为null)
REF_GENERATION String => 指定在 SELF_REFERENCING_COL_NAME 中创建值的方式。这些值为 "SYSTEM"、"USER" 和 "DERIVED"。(可能为null)
- 获取表所有的列
ResultSet cloumnsSet = metaData.getColumns(database, null, tableName, null);
while (cloumnsSet.next()){
//列的描述
String remarks = cloumnsSet.getString("REMARKS");
//获取列名
String columnName = cloumnsSet.getString("COLUMN_NAME");
// 省略部分
{
下面是getColumns返回的相关信息:
TABLE_CAT String => 表类别(可为 null)
TABLE_SCHEM String => 表模式(可为 null)
TABLE_NAME String => 表名称
COLUMN_NAME String => 列名称
DATA_TYPE int => 来自 java.sql.Types 的 SQL 类型
TYPE_NAME String => 数据源依赖的类型名称,对于 UDT,该类型名称是完全限定的
COLUMN_SIZE int => 列的大小。对于 char 或 date 类型,列的大小是最大字符数,对于 numeric 和 decimal 类型,列的大小就是精度。
BUFFER_LENGTH 未被使用。
DECIMAL_DIGITS int => 小数部分的位数
NUM_PREC_RADIX int => 基数(通常为 10 或 2)
NULLABLE int => 是否允许使用 NULL。
columnNoNulls - 可能不允许使用 NULL 值
columnNullable - 明确允许使用 NULL 值
columnNullableUnknown - 不知道是否可使用 null
REMARKS String => 描述列的注释(可为 null)
COLUMN_DEF String => 默认值(可为 null)
SQL_DATA_TYPE int => 未使用
SQL_DATETIME_SUB int => 未使用
CHAR_OCTET_LENGTH int => 对于 char 类型,该长度是列中的最大字节数
ORDINAL_POSITION int => 表中的列的索引(从 1 开始)
IS_NULLABLE String => "NO" 表示明确不允许列使用 NULL 值,"YES" 表示可能允许列使用 NULL 值。空字符串表示没人知道是否允许使用 null 值。
SCOPE_CATLOG String => 表的类别,它是引用属性的作用域(如果 DATA_TYPE 不是 REF,则为null)
SCOPE_SCHEMA String => 表的模式,它是引用属性的作用域(如果 DATA_TYPE 不是 REF,则为null)
SCOPE_TABLE String => 表名称,它是引用属性的作用域(如果 DATA_TYPE 不是 REF,则为 null)
SOURCE_DATA_TYPE short => 不同类型或用户生成 Ref 类型、来自 java.sql.Types 的 SQL 类型的源类型(如果 DATA_TYPE 不是 DISTINCT 或用户生成的 REF,则为null)
- 获取主键信息
ResultSet keySet = metaData.getPrimaryKeys(database, null, tableName);
while (keySet.next()){
key=keySet.getString("COLUMN_NAME");
}
下面是getPrimaryKeys返回的相关信息:
TABLE_CAT String => 表类别(可为 null)
TABLE_SCHEM String => 表模式(可为 null)
TABLE_NAME String => 表名称
COLUMN_NAME String => 列名称
KEY_SEQ short => 主键中的序列号
PK_NAME String => 主键的名称(可为 null)
获取索引信息
ResultSet indexInfoSet = metaData.getIndexInfo(database, null, tableName, false, false); while (indexInfoSet.next()) { String indexName = indexInfoSet.getString("INDEX_NAME"); String colName = indexInfoSet.getString("COLUMN_NAME"); }
下面是getIndexInfo返回的相关信息:
NON_UNIQUE int => 非唯一索引
INDEX_QUALIFIER String => 索引目录(可为 null)
INDEX_NAME String => 索引的名称
TYPE short => 索引类型
ORDINAL_POSITION short => 在索引列顺序号
COLUMN_NAME String => 列名
ASC_OR_DESC String => 列排序顺序:升序还是降序
CARDINALITY int => 基数
模板编写
使用 freemarker来编写模板
配置模板引擎
public static Template loadTemplate(String path, String ftl) throws Exception{
// 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是freemarker对于的版本号。
Configuration configuration = new Configuration(Configuration.getVersion());
// 第二步:设置模板文件所在的路径。
configuration.setDirectoryForTemplateLoading(new File(path));
// 第三步:设置模板文件使用的字符集。一般就是utf-8.
configuration.setDefaultEncoding("utf-8");
// 第四步:加载一个模板,创建一个模板对象。
Template template = configuration.getTemplate(ftl);
return template;
}
使用方法:
public static void writer(Template template,Map dataModel,String file) throws Exception{
// 创建一个Writer对象,一般创建一FileWriter对象,指定生成的文件名。
Writer out = new FileWriter(file);
// 调用模板对象的process方法输出文件。
template.process(dataModel, out);
// 关闭流。
out.close();
}
将模型需要的信息存入 Map dataModel,模板引擎会自动帮我们渲染。
具体的模板代码不同的项目可以自己定义,下面给出一个自用的生成model的模板:
package ${package_pojo};
<#if swagger==true>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
import com.libra.atpbot.common.dao.sql.Column;
import com.libra.atpbot.common.dao.sql.Entity;
import com.libra.atpbot.common.dao.sql.Table;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.RowSet;
import io.vertx.core.json.JsonObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
<#list typeSet as set>
import ${set};
</#list>
/**
* ${tableComment!""}
* auto generate by tool, do not modify
**/
<#if swagger==true>
@ApiModel(description = "${Table}",value = "${Table}")
</#if>
@Table("${TableName}")
public class ${Table} implements Serializable, Entity<${Table}> {
private static final Logger logger = LoggerFactory.getLogger(${Table}.class);
<#list models as model>
<#if swagger==true>
@ApiModelProperty(value = "${model.desc!""}",required = false)
</#if>
/**
* ${model.desc!""}
*/
@Column("${model.column}")
private ${model.simpleType} ${model.name};
</#list>
<#if true>
<#list models as model>
public transient static final String C_${model.column?upper_case} = "${model.columnEscape}";
</#list>
</#if>
<#if true>
<#list models as model>
public transient static final String T_${model.column?upper_case} = "${TableName}.${model.columnEscape}";
</#list>
</#if>
public transient static final String table = "${TableName}";
<#if true>
<#noparse>
public static List<String> allColumn() {
List<String> res = new ArrayList<>();
</#noparse>
<#list models as model>
res.add(C_${model.column?upper_case});
</#list>
return res;
}
<#noparse>
public static List<String> allTableColumn() {
List<String> res = new ArrayList<>();
</#noparse>
<#list models as model>
res.add(T_${model.column?upper_case});
</#list>
return res;
}
public static List<String> columnExcept(String... columns) {
Objects.requireNonNull(columns);
List<String> columnList = Arrays.stream(columns).collect(Collectors.toList());
List<String> allColumnEx = allColumn();
allColumnEx.removeAll(columnList);
return allColumnEx;
}
public static List<String> tableColumnExcept(String... columns) {
Objects.requireNonNull(columns);
List<String> columnList = Arrays.stream(columns).collect(Collectors.toList());
List<String> allColumnEx = allTableColumn();
allColumnEx.removeAll(columnList);
return allColumnEx;
}
public static String allColumnStr() {
return String.join(", ", allColumn());
}
public static String columnExceptStr(String... columns) {
return String.join(", ", columnExcept(columns));
}
public static String allTableColumnStr() {
return String.join(", ", allTableColumn());
}
public static String tableColumnExceptStr(String... columns) {
return String.join(", ", tableColumnExcept(columns));
}
</#if>
@Override
public ${Table} copy() {
${Table} obj = new ${Table}();
<#list models as model>
obj.set${model.upperName}(this.${model.name});
</#list>
return obj;
}
@Override
public JsonObject toJson() {
return JsonObject.mapFrom(this);
}
@Override
public List<${Table}> fromRowSet(RowSet<Row> rowSet){
ArrayList<${Table}> list = new ArrayList<>();
if (rowSet.size() > 0) {
rowSet.forEach( r -> {
${Table} obj = new ${Table}();
<#list models as model>
if (r.getColumnIndex("${model.column}") > -1) obj.set${model.upperName}(r.get${model.simpleType}("${model.column}"));
</#list>
list.add(obj);
} );
}
return list;
}
<#list models as model>
public ${model.simpleType} get${model.upperName}() {
return ${model.name};
}
public ${Table} set${model.upperName}(${model.simpleType} ${model.name}) {
this.${model.name} = ${model.name};
return this;
}
</#list>
@Override
public String toString() {
return "${Table}{" +
<#list models as model>
<#if model_index==0>
"${model.name}= " + ${model.name} +
<#else>
", ${model.name}= " + ${model.name} +
</#if>
</#list>
"}";
}
}
当然,我们可以自接使用第三方的模板,例如 IDEA的 EasyCode 插件的模板。需要做的就是提供它所需要的模型信息。