thymeleaf+mybatis代码生成
前言
最近在开发一款个人小程序,介于可能使用的人可能不是很多,就采用了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);
}
}