最近做项目写了一个代码生成器,现在就分享出来给大家,顺便说一下代码生成器是什么东西!
1.什么是代码生成器
代码生成器顾名思义就是一个生成代码的软件。为了节省成本,在日常的企业开发中,代码生成器使用比较普遍。
2.代码生成器的原理
代码生成器的原理就是: 根据模板+ 数据生成不同文件 。比如以下的两段代码。
1.ItripHotelOrderMapper 文件
2.ItripHotelOrderMapper 文件
在上述 ItripHotelOrderMapper 和 ItripHotelOrderMapper 文件中,我们注意到,两个文件的代码很相似。除了高亮部分不一致以外,其它的代码均一致。因此,我们可以设想,我们能不能编写模板生成这样的 Mapper 类。高亮部分使用占位符来进行代替,具体内容根据具体数据进行置换
3. 代码生成器三要素
了解了代码生成器的概念和原理,接下来我开始着手编写属于自己的代码生成器。我将代码生成器的分为三部分:
➢ 模板:生成文件的模板文件。
➢ 数据:生成文件所需要的关键数据。
➢ 合成机制:使用数据置换模板中的占位符,生成新的文件的机制。
话不多说,给大家看下代码
表的vo类:
/**
* 表Vo
*/
public class TableVo {
private String className;//帕斯卡风格的类名
private String camelName;//骆驼风格的类名
private String tableName;//下划线风格的表名
private List<ColumnVo> columns = new ArrayList<ColumnVo>();//列集合
}
列的vo类:
/**
* 列Vo类
*/
public class ColumnVo {
private String dbName;//数据库列名
private String javaName;//java属性名
private String dbType;//数据库列类型
private String javaType;//java属性类型
private String comm;//注释
private String upperCaseColumnName;//转成帕斯卡之后的名称
}
转换Java格式名称的工具类:
/**
* 转换java格式名称的工具类
*/
public class JavaNameUtil {
/**
* 下划线风格的命名转换为java骆驼命名法或帕斯卡命名法的名称
* @param underscoreName
* @param isPascal true:帕斯卡,false:骆驼
* @return
*/
public static String translate(String underscoreName, boolean isPascal) {
StringBuilder result = new StringBuilder();
if(underscoreName != null && underscoreName.length() > 0) {
boolean flag = false;
//首字母特殊处理,转换成帕斯卡命名法或骆驼命名法的名称
char firstChar = underscoreName.charAt(0);
if(isPascal) {
result.append(Character.toUpperCase(firstChar));
} else {
result.append(firstChar);
}
//第二个之后的所有字符
for(int i = 1; i < underscoreName.length(); i++) {
char ch = underscoreName.charAt(i);
//如果这个字符为下划线,下个字符需要大写处理
if('_' == ch) {
flag = true;
} else {
if(flag) {
result.append(Character.toUpperCase(ch));
flag = false;
} else {
result.append(ch);
}
}
}
}
return result.toString();
}
/**
* 将下划线风格的命名直接转换为骆驼命名法的名称
* @param str
* @return
*/
public static String toCamel(String str) {
return translate(str, false);
}
/**
* 将下划线风格的命名直接转换为帕斯卡命名法的名称
* @param str
* @return
*/
public static String toPascal(String str) {
return translate(str, true);
}
/**
* 将数据库类型转换成java类型
* @param dbType
* @return
*/
public static String dbTypeToJavaType(String dbType) {
String javaType = null;
switch (dbType) {
case "VARCHAR": javaType = "String";break;
case "BIGINT": javaType = "Long";break;
case "INT": javaType = "Integer";break;
case "DATETIME":javaType = "Date";break;
default: javaType = "String";break;
}
return javaType;
}
负责连接数据库、获取表、列的元信息:
/**
* 负责连接数据库、获取表、列的元信息
*/
public class MetadataUtil {
private static Connection conn;//连接
private static DatabaseMetaData meta;//数据库员信息
//静态代码加载数据库驱动
static {
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
System.out.println("数据库连接失败");
}
}
//打开连接,获取数据库元信息
public static void openConnection() {
try {
if(conn == null || conn.isClosed()) {
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/数据库名?useUnicode=true",
"账号",
"密码");
meta = conn.getMetaData();//获取数据库的元数据对象
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//关闭连接
public static void closeConnection() {
try {
if(conn != null || !conn.isClosed()) {
conn.isClosed();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
//获取所有表名称
public static String[] getTableNames() {
openConnection();
ResultSet rs = null;
List<String> nameList = new ArrayList<String>();
try {
rs = meta.getTables(null, null, null, new String[] {"TABLE"});
while (rs.next()) {
String tName = rs.getString("TABLE_NAME");
nameList.add(tName);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
closeConnection();
}
return (String [])nameList.toArray(new String [] {});
}
//获取某个表的列信息
public static List<String []> getTableColumnsInfo(String tableName) throws SQLException {
openConnection();
ResultSet rs = meta.getColumns(null, "%", tableName, "%");
List<String[]> columnInfoList = new ArrayList<String[]>();
while (rs.next()) {
String[] colInfo = new String[3];
colInfo[0] = rs.getString("COLUMN_NAME");
colInfo[1] = rs.getString("REMARKS");
colInfo[2] = rs.getString("TYPE_NAME");
columnInfoList.add(colInfo);
}
closeConnection();
return columnInfoList;
}
代码生成器类:
**
* 代码生成器
*/
public class CodeGenerator {
protected Configuration cfg;//Freemarker配置对象
protected Map valueMap;//要填充到模板的数据
protected Template template;//ftl模板对象
protected String savePath;//保存代码的路径
protected List<TableVo> tableList;//表信息对象集合
protected String fileNameSuffix;//生成的文件名后缀
public CodeGenerator(String ftl) throws Exception {
cfg = new Configuration();//Freemarker配置对象
cfg.setClassForTemplateLoading(this.getClass(), "/templates");//设置加载ftl模板的路径
template = cfg.getTemplate(ftl);//加载模板文件
valueMap = new HashMap();
parseMetadata();
}
//获取所有的表信息、列信息,并且转换为对象
public void parseMetadata() throws Exception {
tableList = new ArrayList<TableVo>();
//获取所有表名
String [] tableNameArr = MetadataUtil.getTableNames();
for (String tName : tableNameArr) {
TableVo table= new TableVo();//表对象
//下划线转帕斯卡命名
table.setClassName(JavaNameUtil.toPascal(tName));
table.setTableName(tName);
table.setCamelName(JavaNameUtil.toCamel(tName));
//调用工具类,获取列信息
List<String []> colInfoList = MetadataUtil.getTableColumnsInfo(tName);
for(String[] colInfo : colInfoList) {
String colName = colInfo[0];//列名
String comment = colInfo[1];//列注释
String colType = colInfo[2];//列类型
ColumnVo column = new ColumnVo();
column.setDbName(colName);
column.setDbType(colType);
column.setComm(comment);
column.setJavaName(JavaNameUtil.toCamel(colName));
column.setJavaType(JavaNameUtil.dbTypeToJavaType(colType));
//列名转帕斯卡命名法,用户生成get和set方法命名
column.setUpperCaseColumnName(JavaNameUtil.toPascal(colName));
//将列信息加入表对象的列信息列表中
table.getColumns().add(column);
}
//加入表集合
tableList.add(table);
}
MetadataUtil.closeConnection();//关闭数据库连接
System.out.println("构建元数据成功!\n\n");
}
//抽象生成代码的方法,各子类实现
public void generateCode() throws Exception {
System.out.println("--------------开始生成" + template.getName() + "代码生成器开始生成代码");
OutputStreamWriter writer = null;
for(TableVo table : tableList) {
valueMap.put("table", table);
try {
//生成的每个代码文件,拼接文件名,创建文件写入器
writer = new FileWriter(savePath + "/" + table.getClassName() + fileNameSuffix);
//Freemarker合成数据和模板,输出到代码文件
this.template.process(valueMap, writer);
//清空写入器缓冲
writer.flush();
} catch (TemplateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
System.out.println("根据" + template.getName() + "模板生成代码成功");
}
代码生成器入口类main方法:
public class App {
public static void main(String [] args) throws Exception {
String myProjectPkg = "cn.itrip";
//实体类生成器
CodeGenerator pojoGenerator = new CodeGenerator("pojo.ftl");//设置所用的模板
pojoGenerator.setSavePath("url");//设置生成代码保存的位置
pojoGenerator.setFileNameSuffix(".java");//设置文件后缀名
pojoGenerator.setPackage(myProjectPkg);//项目包名
//mapper接口生成器
CodeGenerator mapperGenerator = new CodeGenerator("mapper.ftl");//设置所用的模板
mapperGenerator.setSavePath("url");//设置生成代码保存的位置
mapperGenerator.setFileNameSuffix("Mapper.java");//设置文件后缀名
mapperGenerator.setPackage(myProjectPkg);//项目包名
//mapper映射文件生成器
CodeGenerator sqlGenerator = new CodeGenerator("sql.ftl");//设置所用的模板
sqlGenerator.setSavePath("url");//设置生成代码保存的位置
sqlGenerator.setFileNameSuffix("Mapper.xml");//设置文件后缀名
sqlGenerator.setPackage(myProjectPkg);//项目包名
try {
//调用代码生成器
pojoGenerator.generateCode();
mapperGenerator.generateCode();
sqlGenerator.generateCode();
} catch (Exception e) {
e.printStackTrace();
}
}
最后在写3个模板文件(pojo、mapper、sql.ftl)然后运行一下App类就可以自动生成代码了