程式碼生成器——實現生成pojo,sql,mapper介面
程式碼生成器(記錄一次興趣程式碼,多多指教。轉載請標明作者)
在我們開始實現程式碼生成器之前我們先來對程式碼生成器有一個簡單的瞭解。
1.什麼是程式碼生成器?
故名思義,也就是生成程式碼的一個程式。那它是一個什麼樣的機制?怎麼體現這種機制?
比如:
我們學習的 JSP 技術,瀏覽器並不能直接識別 JSP 檔案。當用戶通過瀏覽器訪問某個 JSP 頁
面的時候,其中的互動分為分為幾個步驟:
使用者通過瀏覽器訪問 JSP 頁面
Tomcat 找到對應 JSP 資源,將 JSP 資源轉化(編譯,翻譯)成為 HTML 資料
Tomcat 將 HTML 資料進行返回
在此,我們就可以這樣理解
我們所編寫 JSP ===》 HTML 的模板。
JSP 頁面的標籤 ===》模板中的佔位符。
Tomcat 根據 JSP 模板,生成 HTML 頁面。 資料庫的資料不同,則生成的 HTML 頁面也不同。
最後我們得出一個結論:
Tomcat 可以根據 JSP 模板和資料生成不同的 HTML 頁面。
所以 Tomcat 對 JSP 的處理過程我們就可以認為是一個程式碼生成器的機制,其實也就是程式碼生成器的原理。
2.我們為什麼要編寫程式碼生成器?
我們先不說它在我們軟體之中處處可見,單單就作為一個程式設計師的輔助工具而言,也是極大的提高了開發效率。
比如我們的實體類,一個專案中表的數量不會少於二三十個吧,倘若每一個實體類,都手動編寫,這種重複的程式碼編寫,嗯,作為程式設計師應該都懂得。
所以,程式碼生成器節省人力成本,提高了開發效率。
3.開始編寫程式碼生成器。
前面鋪墊了那麼多,乾貨終於要來了。
3.1 編寫工具:idea
3.2 依賴模板引擎: FreeMarker(FreeMarker 所需要的功能簡介:)
maven依賴freemarker jar
<!-- https://mvnrepository.com/artifact/freemarker/freemarker --> <dependency> <groupId>freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.8</version> </dependency>
3.3 程式碼體系組成:
- 模板:生成檔案的模板檔案。
- 資料:生成檔案所需要的關鍵資料。
- 合成機制:使用資料置換模板中的佔位符,生成新的檔案的機制。
3.4 所有實現程式碼
---------存放資料的物件
package cn.itrip.vo; /** * 列物件 */ public class ColumnVo { private String name;//資料庫中的列名 private String fieldName;//對應的Java屬性名 private String dbType;//資料中的記錄型別 private String javaType;//對應的Java屬性型別 private String comment;//資料庫中的註釋 private String upperCaseColumnName;//轉成帕斯卡後的屬性名 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getFieldName() { return fieldName; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public String getDbType() { return dbType; } public void setDbType(String dbType) { this.dbType = dbType; } public String getJavaType() { return javaType; } public void setJavaType(String javaType) { this.javaType = javaType; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public String getUpperCaseColumnName() { return upperCaseColumnName; } public void setUpperCaseColumnName(String upperCaseColumnName) { this.upperCaseColumnName = upperCaseColumnName; } }
package cn.itrip.vo; import java.util.ArrayList; import java.util.List; /** * 表物件 */ public class TableVo { private String className;//帕斯卡風格的類名 private String tableName;//表名 private String comment;//註釋 private String lowerClassName;//駱駝命名法 private List<ColumnVo> columns=new ArrayList<ColumnVo>();//列物件集合 public String getLowerClassName() { return lowerClassName; } public void setLowerClassName(String lowerClassName) { this.lowerClassName = lowerClassName; } public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getTableName() { return tableName; } public void setTableName(String tableName) { this.tableName = tableName; } public String getComment() { return comment; } public void setComment(String comment) { this.comment = comment; } public List<ColumnVo> getColumns() { return columns; } public void setColumns(List<ColumnVo> columns) { this.columns = columns; } }
--------------幫助類
package cn.itrip.util; /** * 命名轉換工具類 */ public class JavaNameUtil { 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); } /** * 轉換資料型別 * @param dbTypeName * @return */ public static String dbtype2JavaType(String dbTypeName){ String javaType=null; switch (dbTypeName){ 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 static void main(String[] arg){ String testName =toCamel("user_liu_jh"); System.out.println(testName); String testName2=toPascal("user_liu_jh"); System.out.println(testName2); } }
package cn.itrip.util; import java.sql.*; import java.util.ArrayList; import java.util.List; /** * 負責連線資料庫、獲取表的元資訊、列的元資訊 */ public class MetadataUtil { private static Connection conn;//連線物件 /*2. DatabaseMetaData介面常用的方法:獲取資料庫元資料 (1) ResultSet getTables(String catalog,String schemaPattern,String tableNamePattern,String[] types); //獲取表資訊 (2) ResultSet getPrimaryKeys(String catalog,String schema,String table); //獲取表主鍵資訊 (3) ResultSet getIndexInfo(String catalog,String schema,String table,boolean unique,boolean approximate); //獲取表索引資訊 (4) ResultSet getColumns(String catalog,String schemaPattern,String tableNamePattern,String columnNamePattern); //獲取表列資訊*/ 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()) {//isClosed:判斷連線是否關閉 conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/XXX?useUnicode=true&characterEncoding=utf-8" , "XXX", "XXX");//連線資料庫 XXX表示你自己的資料庫名稱 、使用者名稱、密碼 meta = conn.getMetaData();//獲取元資料 } } catch (SQLException e) { e.printStackTrace(); } } /** * 獲取表的註釋 * @param * @return */ public static String getCommentByTableName(String tableName) throws SQLException { openConnection();//開啟連線 Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SHOW CREATE TABLE " + tableName); String comment = null; if (rs != null && rs.next()) { comment=rs.getString(2); //判斷字元,只獲取註解部分 int index = comment.lastIndexOf("="); comment = comment.substring(index+1); } rs.close(); stmt.close(); conn.close(); return comment; } /** * 獲取所有的表名 * @return */ public static String[] getTableNames(){ openConnection(); ResultSet rs=null; List<String> nameList = new ArrayList<>(); 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(); } return (String[])nameList.toArray(new String[]{}); } /** * 獲取某個表中所有的列資訊 * @param tableName * @return * @throws Exception */ public static List<String[]> getTableColumnsInfo(String tableName)throws Exception{ openConnection(); ResultSet rs= meta.getColumns(null, "%",tableName,"%"); List<String[]> columnInfoList =new ArrayList<>(); while (rs.next()){ String[] colInfo = new String[3]; colInfo[0]=rs.getString("COLUMN_NAME");//COLUMN_NAME 列名 colInfo[1]=rs.getString("REMARKS");//REMARKS 獲取列註釋 colInfo[2]=rs.getString("TYPE_NAME");//TYPE_NAME 列型別 columnInfoList.add(colInfo); } return columnInfoList; } }
---------合成機制
package cn.itrip.generator; import cn.itrip.util.JavaNameUtil; import cn.itrip.util.MetadataUtil; import cn.itrip.vo.ColumnVo; import cn.itrip.vo.TableVo; import freemarker.template.Configuration; import freemarker.template.Template; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 抽象生成器 */ public abstract class AbstractGenerator { protected Configuration cfg;//Freemarker配置物件 protected Map valueMap;//填充到模板的資料 protected Template template;//模板物件 protected String savePath;//儲存生成檔案的路徑 protected List<TableVo> tableList;//表資訊 public AbstractGenerator() throws Exception { cfg = new Configuration(); cfg.setClassForTemplateLoading(this.getClass(), "/templates/"); //獲取模板檔案位置 valueMap = new HashMap(); init();//初始化資料 } /** * 獲取所有的表資訊列資訊 * @throws Exception */ public void init()throws Exception{ tableList=new ArrayList<>(); String[] tableNameArr =MetadataUtil.getTableNames();//獲取表名稱 for(String tName:tableNameArr){ String tComment = MetadataUtil.getCommentByTableName(tName);//獲取註釋 TableVo table = new TableVo();//建立表資訊物件 table.setClassName(JavaNameUtil.toPascal(tName)); table.setComment(tComment); table.setTableName(tName); List<String[]> colInfoList =MetadataUtil.getTableColumnsInfo(tName); for (String[] colInfo:colInfoList){ String cName =colInfo[0]; String cComment =colInfo[1]; String cType =colInfo[2]; ColumnVo column = new ColumnVo(); column.setComment(cComment); column.setName(cName); column.setFieldName(JavaNameUtil.toCamel(cName)); column.setDbType(cType); column.setJavaType(JavaNameUtil.dbtype2JavaType(cType)); column.setUpperCaseColumnName(JavaNameUtil.toPascal(cName)); table.getColumns().add(column); } tableList.add(table); System.out.println(table); } System.out.println("構建元資料成功!"); } //抽象生成程式碼檔案,各個子類實現 public abstract void generateCode() throws Exception; public void setSavePath(String savePath) { this.savePath = savePath; } }
package cn.itrip.generator; import cn.itrip.vo.TableVo; import freemarker.template.TemplateException; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; /** * 實體類生成器 */ public class EntityGenerator extends AbstractGenerator{ public EntityGenerator(String ftl) throws Exception {
try{
this.savePath="D:\\object\\itripljh\\itripbeans\\src\\main\\java\\cn\\itrip\\pojo";
//載入freemarker模板檔案
super.template = cfg.getTemplate(ftl);
}catch (IOException e){
e.printStackTrace();
}
} public void generateCode() throws Exception { System.out.println("---------------開始生成程式碼"); valueMap.put("package","cn.itrip");//自定義包的位置 OutputStreamWriter writer=null; for(TableVo table:tableList) { valueMap.put("table",table); try{ //建立一個寫入器 writer = new FileWriter(savePath+"/"+table.getClassName()+".java");//根據不同的檔案自行修改 //合成數據和模板,寫入到檔案 this.template.process(valueMap,writer); writer.flush(); }catch (TemplateException e){ e.printStackTrace(); }catch (IOException e){ e.printStackTrace(); }finally { writer.close(); } } System.out.println("生成程式碼成功!"); } }
----------------模板檔案(pojo.ftl)
package ${package}.pojo; import java.io.Serializable; import java.util.Date; /** * ${table.comment} */ public class ${table.className} implements Serializable{ //生成私有屬性 <#list table.columns as col> //${col.comment} private ${col.javaType} ${col.fieldName}; </#list> //生成getter、setter方法 <#list table.columns as col> public void set${col.upperCaseColumnName} (${col.javaType} ${col.fieldName}){ this.${col.fieldName} =${col.fieldName}; } public ${col.javaType} get${col.upperCaseColumnName} (){ return this.${col.fieldName}; } </#list> }
---------------入口
package cn.itrip; import cn.itrip.generator.EntityGenerator;public class App { public static void main(String[] args)throws Exception{ System.out.println("開始啟動---------------"); String path=App.class.getClassLoader().getResource("").getPath(); System.out.println(path); EntityGenerator generator =new EntityGenerator("pojo.ftl"); try { generator.setSavePath("D:\\object\\itripljh\\itripbeans\\src\\main\\java\\pojo2"); generator.generateCode(); }catch (Exception e){ e.printStackTrace(); }
}
-----------最後執行的結果如下
完成。