前言

最近在开发一款个人小程序,介于可能使用的人可能不是很多,就采用了springboot+mybatis单服务架构作为后端,在代码生成模块使用了mybatis-generator但是繁琐的配置,不能简易自定义都成了痛点。于是使用thymeleaf模板实现项目代码生成。

原理也很简单,从数据库解析表结构。根据字段类型,渲染模板文件。创建实体类+mapper.xml+Mapper.java

步骤

1创建生成模板

创建 pojoTemplate.txt

package [(${daoPackage})];

import [(${pojoPackage})].[(${table.javaTableName})];
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface [(${table.javaTableName})]Mapper extends BaseMapper<[(${table.javaTableName})]> {

}

创建mapperTemplate.txt

<?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="[(${daoPackage})].[(${table.javaTableName})]Mapper" >
  <resultMap id="BaseResultMap" type="[(${pojoPackage})].[(${table.javaTableName})]" >
  [# th:each="field : ${table.fieldList}"][# th:if="${field.isPrimaryKey}"] <id column="[(${field.name})]" property="[(${field.camelName})]" jdbcType="[(${field.jdbcType})]" />
  [/][# th:if="${!field.isPrimaryKey}"] <result column="[(${field.name})]" property="[(${field.camelName})]" jdbcType="[(${field.jdbcType})]" />
  [/][/]</resultMap>
  <sql id="Base_Column_List" >
    [# th:each="field, var : ${table.fieldList}"][(${field.name})][# th:if="${!var.last}"],[/][/]
  </sql>
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.[(${table.primaryKey.javaType})]" >
    select
    <include refid="Base_Column_List" />
    from [(${table.tableName})]
    where [(${table.primaryKey.name})] = #{[(${table.primaryKey.camelName})],jdbcType=[(${table.primaryKey.jdbcType})]}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.[(${table.primaryKey.javaType})]" >
    delete from [(${table.tableName})]
    where [(${table.primaryKey.name})] = #{[(${table.primaryKey.camelName})],jdbcType=[(${table.primaryKey.jdbcType})]}
  </delete>
  <insert id="insert" parameterType="life.wqg.pojo.SysJob"  useGeneratedKeys="true" keyProperty="[(${table.primaryKey.camelName})]">
    insert into [(${table.tableName})] ([# th:each="field, var : ${table.fieldList}"][(${field.name})][# th:if="${!var.last}"],[/][/])
    values ([# th:each="field, var : ${table.fieldList}"]#{[(${field.camelName})],jdbcType=[(${field.jdbcType})]}[# th:if="${!var.last}"],[/][/])
  </insert>
  <insert id="insertSelective" parameterType="life.wqg.pojo.SysJob"  useGeneratedKeys="true" keyProperty="[(${table.primaryKey.camelName})]">
    insert into [(${table.tableName})]
    <trim prefix="(" suffix=")" suffixOverrides="," >
    [# th:each="field, var : ${table.fieldList}"]  <if test="[(${field.camelName})] != null" >[(${field.name})],</if>
    [/]</trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
    [# th:each="field, var : ${table.fieldList}"]  <if test="[(${field.camelName})] != null" >#{[(${field.camelName})],jdbcType=[(${field.jdbcType})]},</if>
    [/]</trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="[(${pojoPackage})].[(${table.javaTableName})]" >
    update [(${table.tableName})]
    <set >
    [# th:each="field, var : ${table.fieldList}"][# th:if="${!field.isPrimaryKey}"]  <if test="[(${field.camelName})] != null" >[(${field.name})] = #{[(${field.camelName})],jdbcType=[(${field.jdbcType})]},</if>
    [/][/]</set>
    where [(${table.primaryKey.name})] = #{[(${table.primaryKey.camelName})],jdbcType=[(${table.primaryKey.jdbcType})]}
  </update>
  <update id="updateByPrimaryKey" parameterType="[(${pojoPackage})].[(${table.javaTableName})]" >
    update [(${table.tableName})]
    set
    [# th:each="field, var : ${table.fieldList}"][# th:if="${!field.isPrimaryKey}"]  [(${field.name})] = #{[(${field.camelName})],jdbcType=[(${field.jdbcType})]}[# th:if="${!var.last}"],[/]
    [/][/]where [(${table.primaryKey.name})] = #{[(${table.primaryKey.camelName})],jdbcType=[(${table.primaryKey.jdbcType})]}
  </update>
</mapper>

创建daoTemplate.txt

package [(${daoPackage})];

import [(${pojoPackage})].[(${table.javaTableName})];
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface [(${table.javaTableName})]Mapper extends BaseMapper<[(${table.javaTableName})]> {

}

2创建表实体

创建实体SqlTable.java

package life.wqg.common.utils.code.entity;

import java.util.List;

public class SqlTable {
    /** 表名 */
    private String tableName;


    /** 对应的java实体类的名称 */
    private String javaTableName;

    /** 表里面的字段名 */
    private List<SqlField> fieldList;

    /** 表里面的主键 */
    private SqlField primaryKey;
    
    //省略getter setter
}

创建实体SqlField.java

package life.wqg.common.utils.code.entity;

public class SqlField {
    /**字段名称*/
    private String name;

    /**驼蜂式命名*/
    private String camelName;

    /**首字母大写的驼蜂式命名*/
    private String firstUpCamelName;

    /**字段类型*/
    private String type;


    /**java实体类的类型*/
    private String javaType;


    /**备注*/
    private String remark;


    /** 数据库类型 */
    private String jdbcType;

    /** 是否为主键 */
    private Boolean isPrimaryKey;

    
    //省略getter setter
}

3创建生成工具类

package life.wqg.common.utils.code;

import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.file.FileReader;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.StrUtil;
import life.wqg.common.utils.code.entity.SqlField;
import life.wqg.common.utils.code.entity.SqlTable;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import javax.sql.DataSource;
import java.io.File;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.regex.Matcher;

/**
 * 代码生成器
 */
public class CodeGenerator {


    private Map<String, String> property2JavaMap = new HashMap<>();

    private Map<String, String> property2JdbcMap = new HashMap<>();

    private DataSource dataSource = null;

    private TemplateEngine templateEngine = null;

    private String filePath = "";

    private String pojoPackage = "";

    private String daoPackage = "";

    private String mapperPackage = "";

    public CodeGenerator(DataSource dataSource, TemplateEngine templateEngine, String pojoPackage, String daoPackage, String mapperPackage) {
        this.dataSource = dataSource;
        this.templateEngine = templateEngine;
        this.filePath = System.getProperty("user.dir");
        this.pojoPackage = pojoPackage;
        this.daoPackage = daoPackage;
        this.mapperPackage = mapperPackage;
        property2JavaMap.put("BIGINT UNSIGNED", "Long");
        property2JavaMap.put("DATETIME", "Date");
        property2JavaMap.put("TIMESTAMP", "Date");
        property2JavaMap.put("VARCHAR", "String");
        property2JavaMap.put("DECIMAL", "BigDecimal");
        property2JavaMap.put("BIGINT", "Long");
        property2JavaMap.put("TEXT", "String");
        property2JavaMap.put("TINYINT", "Integer");
        property2JavaMap.put("INT", "Integer");
        property2JavaMap.put("BIT", "Integer");
        property2JavaMap.put("CHAR", "String");

        property2JdbcMap.put("BIGINT UNSIGNED", "BIGINT");
        property2JdbcMap.put("DATETIME", "TIMESTAMP");
        property2JdbcMap.put("TIMESTAMP", "TIMESTAMP");
        property2JdbcMap.put("VARCHAR", "VARCHAR");
        property2JdbcMap.put("DECIMAL", "DECIMAL");
        property2JdbcMap.put("BIGINT", "BIGINT");
        property2JdbcMap.put("TEXT", "VARCHAR");
        property2JdbcMap.put("TINYINT", "TINYINT");
        property2JdbcMap.put("INT", "INTEGER");
        property2JdbcMap.put("BIT", "BIT");
        property2JdbcMap.put("CHAR", "CHAR");
    }

    public SqlTable getTableInfo(String dbName, String tableName) {


        SqlTable table = new SqlTable();
        //把tableName转成驼峰式,并且首子母改成大写
        String javaTableName = StrUtil.upperFirst(StrUtil.toCamelCase(tableName));
        table.setTableName(tableName);
        table.setJavaTableName(javaTableName);
        Connection conn = null;
        try {
            conn = dataSource.getConnection();

            DatabaseMetaData metaData = conn.getMetaData();
            ResultSet resultSet = metaData.getColumns(null, null, tableName, "%");
            ResultSet pkRSet = metaData.getPrimaryKeys(null, null, tableName);
            Set<String> pkSet = new HashSet<>();
            while (pkRSet.next()) {
                pkSet.add(pkRSet.getString("COLUMN_NAME"));
            }
            String columnName;
            String columnType;


            List<SqlField> fieldList = new ArrayList<>();
            while (resultSet.next()) {
                if (!resultSet.getString("TABLE_CAT").equals(dbName)) {
                    continue;
                }
                SqlField field = new SqlField();
                columnName = resultSet.getString("COLUMN_NAME");
                columnType = resultSet.getString("TYPE_NAME");
                String remark = resultSet.getString("REMARKS");
                field.setName(columnName);
                field.setIsPrimaryKey(pkSet.contains(columnName));
                field.setRemark(remark);
                field.setType(columnType);
                field.setCamelName(StrUtil.toCamelCase(columnName));
                field.setFirstUpCamelName(StrUtil.upperFirst(field.getCamelName()));
                field.setJavaType(property2JavaMap.get(columnType));
                field.setJdbcType(property2JdbcMap.get(columnType));
                fieldList.add(field);
                if (pkSet.contains(columnName)){
                    //主键
                    table.setPrimaryKey(field);
                }
            }
            table.setFieldList(fieldList);
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
        return table;
    }


    /**
     * 开始生成代码
     */
    public void startGenerate(String dbName, String tableName, boolean isGeneratePojo, boolean isGenerateDao, boolean isGenerateXml,boolean isOverwriteDao) {
        SqlTable tableInfo = this.getTableInfo(dbName, tableName);
        Context ctx = new Context(Locale.CHINA);
        if (isGeneratePojo) {
            generatePojo(ctx, tableInfo);
        }
        if (isGenerateDao) {
            generateDao(ctx, tableInfo,isOverwriteDao);
        }
        if (isGenerateXml) {
            generateXml(ctx,tableInfo);
        }
    }

    /**
     * 生成xml
     * @param ctx
     * @param tableInfo
     */
    private void generateXml(Context ctx, SqlTable tableInfo) {
        Map<String, Object> map = new HashMap<>();
        map.put("table", tableInfo);
        map.put("pojoPackage", pojoPackage);
        map.put("daoPackage", daoPackage);
        ctx.setVariables(map);
        String process = templateEngine.process("code/mapperTemplate.txt", ctx);
        FileWriter writer = new FileWriter(filePath + "\\src\\main\\resources\\" + mapperPackage.replaceAll("\\.", Matcher.quoteReplacement(File.separator)) + "\\" + tableInfo.getJavaTableName() + "Mapper.xml");
        writer.write(process);
    }

    /**
     * 生成Dao
     *
     * @param ctx
     * @param tableInfo
     */
    private void generateDao(Context ctx, SqlTable tableInfo,boolean isOverwriteDao) {
        if (!isOverwriteDao){
            try {
                FileReader fileReader = new FileReader(filePath + "\\src\\main\\java\\" + daoPackage.replaceAll("\\.", Matcher.quoteReplacement(File.separator)) + "\\" + tableInfo.getJavaTableName() + "Mapper.java");
                String result = fileReader.readString();
                if (!StrUtil.isEmpty(result)){
                    return;
                }
            }catch (IORuntimeException exception){
                System.out.println("文件不存在,已生成");
            }

        }
        Map<String, Object> map = new HashMap<>();
        map.put("table", tableInfo);
        map.put("pojoPackage", pojoPackage);
        map.put("daoPackage", daoPackage);
        ctx.setVariables(map);
        String process = templateEngine.process("code/daoTemplate.txt", ctx);
        FileWriter writer = null;
        writer = new FileWriter(filePath + "\\src\\main\\java\\" + daoPackage.replaceAll("\\.", Matcher.quoteReplacement(File.separator)) + "\\" + tableInfo.getJavaTableName() + "Mapper.java");
        writer.write(process);
    }

    /**
     * 生成pojo
     *
     * @param ctx
     */
    private void generatePojo(Context ctx, SqlTable tableInfo) {
        Map<String, Object> map = new HashMap<>();
        map.put("table", tableInfo);
        map.put("pojoPackage", pojoPackage);
        ctx.setVariables(map);
        String process = templateEngine.process("code/pojoTemplate.txt", ctx);
        FileWriter writer = null;
        writer = new FileWriter(filePath + "\\src\\main\\java\\" + pojoPackage.replaceAll("\\.", Matcher.quoteReplacement(File.separator)) + "\\" + tableInfo.getJavaTableName() + ".java");
        writer.write(process);
    }


    public static void main(String[] args) {
        System.out.printf(System.getProperty("user.dir"));
       /* FileWriter writer = new FileWriter("test.properties");
        writer.write("test");*/
    }

}

4调用生成

package life.wqg;

import com.upyun.UpException;
import life.wqg.common.utils.code.CodeGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;

import javax.sql.DataSource;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Collections;

@SpringBootTest
public class GeneratorTest {
    @Autowired
    private DataSource dataSource;

    @Autowired
    private TemplateEngine templateEngine;

    @org.junit.jupiter.api.Test
    public void generator() throws SQLException, UpException, IOException {
        String dbName = "test";
        CodeGenerator codeGenerator = new CodeGenerator(dataSource,templateEngine,"life.wqg.pojo","life.wqg.dao","mapper");
        codeGenerator.startGenerate(dbName,"user_info",true,true,true,false);

    }
}

标签: none

添加新评论