写作背景
数据库设计完成后,紧接着就是生成项目了,一个易上手并且可以随意按照自己意愿进行调整的代码生成器就显得很重要了,本例主要的特点就是:简单、实用,废话不多说,走起。
技术简介
- Java基本知识
- mysql连接编程
- freemarker基础使用
环境介绍
本例使用springboot项目,maven构建,项目结构如下:
依赖如下:
代码介绍
- CodeGenerateMain 程序入口
- JdbcUtil 获取数据库连接信息,主要包括表的名称与字段信息
- Config 项目配置信息,主要包括:数据库连接信息、文件的导出路径、所有文件的公共信息
- FreeMarkerUtils 模板输出文件的工具类,主要作用是用数据替换文件中的变量,然后生成Java文件
核心源码
Config.java
package com.ecmp.demo.config;
/**
* 配置信息
* author: 赏花归去
* date: 2019/08/28
*/
public class Config {
/**
* 模板导出路径
*/
public final static String diskPath = "G://code";
/**
* 数据库连接信息
*/
public final static String URL = "jdbc:mysql://127.0.0.1:3306/srm_rfq?serverTimezone=UTC";
public final static String USER = "root";
public final static String PASSWORD = "root";
public final static String DRIVER = "com.mysql.jdbc.Driver";
/**
* 模板对应的表,如果为空则对所有表生成对应文件
*/
public final static String[] tables = {};
/**
* 生成实体时是否去掉前缀,默认第一个下划线前面的字符为前缀
*/
public static boolean isDelPrefix = true;
/**
* 基础包路径,不同模板会自动在此基础上追加包名
*/
public final static String basePackage = "com.ecmp.demo";
/**
* 模板的公共注释信息
*/
public final static String description = "";
public final static String author = "";
public final static String date = "2019/08/28";
}
CodeGenerateMain.java
package com.ecmp.demo;
import com.ecmp.demo.util.JdbcUtil;
import static com.ecmp.demo.util.FreeMarkerUtils.generateFileByTemplate;
/**
* 入口
* author: 赏花归去
* date: 2019/08/28
*/
public class CodeGenerateMain {
public static void main(String[] args) throws Exception {
JdbcUtil.loopTable(dataMap -> {
String tableHumpName = (String) dataMap.get("tableHumpName");
//生成Model文件
generateFileByTemplate("Entity.ftl",dataMap,tableHumpName +".java");
//生成Dao文件
generateFileByTemplate("Dao.ftl",dataMap,tableHumpName +"Dao.java");
//生成Service文件
generateFileByTemplate("Service.ftl",dataMap,"I" + tableHumpName +"Service.java");
//生成helper文件
generateFileByTemplate("Helper.ftl",dataMap,tableHumpName +"ServiceHelper.java");
//生成impl文件
generateFileByTemplate("ServiceImpl.ftl",dataMap,tableHumpName +"Service.java");
//生成controller文件
generateFileByTemplate("Controller.ftl",dataMap,tableHumpName +"Controller.java");
//生成vo文件
generateFileByTemplate("Vo.ftl",dataMap,tableHumpName +"Vo.java");
//生成bo文件
generateFileByTemplate("Bo.ftl",dataMap,tableHumpName +"Bo.java");
});
}
}
JdbcUtil.java
package com.ecmp.demo.util;
import com.ecmp.demo.config.Config;
import com.ecmp.demo.model.ColumnClass;
import com.ecmp.demo.service.GenerateFileService;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.util.*;
/**
* 数据库连接类
* author: 赏花归去
* date: 2019/08/28
*/
public class JdbcUtil {
//忽略表中的某些字段
private static List<String> ignoreColumns = Arrays.asList("id"
,"creator_id"
,"creator_account"
,"creator_name"
,"created_date"
,"last_editor_id"
,"last_editor_account"
,"last_editor_name"
,"last_edited_date"
);
private static Connection connection = null;
/**
* 数据库连接
* @return
* @throws Exception
*/
public Connection getConnection() throws Exception {
if(connection == null){
Class.forName(Config.DRIVER);
Properties properties = new Properties();
properties.setProperty("user",Config.USER);
properties.setProperty("password",Config.PASSWORD);
//设置可以读取表的信息
properties.setProperty("remarks","true");
properties.setProperty("useInformationSchema","true");
connection = DriverManager.getConnection(Config.URL,properties);
}
return connection;
}
/**
* 获取表的列信息
* @return
* @throws Exception
*/
public static void loopTable(GenerateFileService generateFileService) throws Exception {
Connection connection = new JdbcUtil().getConnection();
DatabaseMetaData databaseMetaData = connection.getMetaData();
//获取表信息
ResultSet tableSet = databaseMetaData.getTables(connection.getCatalog(),"%","%",new String[]{"TABLE"});
while (tableSet.next()) {
String tableName = tableSet.getString("TABLE_NAME");
//当配置的表名不为空,则以配置为准,否则默认处理所有表
if(Config.tables.length >0){
List tables = Arrays.asList(Config.tables);
if(!tables.contains(tableName)){
continue;
}
}
Map<String, Object> dataMap = new HashMap<String, Object>();
//表名
dataMap.put("tableName", tableName);
//实体名
String tableHumpName = CommonUtil.replaceUnderLineAndUpperCase(tableName,Config.isDelPrefix);
dataMap.put("tableHumpName",tableHumpName);
//表备注
String remarks = tableSet.getString("REMARKS");
dataMap.put("tableComment",remarks);
//获取列信息
List<ColumnClass> columnClassList = new ArrayList<ColumnClass>();
ResultSet columnSet = databaseMetaData.getColumns(null, "%", tableName, "%");
while (columnSet.next()) {
String columnName = columnSet.getString("COLUMN_NAME");
//id字段略过
if (ignoreColumns.contains(columnName)) continue;
ColumnClass columnClass = new ColumnClass();
//获取字段名称
columnClass.setName(columnName);
//转换字段名称,如 sys_name 变成 SysName
columnClass.setHumpName(CommonUtil.replaceUnderLineAndUpperCase(columnName));
//获取字段类型
columnClass.setType(columnSet.getString("TYPE_NAME"));
//获取字段注释
columnClass.setComment(columnSet.getString("REMARKS").trim());
//是否可为空
String isNullable = columnSet.getString("IS_NULLABLE");
if("YES".equals(isNullable)){
columnClass.setIsNotNull(false);
}else{
columnClass.setIsNotNull(true);
}
//获取字段长度
Integer size = columnSet.getInt("COLUMN_SIZE");
columnClass.setLength(size);
columnClassList.add(columnClass);
}
dataMap.put("columnList", columnClassList);
generateFileService.generate(dataMap);
}
}
}
FreeMarkerUtils.java
package com.ecmp.demo.util;
import com.ecmp.demo.config.Config;
import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.NullCacheStorage;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import java.io.*;
import java.util.Map;
/**
* 模板工具类
* author: 赏花归去
* date: 2018/08/28
*/
public class FreeMarkerUtils {
private static Configuration CONFIGURATION = null;
/**
* 加载模板文件
* @return
*/
private static Configuration getConfiguration(){
if(CONFIGURATION == null){
CONFIGURATION = new Configuration(Configuration.VERSION_2_3_22);
CONFIGURATION.setTemplateLoader(new ClassTemplateLoader(FreeMarkerUtils.class, "/templates"));
CONFIGURATION.setDefaultEncoding("UTF-8");
CONFIGURATION.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
CONFIGURATION.setCacheStorage(NullCacheStorage.INSTANCE);
}
return CONFIGURATION;
}
/**
* 获取模板
* @param templateName
* @return
* @throws IOException
*/
private static Template getTemplate(String templateName) throws IOException {
return getConfiguration().getTemplate(templateName);
}
/**
* 生成java文件
* @param templateName
* @param dataMap
* @param fileName 文件名
* @throws Exception
*/
public static void generateFileByTemplate(String templateName, Map<String, Object> dataMap, String fileName) throws IOException, TemplateException {
dataMap.put("basePackage", Config.basePackage);
dataMap.put("author", Config.author);
dataMap.put("date", Config.date);
dataMap.put("description", Config.description + dataMap.get("tableComment"));
String parentPath = Config.diskPath + File.separator + CommonUtil.getPrefixTemplateName(templateName);
File dir = new File(parentPath);
if(!dir.exists()){
dir.mkdirs();
}
String filePath = parentPath + File.separator + fileName;
Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new File(filePath)), "utf-8"), 10240);
getTemplate(templateName).process(dataMap, out);
}
}
ColumnClass.java
package com.ecmp.demo.model;
/**
* 数据库字段辅助类
* author: 赏花归去
* date: 2019/08/28
*/
public class ColumnClass {
/**
* 数据库字段名称
*/
private String name;
/**
* 数据库字段驼峰命名
*/
private String humpName;
/**
* 数据库字段类型
*/
private String type;
/**
* 数据库字段长度
*/
private Integer length;
/**
* 数据库字段注释
*/
private String comment;
/**
* 是否不允许为空
*/
private Boolean isNotNull;
public String getComment() {
return comment;
}
public void setComment(String comment) {
this.comment = comment;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getHumpName() {
return humpName;
}
public void setHumpName(String humpName) {
this.humpName = humpName;
}
public Integer getLength() {
return length;
}
public void setLength(Integer length) {
this.length = length;
}
public Boolean getIsNotNull() {
return isNotNull;
}
public void setIsNotNull(Boolean isNotNull) {
this.isNotNull = isNotNull;
}
}
最后就是模板了,因为模板是根据实际情况而各有不同的,本文以实体类为例,其中 com.ecmp.core包下的类都是基于项目的,仅供参考使用。
package ${basePackage}.entity;
import com.ecmp.core.entity.BaseAuditableEntity;
import com.ecmp.core.entity.IFrozen;
import com.ecmp.core.entity.ITenant;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.validation.constraints.NotNull;
<#if columnList?exists>
<#list columnList as model>
<#if (model.type = 'DECIMAL')>
import java.math.BigDecimal;
<#break>
</#if>
</#list>
</#if>
<#if columnList?exists>
<#list columnList as model>
<#if (model.type = 'DATETIME' || model.type = 'DATE')>
import java.util.Date;
<#break>
</#if>
</#list>
</#if>
/**
* 描述:${description} entity
* @author ${author}
* @date ${date}
*/
@Entity
@Table(name="${tableName}")
public class ${tableHumpName} extends BaseAuditableEntity implements ITenant,IFrozen{
<#if columnList?exists>
<#list columnList as model>
/**
* ${model.comment!}
*/
<#if (model.isNotNull = true)>
@NotNull
</#if>
@Column(name = "${model.name}")
<#if (model.type = 'VARCHAR' || model.type = 'CHAR') || model.type = 'LONGTEXT'>
private String ${model.humpName?uncap_first};
</#if>
<#if (model.type = 'INT' || model.type = 'int')>
private Integer ${model.humpName?uncap_first};
</#if>
<#if (model.type = 'TINYINT' || model.type = 'tinyint')>
private Boolean ${model.humpName?uncap_first};
</#if>
<#if (model.type = 'DATETIME' || model.type = 'DATE') >
private Date ${model.humpName?uncap_first};
</#if>
<#if (model.type = 'DECIMAL' || model.type = 'decimal') >
private BigDecimal ${model.humpName?uncap_first};
</#if>
</#list>
</#if>
public ${tableHumpName}() {}
public ${tableHumpName}(String id) {
super.setId(id);
}
<#if columnList?exists>
<#list columnList as model>
<#if (model.type = 'VARCHAR' || model.type = 'CHAR') || model.type = 'LONGTEXT'>
public String get${model.humpName}() {
return this.${model.humpName?uncap_first};
}
public void set${model.humpName}(String ${model.humpName?uncap_first}) {
this.${model.humpName?uncap_first} = ${model.humpName?uncap_first};
}
</#if>
<#if model.type = 'INT' >
public Integer get${model.humpName}() {
return this.${model.humpName?uncap_first};
}
public void set${model.humpName}(Integer ${model.humpName?uncap_first}) {
this.${model.humpName?uncap_first} = ${model.humpName?uncap_first};
}
</#if>
<#if model.type = 'TINYINT' >
public Boolean get${model.humpName}() {
return this.${model.humpName?uncap_first};
}
public void set${model.humpName}(Boolean ${model.humpName?uncap_first}) {
this.${model.humpName?uncap_first} = ${model.humpName?uncap_first};
}
</#if>
<#if (model.type = 'DATETIME' || model.type = 'DATE') >
public Date get${model.humpName}() {
return this.${model.humpName?uncap_first};
}
public void set${model.humpName}(Date ${model.humpName?uncap_first}) {
this.${model.humpName?uncap_first} = ${model.humpName?uncap_first};
}
</#if>
<#if model.type = 'DECIMAL' >
public BigDecimal get${model.humpName}() {
return this.${model.humpName?uncap_first};
}
public void set${model.humpName}(BigDecimal ${model.humpName?uncap_first}) {
this.${model.humpName?uncap_first} = ${model.humpName?uncap_first};
}
</#if>
</#list>
</#if>
}
GenerateFileService.java
package com.ecmp.demo.service;
import freemarker.template.TemplateException;
import java.io.IOException;
import java.util.Map;
public interface GenerateFileService {
void generate(Map<String, Object> dataMap) throws IOException, TemplateException;
}
CommonUtil.java
package com.ecmp.demo.util;
import org.apache.commons.lang3.StringUtils;
/**
* 工具类
* author: 赏花归去
* date: 2018/8/14
*/
public class CommonUtil {
/**
* 去掉下划线,采用驼峰命名法
* @param str
* @return
*/
public static String replaceUnderLineAndUpperCase(String str) {
return replaceUnderLineAndUpperCase(str,false);
}
/**
* 去掉下划线,采用驼峰命名法,首字母大写
* @param str
* @param isDelPrefix 是否去掉前缀
* @return
*/
public static String replaceUnderLineAndUpperCase(String str,Boolean isDelPrefix) {
StringBuffer sb = new StringBuffer();
sb.append(str);
int count = sb.indexOf("_");
if(count !=-1 && isDelPrefix){
sb = sb.replace(0,count + 1,"");
count = sb.indexOf("_");
}
//去掉以下划线开头的下划线
if(count == 0){
sb = sb.replace(0,1,"");
count = sb.indexOf("_");
}
while (count != -1 && count != 0) {
int num = sb.indexOf("_", count);
count = num + 1;
if (num != -1) {
char ss = sb.charAt(count);
char ia = (char) (ss - 32);
sb.replace(count, count + 1, ia + "");
}
}
String result = sb.toString().replaceAll("_", "");
return StringUtils.capitalize(result);
}
/**
* 获取模板名字(不包含后缀名)
* @param templateName
* @return
*/
public static String getPrefixTemplateName(String templateName){
return templateName.substring(0,templateName.lastIndexOf("."));
}
}
总结
除了部分模板,基本源码都已经贴出,相信很简单很容易上手,基本两个小时就可以彻底掌握,就可以随心所欲的变幻各种符合自己特色的生成器了,欢迎评论留下您的好想法,共同进步。