解放你的双手-代码生成器

想想一个数据库这么多表自己写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>

转载请注明出处,掌声送给社会人

猜你喜欢

转载自blog.csdn.net/SCDN_CP/article/details/83993365