自定義groovy腳本在IDEA中為數(shù)據(jù)庫生成PO實體類
在文章底部有完整的代碼實現(xiàn)
前言
我們可能會遇到下列這種問題:
公司的一個小項目,被拆分成了API
和后臺管理兩個服務(wù),但是因為二者共用一個數(shù)據(jù)庫,所以存在著大量相同的數(shù)據(jù)庫實體定義.
因此我們不得不在這兩個服務(wù)中分別提供一樣的實體定義,當(dāng)我們的表結(jié)構(gòu)發(fā)生變更時,我們可能會忘記修改某一個項目中對應(yīng)的實體,久而久之,我們會發(fā)現(xiàn),某個項目中的某個實體對象和數(shù)據(jù)庫表定義相差甚遠.
這時候,我們就可以考慮拆出一個common
項目用來存放二者公共的代碼,數(shù)據(jù)實體類作為一個不可變的對象,單獨維護在common
項目中,并保證common
中的實體對象和數(shù)據(jù)庫一一對應(yīng).
那么,我們需要手動的去為每個表建立相應(yīng)的實體類嗎?如果表結(jié)構(gòu)發(fā)生變化,我們還要手動去更改相應(yīng)的實體類嗎?
答案當(dāng)然是不,借助于IDEA
的數(shù)據(jù)庫插件Database
,我們可以通過提供一個groovy
腳本來批量生成數(shù)據(jù)表對應(yīng)的實體類.
這樣,不僅簡化了代碼編寫,同時還能統(tǒng)一實體類的生成邏輯.
關(guān)于
IDEA
中腳本的使用方式參見文檔:Generate Java entity classes for tables and views
IDEA
中本身為提供了一個名為Generate POJOs.groovy
的腳本,該腳本可以完成簡單的代碼生成工作,但是,在實際業(yè)務(wù)中,我們的實體類除了屬性定義之外,往往還有一些其他的數(shù)據(jù)要定義在里面,比如:數(shù)據(jù)庫描述相關(guān)的注解.
自定義腳本實現(xiàn)
這里,我提供了一個簡單的自定義groovy
腳本,通過該腳本,你可以完成包括自定義注解在內(nèi)的代碼生成工作.
基本屬性定義
腳本的最開始,定義了幾個開關(guān)用于控制腳本的行為,建議全部開啟:
// ============= 常量區(qū)域 =================
useLogicDelete = true; /* 啟用邏輯刪除 */
useLombok = true;/*是否啟用lombok*/
useSerializable = true;/*是否啟用實體序列化*/
再定義兩個Map
集合,其中typeMapping
負(fù)責(zé)將數(shù)據(jù)庫類型轉(zhuǎn)換為java
類型,javaAliasMap
則負(fù)責(zé)通過java
類定義的簡短名稱獲取其全限定名稱:
/* 類型轉(zhuǎn)換 ,負(fù)責(zé)將數(shù)據(jù)庫類型轉(zhuǎn)換為java類型,轉(zhuǎn)換邏輯取自 mybatis */
typeMapping = [
(~/(?i)real|decimal|numeric/) : "BigDecimal",
(~/(?i)bigint/) : "Long",
(~/(?i)tinyint/) : "Byte", /* 根據(jù)自己的需求,可以考慮轉(zhuǎn)換為Boolean*/
(~/(?i)int/) : "Integer",
(~/(?i)enum/) : "String", /* 枚舉統(tǒng)一轉(zhuǎn)換為字符串*/
(~/(?i)float|double/) : "Double", /* 根據(jù)自己的需求可以考慮其他類型*/
(~/(?i)datetime|timestamp|date|time/): "Date",
(~/(?i)/) : "String" /*其余的統(tǒng)一轉(zhuǎn)成字符串*/
]
/* java 別名映射 ,負(fù)責(zé)導(dǎo)包*/
javaAliasMap = [
"BigDecimal" : "java.math.BigDecimal",
"Date" : "java.util.Date",
"Getter" : "lombok.Getter",
"Setter" : "lombok.Setter",
"ToString" : "lombok.ToString",
"Serializable": "java.io.Serializable",
"Table": "cn.jpanda.common.page.annotations.Table",
"TableID": "cn.jpanda.common.page.annotations.TableID",
"Column": "cn.jpanda.common.page.annotations.Column",
"Logic": "cn.jpanda.common.page.annotations.Logic",
]
接著定義了三個生成實體類需要使用的屬性,他們負(fù)責(zé)記錄待生成實體類的部分關(guān)鍵信息:
// 代碼生成路徑,在腳本中通過彈出文件框進行選擇,同時會轉(zhuǎn)換為包名稱
String packageName = ""
/* 待引入的java類*/
importClass = [];
/* 待使用的類注解 */
usesAnnotations = [];
屬性比較簡單,上面都有注釋,這些屬性都是在后面運行時被賦值的.
腳本執(zhí)行入口
接下來就是腳本的真正入口:
/* 腳本入口 */
/* 選擇文件目錄 ,并執(zhí)行生成代碼操作 */
FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { dir ->
SELECTION.filter { it instanceof DasTable }.each { generate(it, dir) }
}
該方法會彈出一個文件選擇框,供用戶選擇一個用來存放生成代碼的文件夾.
當(dāng)用戶完成選擇文件夾后,用戶選擇的數(shù)據(jù)庫表數(shù)據(jù)將會依次傳入到generate()
方法中,用來生成相應(yīng)的實體對象.
為數(shù)據(jù)庫表生成對應(yīng)的實體類
通過一個table
屬性生成相應(yīng)實體的邏輯比較簡單,大概有七步:
def generate(table, dir) {
// step1: 獲取包名
packageName = getPackageName(dir)
// step2: 獲取當(dāng)前表名對應(yīng)的類名稱
def className = javaName(table.getName(), true)
// step3: 加載lombok注解
if (useLombok) {
["Getter", "Setter", "ToString"].each() {
convertAliasesAndImportClasses(it)
usesAnnotations << it;
};
}
// step4: 加載序列化
if (useSerializable) {
convertAliasesAndImportClasses("Serializable")
}
// step5: 加載自定義類注解
loadCustomAnnotationToClass(table, className, dir);
// step6: 處理所有數(shù)據(jù)列定義
def properties = processDataColumnDefinition(table);
// step7: 生成代碼
new File(dir as File, className + ".java").withPrintWriter("utf-8") {
out ->
generate(out, className, properties, table)
}
}
1.根據(jù)用戶選擇的目錄生成包名
// step1: 獲取包名
packageName = getPackageName(dir)
static def getPackageName(dir) {
return dir.toString().replaceAll("\\\\", ".").replaceAll("/", ".").replaceAll("^.*src(\\.main\\.java\\.)?", "") + ";"
}
方法的實現(xiàn)并不復(fù)雜,默認(rèn)通過src/main/java
目錄的子目錄層級結(jié)構(gòu)來生成包名稱.
2.將表明轉(zhuǎn)換為java
類型名
// step2: 獲取當(dāng)前表名對應(yīng)的類名稱
def className = javaName(table.getName(), true)
def javaName(str, capitalize) {
def s = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str)
.collect { Case.LOWER.apply(it).capitalize() }
.join("")
.replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
capitalize || s.length() == 1 ? s : Case.LOWER.apply(s[0]) + s[1..-1]
}
轉(zhuǎn)換規(guī)則并不復(fù)雜:下劃線轉(zhuǎn)駝峰,首字母大寫.
3.根據(jù)用戶開關(guān)加載lombok
注解
if (useLombok) {
["Getter", "Setter", "ToString"].each() {
convertAliasesAndImportClasses(it)
usesAnnotations << it;
};
}
convertAliasesAndImportClasses()
方法是一個負(fù)責(zé)完成導(dǎo)包配置的工具方法:
void convertAliasesAndImportClasses(className) {
def entry = javaAliasMap.find { p, t -> p.equalsIgnoreCase(className) };
if (entry == null) {
return;
}
def fullName = entry.value
if (isNotEmpty(fullName)) {
doImportClass(fullName);
}
}
這里只簡單啟用了最常見的三個lombok
注解,可以根據(jù)自己的需求,調(diào)整所需的注解.
4.根據(jù)序列化開關(guān)選擇是否啟用序列化
// step4: 加載序列化
if (useSerializable) {
convertAliasesAndImportClasses("Serializable")
}
5.[擴展點]加載自定義類注解
// step5: 加載自定義類注解
loadCustomAnnotationToClass(table, className, dir);
void loadCustomAnnotationToClass(table, className, dir) {
// 根據(jù)自己的需求實現(xiàn),加載自定義的注解,比如JPA注解
}
loadCustomAnnotationToClass()
方法定義在腳本底部,可以根據(jù)自己的需要來實現(xiàn).
6.預(yù)處理所有數(shù)據(jù)列定義
// step6: 處理所有數(shù)據(jù)列定義
def properties = processDataColumnDefinition(table);
def processDataColumnDefinition(table) {
// 加載當(dāng)前數(shù)據(jù)庫主鍵
def primaryKey = ""
def prKey = DasUtil.getPrimaryKey(table);
if (prKey != null) {
def keyRef = prKey.getColumnsRef();
if (keyRef != null) {
def it = keyRef.iterate();
while (it.hasNext()) {
// 默認(rèn)只處理單主鍵
primaryKey = it.next();
}
primaryKey = javaName(primaryKey, false);
}
}
// 依次處理每一行數(shù)據(jù)列
DasUtil.getColumns(table).reduce([]) { properties, col ->
// 獲取JDBC類型
def spec = Case.LOWER.apply(col.getDataType().getSpecification())
// 將JDBC類型映射為JAVA類型
def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
// 判斷該類型是否需要導(dǎo)包
convertAliasesAndImportClasses(typeStr);
// 將列名稱轉(zhuǎn)換為字段名稱
def javaName = javaName(col.getName(), false);
// 當(dāng)前列是否為主鍵
def isPrimaryKey = javaName.equals(primaryKey);
// 是否為邏輯刪除標(biāo)記
def isLogicDeleteFlag = isLogicDelete(javaName);
def property = [
name : javaName,
colName: col.getName(),
type : typeStr,
comment: col.getComment(),
isKey : isPrimaryKey,
isDel : isLogicDeleteFlag,
annos : []
]
loadCustomAnnotationsToProperty(property);
properties << property;
}
}
processDataColumnDefinition()
方法看起來實現(xiàn)比較復(fù)雜,但是實際上只是負(fù)責(zé)將數(shù)據(jù)列定義解析為下列數(shù)據(jù)結(jié)構(gòu):
def property = [
name : javaName, /* java屬性名*/
colName: col.getName(), /*jdbc列名*/
type : typeStr, /*java類型*/
comment: col.getComment(),/*注釋*/
isKey : isPrimaryKey, /*是否為數(shù)據(jù)表主鍵*/
isDel : isLogicDeleteFlag, /*是否是邏輯刪除標(biāo)志*/
annos : [] /*需要添加的屬性注解*/
]
property
有一個annos
屬性,他的值是通過loadCustomAnnotationsToProperty()
獲取的,這是另一個擴展點,用于加載自定義屬性注解,比如,我的簡單實現(xiàn)是:
void loadCustomAnnotationsToProperty(property) {
// 根據(jù)自己的需求實現(xiàn),加載自定義屬性注解,比如這里,我使用了自定義的注解
if (property.isDel) {
convertAliasesAndImportClasses("Logic");
property.annos << "Logic";
}
if (property.isKey) {
convertAliasesAndImportClasses("TableID");
property.annos << "TableID";
}
convertAliasesAndImportClasses("Column");
property.annos << "Column";
}
7.生成代碼
// step7: 生成代碼
new File(dir as File, className + ".java").withPrintWriter("utf-8") {
out ->
generate(out, className, properties, table)
}
這一步是使用上面的數(shù)據(jù)來生成真正的代碼,實現(xiàn)很長,但是基本上都是用來處理前面獲取到的數(shù)據(jù)的:
// 生成實體類代碼
def generate(out, className, properties, table) {
// step 1: 輸出包名
out.println "package $packageName"
out.println ""
out.println ""
// step2: 導(dǎo)入類
importClass.each() {
out.println "import ${it};"
}
out.println ""
// 補充:生成注釋
generateClassComments(out, table)
// step3: 生成類注解
usesAnnotations.each() {
out.println "@${it}"
}
// step4: 生成類名
out.print "public class $className"
if (useSerializable) {
out.println(" implements Serializable {")
out.println ""
/* 生成序列化ID*/
out.println genSerialID()
} else {
out.println "{"
}
out.println ""
// step5: 處理每個屬性定義
properties.each() {
// step5.1: 輸出注釋
if (isNotEmpty(it.comment)) {
out.println "\t/**"
out.println "\t * ${it.comment}"
out.println "\t */"
}
//step5.2: 輸出注解
it.annos.each() {
out.println "\t@${it}"
}
/*step5.3: 輸入字段內(nèi)容*/
out.println "\tprivate ${it.type} ${it.name};"
}
// step6: 生成getter/setter方法
if (!useLombok) {
// 在未啟用lombok的情況下生成getter/setter方法
properties.each() {
out.println ""
out.println " public ${it.type} get${it.name.capitalize()}() {"
out.println " return ${it.name};"
out.println " }"
out.println ""
out.println " public void set${it.name.capitalize()}(${it.type} ${it.name}) {"
out.println " this.${it.name} = ${it.name};"
out.println " }"
out.println ""
}
}
// step7: 結(jié)尾
out.println "}"
}
generate()
方法中,值得留意的是:
// 補充:生成注釋
generateClassComments(out, table)
這是方法的第三個擴展點,用于加載自定義類注釋,我的簡單實現(xiàn)是:
void generateClassComments(out, table) {
/* 生成類注釋 */
["/**",
" * ",
" * ${table.getComment()}",
" * @author Jpanda [Jpanda@aliyun.com]",
" * @version 1.0",
" * @since " + new SimpleDateFormat("yyyy-MM-dd").format(new Date()),
" */"
].each() {
out.println "${it}"
}
}
上面就是腳本的基本思路了,腳本比較簡單,上面也有詳細的注釋,基本可以滿足使用.
使用腳本
下面是一個簡單的代碼生成示例.
涉及到的表結(jié)構(gòu):
create table user
(
id int auto_increment primary key,
iphone varchar(50) not null comment '手機號',
password varchar(50) not null comment '密碼',
nikename varchar(100) null comment '名字',
group_flag int(1) null comment '組標(biāo)識 0 店長 1 組長 2管理員'
)
comment '用戶';
首先,我們把自定腳本放入IDEA
的腳本執(zhí)行目錄下:
然后通過Database
插件調(diào)用腳本:
選擇目錄:
生成代碼:
package cn.jpanda.common.pojos.test;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import java.io.Serializable;
import cn.jpanda.common.page.annotations.TableID;
import cn.jpanda.common.page.annotations.Column;
/**
*
* 用戶
* @author Jpanda [Jpanda@aliyun.com]
* @version 1.0
* @since 2020-04-22
*/
@Getter
@Setter
@ToString
public class User implements Serializable {
private static final long serialVersionUID = 2598051958746997471L;
@TableID
@Column
private Integer id;
/**
* 手機號
*/
@Column
private String iphone;
/**
* 密碼
*/
@Column
private String password;
/**
* 名字
*/
@Column
private String nikename;
/**
* 組標(biāo)識 0 店長 1 組長 2管理員
*/
@Column
private Integer groupFlag;
}
完整代碼
下面給出完整的腳本代碼,需要根據(jù)自己的需求,調(diào)節(jié)自定義注解相關(guān)的數(shù)據(jù):
import com.intellij.database.model.DasTable
import com.intellij.database.util.Case
import com.intellij.database.util.DasUtil
import java.text.SimpleDateFormat
/*
* Available context bindings:
* SELECTION Iterable<DasObject>
* PROJECT project
* FILES files helper
*/
// ============= 常量區(qū)域 =================
useLogicDelete = true; /* 啟用邏輯刪除 */
useLombok = true;/*是否啟用lombok*/
useSerializable = true;/*是否啟用實體序列化*/
/* 類型轉(zhuǎn)換 ,負(fù)責(zé)將數(shù)據(jù)庫類型轉(zhuǎn)換為java類型,轉(zhuǎn)換邏輯取自 mybatis */
typeMapping = [
(~/(?i)real|decimal|numeric/) : "BigDecimal",
(~/(?i)bigint/) : "Long",
(~/(?i)tinyint/) : "Byte", /* 根據(jù)自己的需求,可以考慮轉(zhuǎn)換為Boolean*/
(~/(?i)int/) : "Integer",
(~/(?i)enum/) : "String", /* 枚舉統(tǒng)一轉(zhuǎn)換為字符串*/
(~/(?i)float|double/) : "Double", /* 根據(jù)自己的需求可以考慮其他類型*/
(~/(?i)datetime|timestamp|date|time/): "Date",
(~/(?i)/) : "String" /*其余的統(tǒng)一轉(zhuǎn)成字符串*/
]
/* java 別名映射 ,負(fù)責(zé)導(dǎo)包*/
javaAliasMap = [
"BigDecimal" : "java.math.BigDecimal",
"Date" : "java.util.Date",
"Getter" : "lombok.Getter",
"Setter" : "lombok.Setter",
"ToString" : "lombok.ToString",
"Serializable": "java.io.Serializable",
"Table" : "cn.jpanda.common.page.annotations.Table",
"TableID" : "cn.jpanda.common.page.annotations.TableID",
"Column" : "cn.jpanda.common.page.annotations.Column",
"Logic" : "cn.jpanda.common.page.annotations.Logic",
]
/* ========= 運行時屬性定義 ============*/
// 代碼生成路徑,在腳本中通過彈出文件框進行選擇
String packageName = ""
/* 待引入的java類*/
importClass = [];
/* 待使用注解 */
usesAnnotations = [];
/* 腳本入口 */
/* 選擇文件目錄 ,并執(zhí)行生成代碼操作 */
FILES.chooseDirectoryAndSave("Choose directory", "Choose where to store generated files") { dir ->
SELECTION.filter { it instanceof DasTable }.each { generate(it, dir) }
}
def generate(table, dir) {
// step1: 獲取包名
packageName = getPackageName(dir)
// step2: 獲取當(dāng)前表名對應(yīng)的類名稱
def className = javaName(table.getName(), true)
// step3: 加載lombok注解
if (useLombok) {
["Getter", "Setter", "ToString"].each() {
convertAliasesAndImportClasses(it)
usesAnnotations << it;
};
}
// step4: 加載序列化
if (useSerializable) {
convertAliasesAndImportClasses("Serializable")
}
// step5: 加載自定義類注解
loadCustomAnnotationToClass(table, className, dir);
// step6: 處理所有數(shù)據(jù)列定義
def properties = processDataColumnDefinition(table);
// step7: 生成代碼
new File(dir as File, className + ".java").withPrintWriter("utf-8") {
out ->
generate(out, className, properties, table)
}
}
// 生成實體類代碼
def generate(out, className, properties, table) {
// step 1: 輸出包名
out.println "package $packageName"
out.println ""
out.println ""
// step2: 導(dǎo)入類
importClass.each() {
out.println "import ${it};"
}
out.println ""
// 補充:生成注釋
generateClassComments(out, table)
// step3: 生成類注解
usesAnnotations.each() {
out.println "@${it}"
}
// step4: 生成類名
out.print "public class $className"
if (useSerializable) {
out.println(" implements Serializable {")
out.println ""
/* 生成序列化ID*/
out.println genSerialID()
} else {
out.println "{"
}
out.println ""
// step5: 處理每個屬性定義
properties.each() {
// step5.1: 輸出注釋
if (isNotEmpty(it.comment)) {
out.println "\t/**"
out.println "\t * ${it.comment}"
out.println "\t */"
}
//step5.2: 輸出注解
it.annos.each() {
out.println "\t@${it}"
}
/*step5.3: 輸入字段內(nèi)容*/
out.println "\tprivate ${it.type} ${it.name};"
}
// step6: 生成getter/setter方法
if (!useLombok) {
// 在未啟用lombok的情況下生成getter/setter方法
properties.each() {
out.println ""
out.println " public ${it.type} get${it.name.capitalize()}() {"
out.println " return ${it.name};"
out.println " }"
out.println ""
out.println " public void set${it.name.capitalize()}(${it.type} ${it.name}) {"
out.println " this.${it.name} = ${it.name};"
out.println " }"
out.println ""
}
}
// step7: 結(jié)尾
out.println "}"
}
def processDataColumnDefinition(table) {
// 加載當(dāng)前數(shù)據(jù)庫主鍵
def primaryKey = ""
def prKey = DasUtil.getPrimaryKey(table);
if (prKey != null) {
def keyRef = prKey.getColumnsRef();
if (keyRef != null) {
def it = keyRef.iterate();
while (it.hasNext()) {
// 默認(rèn)只處理單主鍵
primaryKey = it.next();
}
primaryKey = javaName(primaryKey, false);
}
}
// 依次處理每一行數(shù)據(jù)列
DasUtil.getColumns(table).reduce([]) { properties, col ->
// 獲取JDBC類型
def spec = Case.LOWER.apply(col.getDataType().getSpecification())
// 將JDBC類型映射為JAVA類型
def typeStr = typeMapping.find { p, t -> p.matcher(spec).find() }.value
// 判斷該類型是否需要導(dǎo)包
convertAliasesAndImportClasses(typeStr);
// 將列名稱轉(zhuǎn)換為字段名稱
def javaName = javaName(col.getName(), false);
// 當(dāng)前列是否為主鍵
def isPrimaryKey = javaName.equals(primaryKey);
// 是否為邏輯刪除標(biāo)記
def isLogicDeleteFlag = isLogicDelete(javaName);
def property = [
name : javaName, /* java屬性名*/
colName: col.getName(), /*jdbc列名*/
type : typeStr, /*java類型*/
comment: col.getComment(),/*注釋*/
isKey : isPrimaryKey, /*是否為數(shù)據(jù)表主鍵*/
isDel : isLogicDeleteFlag, /*是否是邏輯刪除標(biāo)志*/
annos : [] /*需要添加的屬性注解*/
]
loadCustomAnnotationsToProperty(property);
properties << property;
}
}
def javaName(str, capitalize) {
def s = com.intellij.psi.codeStyle.NameUtil.splitNameIntoWords(str)
.collect { Case.LOWER.apply(it).capitalize() }
.join("")
.replaceAll(/[^\p{javaJavaIdentifierPart}[_]]/, "_")
capitalize || s.length() == 1 ? s : Case.LOWER.apply(s[0]) + s[1..-1]
}
static def isNotEmpty(content) {
return content != null && content.toString().trim().length() > 0
}
static String genSerialID() {
return "\tprivate static final long serialVersionUID = " + Math.abs(new Random().nextLong()) + "L;"
}
// 生成包名
static def getPackageName(dir) {
return dir.toString().replaceAll("\\\\", ".").replaceAll("/", ".").replaceAll("^.*src(\\.main\\.java\\.)?", "") + ";"
}
/**
* 判斷一個java屬性是否為邏輯刪除
* [根據(jù)自己的需求重構(gòu)該測試]
* @param property 屬性名稱
* @return
*/
boolean isLogicDelete(property) {
return useLogicDelete && property.equalsIgnoreCase("DeleteFlag") || property.equalsIgnoreCase("DelFlag")
}
/**
* 導(dǎo)入lombok的類
*/
void importLombokPackage() {
if (useLombok) {
["lombok.Getter", "lombok.Setter", "lombok.ToString"].each() {
doImportClass(it);
};
}
}
/**
* 導(dǎo)入一個包名稱
* @param className
*/
void doImportClass(className) {
if (importClass.count { it -> it.equalsIgnoreCase(className) } == 0) {
importClass << className;
}
}
void convertAliasesAndImportClasses(className) {
def entry = javaAliasMap.find { p, t -> p.equalsIgnoreCase(className) };
if (entry == null) {
return;
}
def fullName = entry.value
if (isNotEmpty(fullName)) {
doImportClass(fullName);
}
}
void loadCustomAnnotationToClass(table, className, dir) {
// 根據(jù)自己的需求實現(xiàn),加載自定義的注解,比如JPA注解
}
void loadCustomAnnotationsToProperty(property) {
// 根據(jù)自己的需求實現(xiàn),加載自定義屬性注解,比如這里,我使用了自定義的注解
if (property.isDel) {
convertAliasesAndImportClasses("Logic");
property.annos << "Logic";
}
if (property.isKey) {
convertAliasesAndImportClasses("TableID");
property.annos << "TableID";
}
convertAliasesAndImportClasses("Column");
property.annos << "Column";
}
void generateClassComments(out, table) {
/* 生成類注釋 */
["/**",
" * ",
" * ${table.getComment()}",
" * @author Jpanda [Jpanda@aliyun.com]",
" * @version 1.0",
" * @since " + new SimpleDateFormat("yyyy-MM-dd").format(new Date()),
" */"
].each() {
out.println "${it}"
}
}