解决问题
* 前言 *
做业务开发的时候,经常要根据建立好的数据库表,生成相关的 Model , DTO , Service, Controller , DAO 等等。包括基本的增删改查。而这些细活往往比较简单且没有挑战性,纯粹苦力活。因此,根据公司的框架,开发一个代码生成器是很有必要的。
这里:
假如你有一定的java基础;
假如你熟悉freemarker模板引擎;
假如你熟悉MVC框架;
假如你熟悉Spring Data 框架
* 技术选型 *
由于代码生成器是要生成很多文件的,包 Test.java , TestDTO.java ,TestController.java , TestServiceImpl.java , ITestService.java , TestDAO.java 等等这些文件。所有考虑用 freemarker 强大的模板引擎,制作相关的模板。
* 实现思路 *
首先,假如在数据库中有一张表 ay_test.
CREATE TABLE "public"."ay_test" (
"id" varchar(32) COLLATE "default" NOT NULL,
"name" varchar(10) COLLATE "default",
"birth_date" timestamp(6),
"remark" text COLLATE "default",
CONSTRAINT "ay_test_pkey" PRIMARY KEY ("id")
)
我们首先要获取数据库的连接,这里我只贴出相关的代码。
private final String URL = "jdbc:postgresql://192.168.3.160:10655/cibpm";
private final String USER = "postgres";
private final String PASSWORD = "888888";
private final String DRIVER = "org.postgresql.Driver";
public Connection getConnection() throws Exception{
Class.forName(DRIVER);
Connection connection= DriverManager.getConnection(URL, USER, PASSWORD);
return connection;
}
获取数据库表的元数据
private final String changeTableName = replaceUnderLineAndUpperCase(tableName);
Connection connection = getConnection();
DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet resultSet = databaseMetaData.getColumns(null,"%", tableName,"%");
最后根据元数据获取表字段,注释等等,生成相关的文件
代码实现
Java代码实现
package com.evada.inno.pm.code.generate.util;
import com.evada.inno.pm.code.generate.model.ColumnClass;
import freemarker.template.Template;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 描述:代码生成器
* Created by Ay on 2017/5/1.
*/
public class CodeGenerateUtils {
private final String AUTHOR = "Ay";
private final String CURRENT_DATE = "2017/05/03";
private final String tableName = "tm_project_quality_problem";
private final String packageName = "com.evada.pm.process.manage";
private final String tableAnnotation = "质量问题";
private final String URL = "jdbc:postgresql://192.168.3.160:10655/cibpm";
private final String USER = "postgres";
private final String PASSWORD = "888888";
private final String DRIVER = "org.postgresql.Driver";
private final String diskPath = "D://";
private final String changeTableName = replaceUnderLineAndUpperCase(tableName);
public Connection getConnection() throws Exception{
Class.forName(DRIVER);
Connection connection= DriverManager.getConnection(URL, USER, PASSWORD);
return connection;
}
public static void main(String[] args) throws Exception{
CodeGenerateUtils codeGenerateUtils = new CodeGenerateUtils();
codeGenerateUtils.generate();
}
public void generate() throws Exception{
try {
Connection connection = getConnection();
DatabaseMetaData databaseMetaData = connection.getMetaData();
ResultSet resultSet = databaseMetaData.getColumns(null,"%", tableName,"%");
//生成Mapper文件
generateMapperFile(resultSet);
//生成Dao文件
generateDaoFile(resultSet);
//生成Repository文件
generateRepositoryFile(resultSet);
//生成服务层接口文件
generateServiceInterfaceFile(resultSet);
//生成服务实现层文件
generateServiceImplFile(resultSet);
//生成Controller层文件
generateControllerFile(resultSet);
//生成DTO文件
generateDTOFile(resultSet);
//生成Model文件
generateModelFile(resultSet);
} catch (Exception e) {
throw new RuntimeException(e);
}finally{
}
}
private void generateModelFile(ResultSet resultSet) throws Exception{
final String suffix = ".java";
final String path = diskPath + changeTableName + suffix;
final String templateName = "Model.ftl";
File mapperFile = new File(path);
List<ColumnClass> columnClassList = new ArrayList<>();
ColumnClass columnClass = null;
while(resultSet.next()){
//id字段略过
if(resultSet.getString("COLUMN_NAME").equals("id")) continue;
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);
}
Map<String,Object> dataMap = new HashMap<>();
dataMap.put("model_column",columnClassList);
generateFileByTemplate(templateName,mapperFile,dataMap);
}
private void generateDTOFile(ResultSet resultSet) throws Exception{
final String suffix = "DTO.java";
final String path = "D://" + changeTableName + suffix;
final String templateName = "DTO.ftl";
File mapperFile = new File(path);
Map<String,Object> dataMap = new HashMap<>();
generateFileByTemplate(templateName,mapperFile,dataMap);
}
private void generateControllerFile(ResultSet resultSet) throws Exception{
final String suffix = "Controller.java";
final String path = diskPath + changeTableName + suffix;
final String templateName = "Controller.ftl";
File mapperFile = new File(path);
Map<String,Object> dataMap = new HashMap<>();
generateFileByTemplate(templateName,mapperFile,dataMap);
}
private void generateServiceImplFile(ResultSet resultSet) throws Exception{
final String suffix = "ServiceImpl.java";
final String path = diskPath + changeTableName + suffix;
final String templateName = "ServiceImpl.ftl";
File mapperFile = new File(path);
Map<String,Object> dataMap = new HashMap<>();
generateFileByTemplate(templateName,mapperFile,dataMap);
}
private void generateServiceInterfaceFile(ResultSet resultSet) throws Exception{
final String prefix = "I";
final String suffix = "Service.java";
final String path = diskPath + prefix + changeTableName + suffix;
final String templateName = "ServiceInterface.ftl";
File mapperFile = new File(path);
Map<String,Object> dataMap = new HashMap<>();
generateFileByTemplate(templateName,mapperFile,dataMap);
}
private void generateRepositoryFile(ResultSet resultSet) throws Exception{
final String suffix = "Repository.java";
final String path = diskPath + changeTableName + suffix;
final String templateName = "Repository.ftl";
File mapperFile = new File(path);
Map<String,Object> dataMap = new HashMap<>();
generateFileByTemplate(templateName,mapperFile,dataMap);
}
private void generateDaoFile(ResultSet resultSet) throws Exception{
final String suffix = "DAO.java";
final String path = diskPath + changeTableName + suffix;
final String templateName = "DAO.ftl";
File mapperFile = new File(path);
Map<String,Object> dataMap = new HashMap<>();
generateFileByTemplate(templateName,mapperFile,dataMap);
}
private void generateMapperFile(ResultSet resultSet) throws Exception{
final String suffix = "Mapper.xml";
final String path = diskPath + changeTableName + suffix;
final String templateName = "Mapper.ftl";
File mapperFile = new File(path);
Map<String,Object> dataMap = new HashMap<>();
generateFileByTemplate(templateName,mapperFile,dataMap);
}
private void generateFileByTemplate(final String templateName,File file,Map<String,Object> dataMap) throws Exception{
Template template = FreeMarkerTemplateUtils.getTemplate(templateName);
FileOutputStream fos = new FileOutputStream(file);
dataMap.put("table_name_small",tableName);
dataMap.put("table_name",changeTableName);
dataMap.put("author",AUTHOR);
dataMap.put("date",CURRENT_DATE);
dataMap.put("package_name",packageName);
dataMap.put("table_annotation",tableAnnotation);
Writer out = new BufferedWriter(new OutputStreamWriter(fos, "utf-8"),10240);
template.process(dataMap,out);
}
public String replaceUnderLineAndUpperCase(String str){
StringBuffer sb = new StringBuffer();
sb.append(str);
int count = sb.indexOf("_");
while(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);
}
}
FreeMarkerTemplateUtils工具类
FreeMarkerTemplateUtils工具类用来配置模板所在的路径
package com.evada.inno.pm.code.generate.util;
import com.evada.inno.core.exception.BusinessException;
import freemarker.cache.ClassTemplateLoader;
import freemarker.cache.NullCacheStorage;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import java.io.IOException;
/**
* Created by Ay on 2016/7/27.
*/
public class FreeMarkerTemplateUtils {
private FreeMarkerTemplateUtils(){}
private static final Configuration CONFIGURATION = new Configuration(Configuration.VERSION_2_3_22);
static{
//这里比较重要,用来指定加载模板所在的路径
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;
}
}
public static void clearCache() {
CONFIGURATION.clearTemplateCache();
}
}
实体类 ColumnClass
ColumnClass 用来封装 数据库表元数据的信息,如字段名称,字段类型,字段注释等等。
package com.evada.inno.pm.code.generate.model;
/**
* 数据库字段封装类
* Created by Ay on 2017/5/3.
*/
public class ColumnClass {
/** 数据库字段名称 **/
private String columnName;
/** 数据库字段类型 **/
private String columnType;
/** 数据库字段首字母小写且去掉下划线字符串 **/
private String changeColumnName;
/** 数据库字段注释 **/
private String columnComment;
public String getColumnComment() {
return columnComment;
}
public void setColumnComment(String columnComment) {
this.columnComment = columnComment;
}
public String getColumnName() {
return columnName;
}
public void setColumnName(String columnName) {
this.columnName = columnName;
}
public String getColumnType() {
return columnType;
}
public void setColumnType(String columnType) {
this.columnType = columnType;
}
public String getChangeColumnName() {
return changeColumnName;
}
public void setChangeColumnName(String changeColumnName) {
this.changeColumnName = changeColumnName;
}
}
freemarker 模板文件
Freemarker的模板文件,后缀都是以ftl结尾的。
Model.ftl模型模板
Model.ftl可以生成字段的属性,并且可以生成字段属性对应的 set 和 get 方法,包括字段在数据库中对应的注释,以及一些该引入的包和类注释。
package ${package_name}.model;
import com.evada.inno.common.domain.BaseModel;
import com.evada.inno.common.listener.ICreateListenable;
import com.evada.inno.common.listener.IDeleteListenable;
import com.evada.inno.common.listener.IModifyListenable;
import org.hibernate.annotations.Where;
import javax.persistence.*;
import java.util.Date;
/**
* 描述:${table_annotation}模型
* @author ${author}
* @date ${date}
*/
@Entity
@Table(name="${table_name_small}")
@Where(clause = "status > '0'")
@Inheritance(strategy= InheritanceType.SINGLE_TABLE)
public class ${table_name} extends BaseModel implements ICreateListenable,IModifyListenable,IDeleteListenable {
<#if model_column?exists>
<#list model_column as model>
/**
*${model.columnComment!}
*/
<#if (model.columnType = 'varchar' || model.columnType = 'text')>
@Column(name = "${model.columnName}",columnDefinition = "VARCHAR")
private String ${model.changeColumnName?uncap_first};
</#if>
<#if model.columnType = 'timestamp' >
@Column(name = "${model.columnName}",columnDefinition = "TIMESTAMP")
private Date ${model.changeColumnName?uncap_first};
</#if>
</#list>
</#if>
<#if model_column?exists>
<#list model_column as model>
<#if (model.columnType = 'varchar' || model.columnType = 'text')>
public String get${model.changeColumnName}() {
return this.${model.changeColumnName?uncap_first};
}
public void set${model.changeColumnName}(String ${model.changeColumnName?uncap_first}) {
this.${model.changeColumnName?uncap_first} = ${model.changeColumnName?uncap_first};
}
</#if>
<#if model.columnType = 'timestamp' >
public Date get${model.changeColumnName}() {
return this.${model.changeColumnName?uncap_first};
}
public void set${model.changeColumnName}(Date ${model.changeColumnName?uncap_first}) {
this.${model.changeColumnName?uncap_first} = ${model.changeColumnName?uncap_first};
}
</#if>
</#list>
</#if>
}
DTO.ftl模板
DTO.ftl 文件用来生产 DTO 值对象,该对象继承 Model.ftl 文件中的对象。
package ${package_name}.dto;
import ${package_name}.model.${table_name};
/**
* 描述:${table_annotation}DTO
* @author ${author}
* @date ${date}
*/
public class ${table_name}DTO extends ${table_name}{
}
Service.ftl模板
Service.ftl 模板用来生成服务层实现类,在模板中已经添加了增 ,删,改,查等方法,同时可以注入DAO和repository 到service中,一旦文件生成,就不需要我们去写了,很方便,提高开发的效率。
package ${package_name}.service.impl;
import com.evada.inno.core.service.impl.BaseServiceImpl;
import ${package_name}.model.${table_name};
import ${package_name}.repository.${table_name}Repository;
import ${package_name}.service.I${table_name}Service;
import ${package_name}.repository.mybatis.${table_name}DAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import ${package_name}.dto.${table_name}DTO;
import org.apache.commons.beanutils.BeanUtils;
import com.evada.inno.core.enums.StatusEnum;
/**
* 描述:${table_annotation} 服务实现层
* @author ${author}
* @date ${date}
*/
@Service
public class ${table_name}ServiceImpl extends BaseServiceImpl<${table_name}, String> implements I${table_name}Service {
@Autowired
private ${table_name}DAO ${table_name?uncap_first}DAO;
@Autowired
private ${table_name}Repository ${table_name?uncap_first}Repository;
@Override
public ${table_name}DTO findDTOById(String id) throws Exception {
${table_name}DTO ${table_name?uncap_first}DTO = ${table_name?uncap_first}DAO.findDTOById(id);
return ${table_name?uncap_first}DTO;
}
@Override
public ${table_name}DTO create${table_name}(${table_name}DTO ${table_name?uncap_first}DTO) throws Exception {
${table_name} ${table_name?uncap_first} = new ${table_name}();
BeanUtils.copyProperties(${table_name?uncap_first},${table_name?uncap_first}DTO);
${table_name?uncap_first}.setStatus(StatusEnum.ENABLE.toString());
${table_name?uncap_first} = ${table_name?uncap_first}Repository.saveAndFlush(${table_name?uncap_first});
return this.findDTOById(${table_name?uncap_first}.getId());
}
@Override
public ${table_name}DTO update${table_name}(${table_name}DTO ${table_name?uncap_first}DTO)throws Exception {
${table_name} ${table_name?uncap_first} = new ${table_name}();
BeanUtils.copyProperties(${table_name?uncap_first},${table_name?uncap_first}DTO);
${table_name?uncap_first} = ${table_name?uncap_first}Repository.saveAndFlush(${table_name?uncap_first});
return this.findDTOById(${table_name?uncap_first}.getId());
}
Interface.ftl 模板
Interface.ftl 用来生成服务层接口,接口中定义了增,删,改,查等接口。
package ${package_name}.service;
import com.evada.inno.core.service.IBaseService;
import ${package_name}.model.${table_name};
import ${package_name}.dto.${table_name}DTO;
/**
* 描述:${table_annotation} 服务实现层接口
* @author ${author}
* @date ${date}
*/
public interface I${table_name}Service extends IBaseService<${table_name},String> {
/**
* 描述:根据Id获取DTO
* @param id
*/
${table_name}DTO findDTOById(String id)throws Exception;
${table_name}DTO create${table_name}(${table_name}DTO ${table_name?uncap_first}DTO) throws Exception;
void delete${table_name}(String id) throws Exception;
${table_name}DTO update${table_name}(${table_name}DTO ${table_name?uncap_first}DTO) throws Exception;
}
Respontory.ftl 模板
Respontory.ftl 用来生成 Repository 文件,这一块是 Spring Data的内容,可能不同的公司,使用的框架不一样。
package ${package_name}.repository;
import com.evada.inno.core.repository.BaseJpaRepository;
import ${package_name}.model.${table_name};
/**
* 描述:${table_annotation} Repository接口
* @author ${author}
* @date ${date}
*/
public interface ${table_name}Repository extends BaseJpaRepository<${table_name}, String> {
}
Mappter.ftl 模板
Mappter.ftl 用来生成 MyBatis 使用到的 mappter 文件,在文件中,定义了查询的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_name}.repository.mybatis.${table_name}DAO">
<resultMap id="${table_name}DTOResultMap" type="${package_name}.dto.${table_name}DTO"></resultMap>
<sql id="findDtoSql">
select * from (
select * from ${table_name_small} temp
) t
</sql>
<select id="findDTOById" parameterType="String" resultMap="${table_name}DTOResultMap">
<include refid="findDtoSql"></include>
<where>
and t.id = ${r'#{id}'}
</where>
</select>
<select id="find${table_name}Page" parameterType="${package_name}.dto.${table_name}DTO" resultMap="${table_name}DTOResultMap">
<include refid="findDtoSql" />
<where>
</where>
</select>
</mapper>
Controller.ftl 模板
Controller.ftl 文件用来生成控制层类,类中已经帮我们生成了,增,删,改,查等路由。同时可以注入接口道控制层中。
package ${package_name}.controller;
import com.evada.inno.core.annotation.Rest;
import ${package_name}.service.I${table_name}Service;
import ${package_name}.model.${table_name};
import ${package_name}.dto.${table_name}DTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.http.MediaType;
import com.evada.inno.common.domain.ResultData;
import com.evada.inno.core.util.AssertUtils;
/**
* 描述:${table_annotation}控制层
* @author ${author}
* @date ${date}
*/
@Rest(${table_name}.class)
public class ${table_name}Controller {
@Autowired
private I${table_name}Service ${table_name?uncap_first}Service;
/**
* 描述:根据Id 查询
* @param id ${table_annotation}id
*/
@RequestMapping(value = "/{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResultData findById(@PathVariable("id") String id)throws Exception {
${table_name}DTO ${table_name?uncap_first}DTO = ${table_name?uncap_first}Service.findDTOById(id);
AssertUtils.checkResourceFound(${table_name?uncap_first}DTO);
return new ResultData(${table_name}DTO.class, ${table_name?uncap_first}DTO);
}
/**
* 描述:创建${table_annotation}
* @param ${table_name?uncap_first}DTO ${table_annotation}DTO
*/
@RequestMapping(value = "", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResultData create(@RequestBody ${table_name}DTO ${table_name?uncap_first}DTO) throws Exception {
return new ResultData(${table_name}.class,${table_name?uncap_first}Service.create${table_name}(${table_name?uncap_first}DTO));
}
/**
* 描述:删除${table_annotation}
* @param id ${table_annotation}id
*/
@RequestMapping(value = "/{id}/bulk", method = RequestMethod.DELETE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public void deleteById(@PathVariable("id") String id) throws Exception {
${table_name?uncap_first}Service.deleteById(id);
}
/**
* 描述:更新${table_annotation}
* @param id ${table_annotation}id
*/
@RequestMapping(value = "/{id}", method = RequestMethod.PUT, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResultData update${table_name}(@PathVariable("id") String id,@RequestBody ${table_name}DTO ${table_name?uncap_first}DTO) throws Exception {
${table_name?uncap_first}DTO.setId(id);
return new ResultData(${table_name}.class,${table_name?uncap_first}Service.update${table_name}(${table_name?uncap_first}DTO));
}
}
通过上面的代码生成,我们就可以把生成的文件复制到相关的目录,重新启动系统,这样,基本的增,删,改,查就实现了。