1. 程式人生 > >解放你的雙手-程式碼生成器

解放你的雙手-程式碼生成器

想想一個數據庫這麼多表自己寫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>

轉載請註明出處,掌聲送給社會人