前言
每个项目组甚至是一个公司的代码风格几乎都相同,其实每个需求下来,建完表之后,新建dao,service,和controller以及对应的实体类是一项非常简单又麻烦的事情,一不小心写错单词还要浪费半天时间去
找问题。通过固定的工具生成的代码是不会有这样的问题的。因此,在实际项目开发过程中,建立自己或者自己项目的代码生成工具能提高开发效率降低代码出错的可能性。而java+Freemarker能根据项目的代码设计
风格和格式很简单的设计出最适合自己的代码生成工具。
注意:本文只提供部分代码,具体可直接运行项目请到本文最后连接地址直接下载
需要了解的技术和工具
java以及开发工具(本文用idea),freeMarker基本语法以及任意文档开发工具可以直接用idea编写(本文用VSCode开发工具),
模板原型:本文是设计基于mysql和JPA实现的dao层,以及通用的restful风格的controller接口和service层代码,生成的代码直接启动springboot项目通过swagger访问接口和数据库交互
实现过程
假设mysql数据库中有一个user表,表中字段名称和类型以及说明如下
名 类型 长度
id bigint 20
creat_time timestamp 0
email varchar 100
image_url varchar 255
name varchar 100
password varchar 255
phone int 11
we_chat varchar 255
一、通过java代码获得数据库中表的字段相关信息
关键代码如下
public Connection getConnection() throws Exception {
Class.forName(DRIVER);
Connection connection = DriverManager.getConnection(URL, USER, PASSWORD);
return connection;
}
获得数据库连接之后,通过databaseMetaData对象传入表名能获得表中列相关的信息,表中列的信息放入到resultSet中
DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet resultSet = databaseMetaData.getColumns(null, "%", tableName, "%");
while (resultSet.next()) {
columnClass = new ColumnClass();
//获取字段名称
columnClass.setColumnName(resultSet.getString("COLUMN_NAME"));
//获取字段类型
columnClass.setColumnType(resultSet.getString("TYPE_NAME"));
//转换字段名称,如 sys_name 变成 SysName columnClass.setChangeColumnName(replaceUnderLineAndUpperCase(resultSet.getString("COLUMN_NAME")));
//字段在数据库的注释
columnClass.setColumnComment(resultSet.getString("REMARKS"));
columnClassList.add(columnClass);
}
二、封装一个TemplateGenModel对象,该对象封装了模板名称、生成代码的路径,和要传送的数据的map集合,以Entity为例
/**
* 根据数据库对象创建java实体类对象
*
* @param resultSet 查询数据库返回对象
* @throws Exception 抛出异常
*/
public static TemplateGenModel generateEntityFile(ResultSet resultSet, String diskPath, String changeTableName) throws Exception {
final String suffix = ".java";
final String pagePath = diskPath + File.separator+"entity"+File.separator;
File pageFile = new File(pagePath);
if (!pageFile.exists()) {
pageFile.mkdirs();
}
final String path = diskPath + File.separator+"entity"+File.separator + changeTableName + suffix;
final String templateName = "Entity.ftl";
File mapperFile = new File(path);
List<ColumnClass> columnClassList = new ArrayList<>();
ColumnClass columnClass = null;
while (resultSet.next()) {
columnClass = new ColumnClass();
String length = resultSet.getString("CHAR_OCTET_LENGTH");
//获取字段名称
columnClass.setColumnName(resultSet.getString("COLUMN_NAME"));
//获取字段类型
columnClass.setColumnType(resultSet.getString("TYPE_NAME"));
//转换字段名称,如 sys_name 变成 SysName
columnClass.setChangeColumnName(replaceUnderLineAndUpperCase(resultSet.getString("COLUMN_NAME")));
//字段在数据库的注释
columnClass.setColumnComment(resultSet.getString("REMARKS"));
if (length!=null){
columnClass.setCharOctetLength(length);
}
columnClassList.add(columnClass);
}
Map<String, Object> dataMap = new HashMap<>();
dataMap.put("model_column", columnClassList);
return new TemplateGenModel(templateName, mapperFile, dataMap);
}
public class ColumnClass {
/** 数据库字段名称 **/
private String columnName;
/** 数据库字段类型 **/
private String columnType;
/** 数据库字段首字母小写且去掉下划线字符串 **/
private String changeColumnName;
/** 数据库字段注释 **/
private String columnComment;
private String charOctetLength;
}
三、根据模板名称获得freemarker.template.Template对象,具体代码如下:
public class FreeMarkerTemplateUtils {
private FreeMarkerTemplateUtils(){}
private static final Configuration CONFIGURATION = new Configuration(Configuration.VERSION_2_3_22);
static{
//这里用来指定模板所在的路径,本项目配置在resources/templates目录下,springBoot项目会默认到resources下读文件
CONFIGURATION.setTemplateLoader(new ClassTemplateLoader(FreeMarkerTemplateUtils.class, "/templates"));
CONFIGURATION.setDefaultEncoding("UTF-8");
CONFIGURATION.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
CONFIGURATION.setCacheStorage(NullCacheStorage.INSTANCE);
}
public static Template getTemplate(String templateName) throws IOException {
try {
return CONFIGURATION.getTemplate(templateName);
} catch (IOException e) {
throw e;
}
}
}
通过我们前面封装的TemplateGenModel对象中的文件路径,生成文件输入流fos
FileOutputStream fos = new FileOutputStream(genModel.getMapperFile());
并且需一些公用字段在这里再次封装到TemplateGenModel对象中
genModel.getDataMap().put("table_name_small", tableName);
genModel.getDataMap().put("table_name", changeTableName);
genModel.getDataMap().put("author", AUTHOR);
genModel.getDataMap().put("date", CURRENT_DATE);
genModel.getDataMap().put("package_name", packageName);
genModel.getDataMap().put("table_annotation", tableAnnotation);
genModel.getDataMap().put("business_package_name",businessBasePackage);
genModel.getDataMap().put("id_type", idType);
把字节流封装成字符输出了:
Writer out = new BufferedWriter(new OutputStreamWriter(fos, "utf-8"), 10240);
通过freemarker.template.Template对象的process方法传入需要传送的数据和字符流,通过模板生成相应的.java文件。其实生成代码就是生成到指定目录下的文件。
Writer out = new BufferedWriter(new OutputStreamWriter(fos, "utf-8"), 10240);
template.process(genModel.getDataMap(), out);
四、获得相关属性之后把获取到的值放到freeMark模板中,entity模板如下:
package ${business_package_name}.entity;
import javax.persistence.*;
import lombok.Data;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
<#if model_column?exists>
<#list model_column as model>
<#if (model.columnType?lower_case = 'datetime' || model.columnType?lower_case = 'date' || model.columnType?lower_case = 'time' || model.columnType?lower_case = 'year' || model.columnType?lower_case = 'timestamp')>
import java.util.Date;
</#if>
<#if (model.columnType?lower_case = 'decimal')>
import java.math.BigDecimal;
</#if>
</#list>
</#if>
/**
* 描述:${table_annotation}模型
*
* @author ${author}
* @date ${date}
*/
@Entity
@Table(name = "${table_name_small}")
@EntityListeners(AuditingEntityListener.class)
<#-- @Where(clause = "status > '0'") -->
<#-- @Inheritance(strategy= InheritanceType.SINGLE_TABLE) -->
@Data
public class ${table_name} {
<#if model_column?exists>
<#list model_column as model>
/**
* ${model.columnComment!}
*/
<#if (model.columnName?lower_case = 'id')>
<#if (model.columnType?lower_case = 'bigint')>
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "${model.columnName}", columnDefinition = "${model.columnType}")
private Long ${model.changeColumnName?uncap_first};
<#else >
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "${model.columnName}", columnDefinition = "${model.columnType}")
private Integer ${model.changeColumnName?uncap_first};
</#if>
<#else>
<#if (model.columnType?lower_case = 'tinyint' || model.columnType?lower_case = 'bit')>
@Column(name = "${model.columnName}", columnDefinition = "VARCHAR")
private Boolean ${model.changeColumnName?uncap_first};
<#elseif (model.columnType?lower_case = 'smallint' || model.columnType?lower_case = 'mediumint' || model.columnType?lower_case = 'int' || model.columnType?lower_case = 'integer')>
@Column(name = "${model.columnName}", columnDefinition = "${model.columnType}")
private Integer ${model.changeColumnName?uncap_first};
<#elseif (model.columnType?lower_case = 'bigint')>
@Column(name = "${model.columnName}", columnDefinition = "${model.columnType}")
private Long ${model.changeColumnName?uncap_first};
<#elseif (model.columnType?lower_case = 'float')>
@Column(name = "${model.columnName}", columnDefinition = "${model.columnType}")
private Float ${model.changeColumnName?uncap_first};
<#elseif (model.columnType?lower_case = 'double')>
@Column(name = "${model.columnName}", columnDefinition = "${model.columnType}")
private Double ${model.changeColumnName?uncap_first};
<#elseif (model.columnType?lower_case = 'decimal')>
@Column(name = "${model.columnName}", columnDefinition = "${model.columnType}")
private Decimal ${model.changeColumnName?uncap_first};
<#elseif (model.columnType?lower_case = 'varchar' || model.columnType?lower_case = 'text')>
@Column(name = "${model.columnName}", columnDefinition = "VARCHAR(${model.charOctetLength})")
private String ${model.changeColumnName?uncap_first};
<#elseif model.columnType?lower_case = 'datetime' || model.columnType?lower_case = 'date' || model.columnType?lower_case = 'time' || model.columnType?lower_case = 'year' || model.columnType?lower_case = 'timestamp'>
@Column(name = "${model.columnName}", columnDefinition = "TIMESTAMP")
private Date ${model.changeColumnName?uncap_first};
<#else>
</#if>
</#if>
</#list>
</#if>
}
五、生成的代码如下,如Entity
/**
* 描述:user模型
*
* @author why
* @date 2018-06-25 14:17:51
*/
@Entity
@Table(name = "user")
@EntityListeners(AuditingEntityListener.class)
@Data
public class User {
/**
*
*/
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", columnDefinition = "BIGINT")
private Long id;
}
查看完整项目代码请下载链接:https://pan.baidu.com/s/1o-eAg-xhpyN28sg-jeie9g 密码:e0ze
上面的连接审核失败了下面刚分析的 链接:https://pan.baidu.com/s/1MDm-b2tyemyLrQ0NbAOzbQ
提取码:4v9c