解放你的雙手-程式碼生成器
阿新 • • 發佈:2018-11-19
想想一個數據庫這麼多表自己寫pojo類看看就沒有寫的慾望,沒辦法這是底層的玩意不得不寫,有沒有辦法搞個程式讓他自動生成呢?這樣的話pojo類和Mybatis中的通用方法都不用自己寫,一鍵生成想想都帶勁。仔細想想其實很多的東西都是相似的,接下來教大家如何寫一個程式碼生成器。
第一步,我們需要用一個實體類用於封裝表的資訊
import java.util.ArrayList; import java.util.List; /** * 表資訊,用於封裝表的元 資訊 */ public class TableVo { private String className; //帕斯卡風格命名 private String camelName; //駱駝風格命名(用於作為方法的引數) private String tableName; //下劃線風格的表名 private String comment; //註釋 private List<ColumnVo> columns=new ArrayList<ColumnVo>(); //包含的列的物件集合 public String getClassName() { return className; } public void setClassName(String className) { this.className = className; } public String getCamelName() { return camelName; } public void setCamelName(String camelName) { this.camelName = camelName; } 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; } }
第二步、建立一個實體類封裝列的資訊
/** * 列物件,封裝元資訊 */ public class ColumnVo { private String dbName; //在資料庫中的列名 private String fieldName; //java屬性名 private String javaType; //java型別 private String dbType; //資料庫中的型別 private String comment; //註釋 private String upperCaseColumnName; //轉換成為帕斯卡之後的名稱,用於getter ,setteer public String getDbName() { return dbName; } public void setDbName(String dbName) { this.dbName = dbName; } public String getFieldName() { return fieldName; } public void setFieldName(String fieldName) { this.fieldName = fieldName; } public String getJavaType() { return javaType; } public void setJavaType(String javaType) { this.javaType = javaType; } public String getDbType() { return dbType; } public void setDbType(String dbType) { this.dbType = dbType; } 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; } }
第三步、由於資料庫的命名和JAVA標準命名可以不會一致,我們需要轉換命名並且需要考慮到資料型別的關係轉換資料的型別
比如Varchar轉換為String 型別
/** * 轉換命名和資料庫型別 */ public class JavaNameUtil { /** * 下劃線風格的命名轉換為java駱駝命名法或者帕斯卡命名法 * * @param underscoreName * @paramisPascal是否首字母大寫(帕斯卡),ture為需要轉換帕斯卡,false只轉換為駱駝命名法 * @return駱駝或者帕斯卡命名法的 字串 */ public static String translate(String underscoreName, Boolean isPascal) { StringBuilder result = new StringBuilder(); if (underscoreName != null || underscoreName.length() > 0) { boolean flag = false; //首字母特殊處理,是否為帕斯卡 char fristChar = underscoreName.charAt(0); if (isPascal) { //判斷首字母是否需要處理成大寫 result.append(Character.toUpperCase(fristChar)); } else { result.append(fristChar); } //第二個(包含第二個)之後所有的字元 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(); } //封裝後的,更加容易使用,轉換為帕斯卡命名法 public static String toPascal(String str) { return translate(str, true); } //封裝後的,更加容易使用,轉換為駱駝命名法 public static String toCamel(String str) { return translate(str, false); } //資料庫型別名到java型別名的轉換 public static String dbType2JavaType(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; } }
第四步、獲取我們所需要的資料的來源
就是資料的元資訊,我們可以通過 DatabaseMetaData 這個sql包裡面提供的類來直接獲取,節省了發明這個輪子的步驟
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
/**
* 負責連資料庫、獲取表的元資訊,列的元資訊
*/
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/itripdb?useUnicode=true&characterEncoding=utf-8",
"root",
"123");
meta = conn.getMetaData();//獲取資料庫的元資料物件
}
}catch (SQLException e){
e.printStackTrace();
}
}
//關閉連線
public static void closeConnection(){
try {
if(conn != null && !conn.isClosed()){
conn.close();
}
}catch (SQLException e){
e.printStackTrace();
}
}
//獲取所有表名稱
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[]{});
}
public static List<String[]> getTableColumnsInfo(String tableName) throws SQLException{
openConnection();
//元資訊物件,獲取所有該表的列資訊
ResultSet rs = meta.getColumns(null,
"%",
tableName,
"%");
List<String[]> columnInfoList = new ArrayList<>();
//從rs中獲取列資訊
while (rs.next()){
//針對每個列,建立一個長度為3的陣列,封裝3方面資訊:列名、註釋、資料庫型別
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);
}
return columnInfoList;
}
public static Connection getConn() {
return conn;
}
public static void setConn(Connection conn) {
MetadataUtil.conn = conn;
}
public static DatabaseMetaData getMeta() {
return meta;
}
public static void setMeta(DatabaseMetaData meta) {
MetadataUtil.meta = meta;
}
}
第五步、建立一個程式碼生成器,提供一個公共的模板
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 程式碼生成器
*/
public class CodeGenerator {
protected Configuration cfg; //Freemarker配置物件
protected Map valueMap; //要填充到模板的資料,用來和ftl模板合成
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); //載入ftl模板檔案
valueMap=new HashMap(); //初始化
parseMetadata();
}
//獲取所有表的資訊,列資訊,並且轉換為物件
public void parseMetadata()throws Exception{
tableList=new ArrayList<>();
//獲取所有表名
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 cName=colInfo[0]; //列名
String cComment=colInfo[1]; //列註釋
String cType=colInfo[2]; //列的資料庫型別
ColumnVo column=new ColumnVo();
column.setComment(cComment);
column.setDbName(cName);
//列名轉java屬性名
column.setFieldName(JavaNameUtil.toCamel(cName));
column.setDbType(cType);
//資料庫型別轉java型別
column.setJavaType(JavaNameUtil.dbType2JavaType(cType));
//列名轉帕斯卡,用於getter和setter
column.setUpperCaseColumnName(JavaNameUtil.toPascal(cName));
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){//迴圈每個表物件,開始生成
//模板檔案ftl中用到的${table.className}表物件就是從這裡注入的
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()+"模板生成程式碼成功!\n\n");
}
public void setSavePath(String savePath) {
this.savePath = savePath;
}
public void setFileNameSuffix(String fileNameSuffix) {
this.fileNameSuffix = fileNameSuffix;
}
public void setPackage(String packg){
this.valueMap.put("package",packg);
}
}
第六步、建立一個公共的ftl檔案模板(以ftl為字尾的檔案,裡面的欄位對應TableVo和ColumnVo這兩個類)
pojo類的模板
package ${package}.pojo;
import java.io.Serializable;
import java.util.Date;
/**
*
*/
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>
}
通用sql的模板
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package}.dao.${table.className}Mapper">
<!-- 根據id查詢單個物件 -->
<select id="get${table.className}ById" resultType="${package}.pojo.${table.className}">
select
<trim suffixOverrides=",">
<#list table.columns as col>
${col.dbName} as ${col.fieldName},
</#list>
</trim>
form ${table.tableName}
<trim prefix="where" prefixOverrides="and | or">
<if test="id != null">
and id ${r"#{"}id}
</if>
</trim>
</select>
<!-- 根據多個條件查詢符合條件的資料 -->
<select id="find${table.className}ListByMap" resultType="${package}.pojo.${table.className}">
select
<trim suffixOverrides=",">
<#list table.columns as col>
${col.dbName} as ${col.fieldName},
</#list>
</trim>
from ${table.tableName}
<trim prefix="where" prefixOverrides="and | or">
<#list table.columns as col>
<if test="${col.fieldName} != null and ${col.fieldName} != ''">
and ${col.dbName} = ${r"#{"}${col.fieldName}},
</if>
</#list>
</trim>
<if test="beginPos != null and beginPos != '' and pageSize != null and pageSize != ''">
list ${r"#{"}beginPos}}, ${r"#{"}pageSize}}
</if>
</select>
<!-- 根據多個條件查詢符合條件的資料總數量 -->
<select id="get${table.className}CountByMap" resultType="Integer">
select count(1)
from ${table.tableName}
<trim prefix="where" prefixOverrides="and | or">
<#list table.columns as col>
<if test="${col.fieldName} != null and ${col.fieldName} != ''">
and ${col.dbName} = ${r"#{"}${col.fieldName}},
</if>
</#list>
</trim>
</select>
<!-- 插入一個物件,返回受影響的行數 -->
<insert id="insert${table.className}" parameterType="${package}.pojo.${table.className}">
insert into ${table.tableName}(
<trim suffixOverrides=",">
<#list table.columns as col>
${r"#{"}${col.fieldName}},
</#list>
</trim>
)
values(
<trim suffixOverrides=",">
<#list table.columns as col>
${r"#{"}${col.fieldName}},
</#list>
</trim>
)
</insert>
<!-- 修改一個物件,返回受影響的行數 -->
<update id="update${table.className}" parameterType="${package}.pojo.${table.className}">
update ${table.tableName}
<trim prefix="set" suffixOverrides="," suffix="where id=${r"#{"}id}">
<#list table.columns as col>
<if test="${col.fieldName} != null and ${col.fieldName} != ''">
${col.dbName} = ${r"#{"}${col.fieldName}},
</if>
</#list>
</trim>
</update>
<!-- 刪除一個物件,返回受影響的行數 -->
<delete id="delete${table.className}ById" parameterType="Integer">
delete from ${table.tableName} where id = ${r"#{"}id}
</delete>
</mapper>
mapper檔案的模板(使用的Mybatis框架)
package ${package}.dao;
import ${package}.pojo.${table.className};
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.Map;
import java.util.List;
/**
* ${table.className}對映器介面
*/
public interface ${table.className}Mapper {
//根據id查詢單個物件
${table.className} get${table.className}ById(@Param("id") Long id) throws RuntimeException;
//根據多個條件查詢符合條件的資料
List<${table.className}> find${table.className}ListByMap(Map<String, Object> paramer) throws RuntimeException;
//根據多個條件查詢符合條件的資料總數量
Integer get${table.className}CountByMap(Map<String, Object> paramer) throws RuntimeException;
//插入一個物件,返回受影響的行數
Integer insert${table.className}(${table.className} ${table.camelName}) throws RuntimeException;
//修改一個物件,返回受影響的行數
Integer update${table.className}(${table.className} ${table.camelName}) throws RuntimeException;
//刪除一個物件,返回受影響的行數
Integer delete${table.className}ById(@Param("id")Long id) throws RuntimeException;
}
第七步、呼叫程式碼生成器自動生成pojo,dao,mapper檔案
/**
* 程式碼生成器入口
*/
public class App {
public static void main(String[]args){
String myProjectPkg="cn.yunfan.itrip";
//new一個實體類生成器pojo.ftl
try {
CodeGenerator pojoGenerator=new CodeGenerator("pojo.ftl"); //設定所有模板
pojoGenerator.setSavePath("F:\\LoveToTravel\\itrip\\itrip-beans\\src\\main\\java\\cn\\yunfan\\itrip\\pojo");
pojoGenerator.setFileNameSuffix(".java"); //生成檔案字尾
pojoGenerator.setPackage(myProjectPkg); //專案包名
//對映介面生成器
CodeGenerator mapperGenerator=new CodeGenerator("mapper.ftl");
mapperGenerator.setSavePath("F:\\LoveToTravel\\itrip\\itrip-dao\\src\\main\\java\\cn\\yunfan\\itrip\\dao");
mapperGenerator.setFileNameSuffix("Mapper.java");
mapperGenerator.setPackage(myProjectPkg);
//Mapper.xml生成器
CodeGenerator sqlGenerator=new CodeGenerator("sql.ftl");
sqlGenerator.setSavePath("F:\\LoveToTravel\\itrip\\itrip-dao\\src\\main\\java\\cn\\yunfan\\itrip\\dao");
sqlGenerator.setFileNameSuffix("Mapper.xml");
sqlGenerator.setPackage(myProjectPkg);
//呼叫三個生成器生成,分別生成1
pojoGenerator.generateCode();
mapperGenerator.generateCode();
sqlGenerator.generateCode();
} catch (Exception e) {
e.printStackTrace();
}
}
}
注:筆者所用的是maven多模組整合開發,開發環境是IDEA,以下是CodeGenerator這個模組的manven配置檔案
有需要的話可以將這個模組打壓成為jar包,放在maven本地倉庫,以便日後直接使用
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<dependencies>
<dependency>
<groupId>freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
</dependencies>
《這部分不是多模組開發可以不需要配置這個parent》
<parent>
<artifactId>itrip</artifactId>
<groupId>cn.yunfan</groupId>
<version>1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.yunfan</groupId>
<artifactId>code-generator</artifactId>
<packaging>jar</packaging>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>7</source>
<target>7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
轉載請註明出處,掌聲送給社會人