一、介绍

我记得最早刚步入互联网行业的时候,当时按照 MVC 的思想和模型,每次开发新功能,会依次编写 dao、service、controller相关服务类,包括对应的 dto、entity、vo 等等实体类,如果有多张单表,也会重复的编写相似的代码,现在回想起来,感觉当时自己好像处于石器时代!

实际上,当仔细的总结一下,对于任何一张单表的操作,基本都是围绕增(Create )、删(Delete )、改(Update )、查(Retrieve )四个方向进行数据操作,简称 CRUD!

他们除了表名和存储空间不一样,基本的 CRUD 思路基本都是一样的。

为了解决这些重复劳动的痛点,业界逐渐开源了一批代码生成器,目的也很简单,就是为了减少手工操作的繁琐,集中精力在业务开发上,提升开发效率。

而今天,我们所要介绍的也是代码生成器,很多初学者可能觉得代码生成器很高深。代码生成器其实是一个很简单的东西,一点都不高深。

当你看完本文的时候,你会完全掌握代码生成器的逻辑,甚至可以根据自己的项目情况,进行深度定制。

二、实现思路

下面我们就以SpringBoot项目为例,数据持久化操作采用Mybatis,数据库采用Mysql,编写一个自动生成增、删、改、查等基础功能的代码生成器,内容包括controller、service、dao、entity、dto、vo等信息。

实现思路如下:

第一步:获取表字段名称、类型、表注释等信息

第二步:基于 freemarker 模板引擎,编写相应的模板

第三步:根据对应的模板,生成相应的 java 代码

2.1、获取表结构

首先我们创建一张test_db表,脚本如下:


  1. CREATE TABLE test_db ( 

  2.   id bigint(20) unsigned NOT NULL COMMENT '主键ID'

  3.   name varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名称'

  4.   is_delete tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否删除 1:已删除;0:未删除'

  5.   create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'

  6.   update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'

  7.   PRIMARY KEY (id), 

  8.   KEY idx_create_time (create_time) USING BTREE 

  9. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='测试表'


表创建完成之后,基于test_db表,我们查询对应的表结果字段名称、类型、备注信息,这些关键信息将用于后续进行代码生成器所使用!

  1. # 获取对应表结构 

  2. SELECT column_name, data_type, column_comment FROM information_schema.columns WHERE table_schema = 'yjgj_base' AND table_name = 'test_db

同时,获取对应表注释,用于生成备注信息!

  1. # 获取对应表注释 

  2. SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = 'yjgj_base' AND table_name = 'test_db' 


2.2、编写模板

编写mapper.ftl模板,涵盖新增、修改、删除、查询等信息

  1. <?xml version="1.0" encoding="UTF-8" ?> 

  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > 

  3. <mapper namespace="${daoPackageName}.${daoName}"

  4.  

  5.  <!--BaseResultMap--> 

  6.  <resultMap id="BaseResultMap" type="${entityPackageName}.${entityName}"

  7.         <#list columns as pro> 

  8.             <#if pro.proName == primaryId> 

  9.     <id column="${primaryId}" property="${primaryId}" jdbcType="${pro.fieldType}"/> 

  10.             <#else

  11.     <result column="${pro.fieldName}" property="${pro.proName}" jdbcType="${pro.fieldType}"/> 

  12.             </#if> 

  13.         </#list> 

  14.  </resultMap> 

  15.  

  16.  <!--Base_Column_List--> 

  17.  <sql id="Base_Column_List"

  18.         <#list columns as pro> 

  19.             <#if pro_index == 0>${pro.fieldName}<#else>,${pro.fieldName}</#if> 

  20.         </#list> 

  21.  </sql> 

  22.  

  23.  <!--批量插入--> 

  24.  <insert id="insertList" parameterType="java.util.List"

  25.   insert into ${tableName} ( 

  26.         <#list columns as pro> 

  27.             <#if pro_index == 0>${pro.fieldName},<#elseif pro_index == 1>${pro.fieldName}<#else>,${pro.fieldName}</#if> 

  28.         </#list> 

  29.   ) 

  30.   values 

  31.   <foreach collection ="list" item="obj" separator =","

  32.    <trim prefix=" (" suffix=")" suffixOverrides=","

  33.                 <#list columns as pro> 

  34.                     ${r"#{obj." + pro.proName + r"}"}, 

  35.                 </#list> 

  36.    </trim> 

  37.   </foreach > 

  38.  </insert

  39.  

  40.  <!--按需新增--> 

  41.  <insert id="insertPrimaryKeySelective" parameterType="${entityPackageName}.${entityName}"

  42.   insert into ${tableName} 

  43.   <trim prefix="(" suffix=")" suffixOverrides=","

  44.             <#list columns as pro> 

  45.     <if test="${pro.proName} != null"

  46.                     ${pro.fieldName}, 

  47.     </if> 

  48.             </#list> 

  49.   </trim> 

  50.   <trim prefix="values (" suffix=")" suffixOverrides=","

  51.             <#list columns as pro> 

  52.     <if test="${pro.proName} != null"

  53.                     ${r"#{" + pro.proName + r",jdbcType=" + pro.fieldType +r"}"}, 

  54.     </if> 

  55.             </#list> 

  56.   </trim> 

  57.  </insert

  58.  

  59.  <!-- 按需修改--> 

  60.  <update id="updatePrimaryKeySelective" parameterType="${entityPackageName}.${entityName}"

  61.   update ${tableName} 

  62.   <set

  63.             <#list columns as pro> 

  64.                 <#if pro.fieldName != primaryId && pro.fieldName != primaryId> 

  65.      <if test="${pro.proName} != null"

  66.                         ${pro.fieldName} = ${r"#{" + pro.proName + r",jdbcType=" + pro.fieldType +r"}"}, 

  67.      </if> 

  68.                 </#if> 

  69.             </#list> 

  70.   </set

  71.   where ${primaryId} = ${r"#{" + "${primaryId}" + r",jdbcType=BIGINT}"

  72.  </update

  73.  

  74.  <!-- 按需批量修改--> 

  75.  <update id="updateBatchByIds" parameterType="java.util.List"

  76.   update ${tableName} 

  77.   <trim prefix="set" suffixOverrides=","

  78.             <#list columns as pro> 

  79.                 <#if pro.fieldName != primaryId && pro.fieldName != primaryId> 

  80.      <trim prefix="${pro.fieldName}=case" suffix="end,"

  81.       <foreach collection="list" item="obj" index="index"

  82.        <if test="obj.${pro.proName} != null"

  83.         when id = ${r"#{" + "obj.id" + r"}"

  84.         then  ${r"#{obj." + pro.proName + r",jdbcType=" + pro.fieldType +r"}"

  85.        </if> 

  86.       </foreach> 

  87.      </trim> 

  88.                 </#if> 

  89.             </#list> 

  90.   </trim> 

  91.   where 

  92.   <foreach collection="list" separator="or" item="obj" index="index" > 

  93.    id = ${r"#{" + "obj.id" + r"}"

  94.   </foreach> 

  95.  </update

  96.  

  97.  <!-- 删除--> 

  98.  <delete id="deleteByPrimaryKey" parameterType="java.lang.Long"

  99.   delete from ${tableName} 

  100.   where ${primaryId} = ${r"#{" + "${primaryId}" + r",jdbcType=BIGINT}"

  101.  </delete

  102.  

  103.  <!-- 查询详情 --> 

  104.  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long"

  105.   select 

  106.   <include refid="Base_Column_List"/> 

  107.   from ${tableName} 

  108.   where ${primaryId} = ${r"#{" + "${primaryId}" + r",jdbcType=BIGINT}"

  109.  </select

  110.  

  111.  <!-- 按需查询 --> 

  112.  <select id="selectByPrimaryKeySelective" resultMap="BaseResultMap" parameterType="${entityPackageName}.${entityName}"

  113.   select 

  114.   <include refid="Base_Column_List"/> 

  115.   from ${tableName} 

  116.  </select

  117.  

  118.  <!-- 批量查询--> 

  119.  <select id="selectByIds" resultMap="BaseResultMap" parameterType="java.util.List"

  120.   select 

  121.   <include refid="Base_Column_List"/> 

  122.   from ${tableName} 

  123.   <where

  124.    <if test="ids != null"

  125.     and ${primaryId} in 

  126.     <foreach item="item" index="index" collection="ids" open="(" separator="," close=")"

  127.                     ${r"#{" + "item" + r"}"

  128.     </foreach> 

  129.    </if> 

  130.   </where

  131.  </select

  132.  

  133.  <!-- 根据条件查询 --> 

  134.  <select id="selectByMap" resultMap="BaseResultMap" parameterType="java.util.Map"

  135.   select 

  136.   <include refid="Base_Column_List"/> 

  137.   from ${tableName} 

  138.  </select

  139.  

  140.  <!-- 查询${entityName}总和 --> 

  141.  <select id="countPage" resultType="int" parameterType="${dtoPackageName}.${dtoName}"

  142.   select count(${primaryId}) 

  143.   from ${tableName} 

  144.  </select

  145.  

  146.  <!-- 查询${entityName}列表 --> 

  147.  <select id="selectPage" resultMap="BaseResultMap" parameterType="${dtoPackageName}.${dtoName}"

  148.   select 

  149.   <include refid="Base_Column_List"/> 

  150.   from ${tableName} 

  151.   limit ${r"#{" + "start,jdbcType=INTEGER" + r"}"},${r"#{" + "end,jdbcType=INTEGER" + r"}"

  152.  </select

  153.  

  154. </mapper> 


编写dao.ftl数据访问模板

  1. package ${daoPackageName}; 

  2.  

  3. import com.example.generator.core.BaseMapper; 

  4. import java.util.List; 

  5. import ${entityPackageName}.${entityName}; 

  6. import ${dtoPackageName}.${dtoName}; 

  7.  

  8. /** 

  9. * @ClassName: ${daoName} 

  10. * @Description: 数据访问接口 

  11. * @author ${authorName} 

  12. * @date ${currentTime} 

  13. */ 

  14. public interface ${daoName} extends BaseMapper<${entityName}>{ 

  15.  

  16.  int countPage(${dtoName} ${dtoName?uncap_first}); 

  17.  

  18.  List<${entityName}> selectPage(${dtoName} ${dtoName?uncap_first}); 

编写service.ftl服务接口模板

  1. package ${servicePackageName}; 

  2.  

  3. import com.example.generator.core.BaseService; 

  4. import com.example.generator.common.Pager; 

  5. import ${voPackageName}.${voName}; 

  6. import ${dtoPackageName}.${dtoName}; 

  7. import ${entityPackageName}.${entityName}; 

  8.  

  9. /** 

  10.  * 

  11.  * @ClassName: ${serviceName} 

  12.  * @Description: ${entityName}业务访问接口 

  13.  * @author ${authorName} 

  14.  * @date ${currentTime} 

  15.  * 

  16.  */ 

  17. public interface ${serviceName} extends BaseService<${entityName}> { 

  18.  

  19.  /** 

  20.   * 分页列表查询 

  21.   * @param request 

  22.   */ 

  23.  Pager<${voName}> getPage(${dtoName} request); 

编写serviceImpl.ftl服务实现类模板

  1. package ${serviceImplPackageName}; 

  2.  

  3. import com.example.generator.common.Pager; 

  4. import com.example.generator.core.BaseServiceImpl; 

  5. import com.example.generator.test.service.TestEntityService; 

  6. import org.springframework.beans.BeanUtils; 

  7. import org.springframework.stereotype.Service; 

  8. import org.springframework.util.CollectionUtils; 

  9. import org.slf4j.Logger; 

  10. import org.slf4j.LoggerFactory; 

  11.  

  12. import java.util.ArrayList; 

  13. import java.util.List; 

  14.  

  15.  

  16. import ${daoPackageName}.${daoName}; 

  17. import ${entityPackageName}.${entityName}; 

  18. import ${dtoPackageName}.${dtoName}; 

  19. import ${voPackageName}.${voName}; 

  20.  

  21.  

  22. @Service 

  23. public class ${serviceImplName} extends BaseServiceImpl<${daoName}, ${entityName}> implements ${serviceName} { 

  24.  

  25.  private static final Logger log = LoggerFactory.getLogger(${serviceImplName}.class); 

  26.  

  27.  /** 

  28.   * 分页列表查询 

  29.   * @param request 

  30.   */ 

  31.  public Pager<${voName}> getPage(${dtoName} request) { 

  32.   List<${voName}> resultList = new ArrayList(); 

  33.   int count = super.baseMapper.countPage(request); 

  34.   List<${entityName}> dbList = count > 0 ? super.baseMapper.selectPage(request) : new ArrayList<>(); 

  35.   if(!CollectionUtils.isEmpty(dbList)){ 

  36.    dbList.forEach(source->{ 

  37.     ${voName} target = new ${voName}(); 

  38.     BeanUtils.copyProperties(source, target); 

  39.     resultList.add(target); 

  40.    }); 

  41.   } 

  42.   return new Pager(request.getCurrPage(), request.getPageSize(), count, resultList); 

  43.  } 

编写controller.ftl控制层模板

  1. package ${controllerPackageName}; 

  2.  

  3. import com.example.generator.common.IdRequest; 

  4. import com.example.generator.common.Pager; 

  5. import org.springframework.beans.BeanUtils; 

  6. import org.springframework.beans.factory.annotation.Autowired; 

  7. import org.springframework.web.bind.annotation.PostMapping; 

  8. import org.springframework.web.bind.annotation.RequestBody; 

  9. import org.springframework.web.bind.annotation.RequestMapping; 

  10. import org.springframework.web.bind.annotation.RestController; 

  11. import java.util.Objects; 

  12.  

  13. import ${servicePackageName}.${serviceName}; 

  14. import ${entityPackageName}.${entityName}; 

  15. import ${dtoPackageName}.${dtoName}; 

  16. import ${voPackageName}.${voName}; 

  17.  

  18. /** 

  19.  * 

  20.  * @ClassName: ${controllerName} 

  21.  * @Description: 外部访问接口 

  22.  * @author ${authorName} 

  23.  * @date ${currentTime} 

  24.  * 

  25.  */ 

  26. @RestController 

  27. @RequestMapping("/${entityName?uncap_first}"

  28. public class ${controllerName} { 

  29.  

  30.  @Autowired 

  31.  private ${serviceName} ${serviceName?uncap_first}; 

  32.  

  33.  /** 

  34.   * 分页列表查询 

  35.   * @param request 

  36.   */ 

  37.  @PostMapping(value = "/getPage"

  38.  public Pager<${voName}> getPage(@RequestBody ${dtoName} request){ 

  39.   return ${serviceName?uncap_first}.getPage(request); 

  40.  } 

  41.  

  42.  /** 

  43.   * 查询详情 

  44.   * @param request 

  45.   */ 

  46.  @PostMapping(value = "/getDetail"

  47.  public ${voName} getDetail(@RequestBody IdRequest request){ 

  48.   ${entityName} source = ${serviceName?uncap_first}.selectById(request.getId()); 

  49.   if(Objects.nonNull(source)){ 

  50.    ${voName} result = new ${voName}(); 

  51.    BeanUtils.copyProperties(source, result); 

  52.    return result; 

  53.   } 

  54.   return null

  55.  } 

  56.  

  57.  /** 

  58.   * 新增操作 

  59.   * @param request 

  60.   */ 

  61.  @PostMapping(value = "/save"

  62.  public void save(${dtoName} request){ 

  63.   ${entityName} entity = new ${entityName}(); 

  64.   BeanUtils.copyProperties(request, entity); 

  65.   ${serviceName?uncap_first}.insert(entity); 

  66.  } 

  67.  

  68.  /** 

  69.   * 编辑操作 

  70.   * @param request 

  71.   */ 

  72.  @PostMapping(value = "/edit"

  73.  public void edit(${dtoName} request){ 

  74.   ${entityName} entity = new ${entityName}(); 

  75.   BeanUtils.copyProperties(request, entity); 

  76.   ${serviceName?uncap_first}.updateById(entity); 

  77.  } 

  78.  

  79.  /** 

  80.   * 删除操作 

  81.   * @param request 

  82.   */ 

  83.  @PostMapping(value = "/delete"

  84.  public void delete(IdRequest request){ 

  85.   ${serviceName?uncap_first}.deleteById(request.getId()); 

  86.  } 


编写entity.ftl实体类模板

  1. package ${entityPackageName}; 

  2.  

  3. import java.io.Serializable

  4. import java.math.BigDecimal; 

  5. import java.util.Date

  6.  

  7. /** 

  8.  * 

  9.  * @ClassName: ${entityName} 

  10.  * @Description: ${tableDes!}实体类 

  11.  * @author ${authorName} 

  12.  * @date ${currentTime} 

  13.  * 

  14.  */ 

  15. public class ${entityName} implements Serializable { 

  16.  

  17.  private static final long serialVersionUID = 1L; 

  18.   

  19.  <#--属性遍历--> 

  20.  <#list columns as pro> 

  21.  <#--<#if pro.proName != primaryId 

  22.  && pro.proName != 'remarks' 

  23.  && pro.proName != 'createBy' 

  24.  && pro.proName != 'createDate' 

  25.  && pro.proName != 'updateBy' 

  26.  && pro.proName != 'updateDate' 

  27.  && pro.proName != 'delFlag' 

  28.  && pro.proName != 'currentUser' 

  29.  && pro.proName != 'page' 

  30.  && pro.proName != 'sqlMap' 

  31.  && pro.proName != 'isNewRecord' 

  32.  ></#if>--> 

  33.  /** 

  34.   * ${pro.proDes!} 

  35.   */ 

  36.  private ${pro.proType} ${pro.proName}; 

  37.  </#list> 

  38.  

  39.  <#--属性get||set方法--> 

  40.  <#list columns as pro> 

  41.  public ${pro.proType} get${pro.proName?cap_first}() { 

  42.   return this.${pro.proName}; 

  43.  } 

  44.  

  45.  public ${entityName} set${pro.proName?cap_first}(${pro.proType} ${pro.proName}) { 

  46.   this.${pro.proName} = ${pro.proName}; 

  47.   return this; 

  48.  } 

  49.  </#list> 

编写dto.ftl实体类模板

package ${dtoPackageName};  import com.example.generator.core.BaseDTO; import java.io.Serializable;  /**  * @ClassName: ${dtoName}  * @Description: 请求实体类  * @author ${authorName}  * @date ${currentTime}  *  */ public class ${dtoName} extends BaseDTO {  }

编写vo.ftl视图实体类模板

package ${voPackageName};  import java.io.Serializable;  /**  * @ClassName: ${voName}  * @Description: 返回视图实体类  * @author ${authorName}  * @date ${currentTime}  *  */ public class ${voName} implements Serializable {   private static final long serialVersionUID = 1L; }

可能细心的网友已经看到了,在模板中我们用到了BaseMapper、BaseService、BaseServiceImpl等等服务类。

之所以有这三个类,是因为在模板中,我们有大量的相同的方法名包括逻辑也相似,除了所在实体类不一样以外,其他都一样,因此我们可以借助泛型类来将这些服务抽成公共的部分。

BaseMapper,主要负责将dao层的公共方法抽出来

package com.example.generator.core;  import org.apache.ibatis.annotations.Param;  import java.io.Serializable; import java.util.List; import java.util.Map;  /**  * @author pzblog  * @Description  * @since 2020-11-11  */ public interface BaseMapper<T> {      /**      * 批量插入      * @param list      * @return      */     int insertList(@Param("list") List<T> list);      /**      * 按需插入一条记录      * @param entity      * @return      */     int insertPrimaryKeySelective(T entity);      /**      * 按需修改一条记录(通过主键ID)      * @return      */     int updatePrimaryKeySelective(T entity);      /**      * 批量按需修改记录(通过主键ID)      * @param list      * @return      */     int updateBatchByIds(@Param("list") List<T> list);      /**      * 根据ID删除      * @param id 主键ID      * @return      */     int deleteByPrimaryKey(Serializable id);      /**      * 根据ID查询      * @param id 主键ID      * @return      */     T selectByPrimaryKey(Serializable id);      /**      * 按需查询      * @param entity      * @return      */     List<T> selectByPrimaryKeySelective(T entity);      /**      * 批量查询      * @param ids 主键ID集合      * @return      */     List<T> selectByIds(@Param("ids") List<? extends Serializable> ids);      /**      * 查询(根据 columnMap 条件)      * @param columnMap 表字段 map 对象      * @return      */     List<T> selectByMap(Map<String, Object> columnMap); }

BaseService,主要负责将service层的公共方法抽出来

package com.example.generator.core;  import java.io.Serializable; import java.util.List; import java.util.Map;  /**  * @author pzblog  * @Description 服务类  * @since 2020-11-11  */ public interface BaseService<T> {      /**      * 新增      * @param entity      * @return boolean      */     boolean insert(T entity);      /**      * 批量新增      * @param list      * @return boolean      */     boolean insertList(List<T> list);      /**      * 通过ID修改记录(如果想全部更新,只需保证字段都不为NULL)      * @param entity      * @return boolean      */     boolean updateById(T entity);      /**      * 通过ID批量修改记录(如果想全部更新,只需保证字段都不为NULL)      * @param list      * @return boolean      */     boolean updateBatchByIds(List<T> list);      /**      * 根据ID删除      * @param id 主键ID      * @return boolean      */     boolean deleteById(Serializable id);      /**      * 根据ID查询      * @param id 主键ID      * @return      */     T selectById(Serializable id);      /**      * 按需查询      * @param entity      * @return      */     List<T> selectByPrimaryKeySelective(T entity);      /**      * 批量查询      * @param ids      * @return      */     List<T> selectByIds(List<? extends Serializable> ids);      /**      * 根据条件查询      * @param columnMap      * @return      */     List<T> selectByMap(Map<String, Object> columnMap);  }

BaseServiceImpl,service层的公共方法具体实现类

package com.example.generator.core;  import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional;  import java.io.Serializable; import java.util.List; import java.util.Map;  /**  * @author pzblog  * @Description 实现类( 泛型说明:M 是 mapper 对象,T 是实体)  * @since 2020-11-11  */ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements BaseService<T>{      @Autowired     protected M baseMapper;      /**      * 新增      * @param entity      * @return boolean      */     @Override     @Transactional(rollbackFor = {Exception.class})     public boolean insert(T entity){         return returnBool(baseMapper.insertPrimaryKeySelective(entity));     }      /**      * 批量新增      * @param list      * @return boolean      */     @Override     @Transactional(rollbackFor = {Exception.class})     public boolean insertList(List<T> list){         return returnBool(baseMapper.insertList(list));     }      /**      * 通过ID修改记录(如果想全部更新,只需保证字段都不为NULL)      * @param entity      * @return boolean      */     @Override     @Transactional(rollbackFor = {Exception.class})     public boolean updateById(T entity){         return returnBool(baseMapper.updatePrimaryKeySelective(entity));     }      /**      * 通过ID批量修改记录(如果想全部更新,只需保证字段都不为NULL)      * @param list      * @return boolean      */     @Override     @Transactional(rollbackFor = {Exception.class})     public boolean updateBatchByIds(List<T> list){         return returnBool(baseMapper.updateBatchByIds(list));     }      /**      * 根据ID删除      * @param id 主键ID      * @return boolean      */     @Override     @Transactional(rollbackFor = {Exception.class})     public boolean deleteById(Serializable id){         return returnBool(baseMapper.deleteByPrimaryKey(id));     }      /**      * 根据ID查询      * @param id 主键ID      * @return      */     @Override     public T selectById(Serializable id){         return baseMapper.selectByPrimaryKey(id);     }      /**      * 按需查询      * @param entity      * @return      */     @Override     public List<T> selectByPrimaryKeySelective(T entity){         return baseMapper.selectByPrimaryKeySelective(entity);     }      /**      * 批量查询      * @param ids      * @return      */     @Override     public List<T> selectByIds(List<? extends Serializable> ids){         return baseMapper.selectByIds(ids);     }      /**      * 根据条件查询      * @param columnMap      * @return      */     @Override     public List<T> selectByMap(Map<String, Object> columnMap){         return baseMapper.selectByMap(columnMap);     }      /**      * 判断数据库操作是否成功      * @param result 数据库操作返回影响条数      * @return boolean      */     protected boolean returnBool(Integer result) {         return null != result && result >= 1;     }  }

在此,还封装来其他的类,例如 dto 公共类BaseDTO,分页类Pager,还有 id 请求类IdRequest。

BaseDTO公共类

public class BaseDTO implements Serializable {      /**      * 请求token      */     private String token;      /**      * 当前页数      */     private Integer currPage = 1;      /**      * 每页记录数      */     private Integer pageSize = 20;      /**      * 分页参数(第几行)      */     private Integer start;      /**      * 分页参数(行数)      */     private Integer end;      /**      * 登录人ID      */     private String loginUserId;      /**      * 登录人名称      */     private String loginUserName;      public String getToken() {         return token;     }      public BaseDTO setToken(String token) {         this.token = token;         return this;     }      public Integer getCurrPage() {         return currPage;     }      public BaseDTO setCurrPage(Integer currPage) {         this.currPage = currPage;         return this;     }      public Integer getPageSize() {         return pageSize;     }      public BaseDTO setPageSize(Integer pageSize) {         this.pageSize = pageSize;         return this;     }      public Integer getStart() {         if (this.currPage != null && this.currPage > 0) {             start = (currPage - 1) * getPageSize();             return start;         }         return start == null ? 0 : start;     }      public BaseDTO setStart(Integer start) {         this.start = start;         return this;     }      public Integer getEnd() {         return getPageSize();     }      public BaseDTO setEnd(Integer end) {         this.end = end;         return this;     }      public String getLoginUserId() {         return loginUserId;     }      public BaseDTO setLoginUserId(String loginUserId) {         this.loginUserId = loginUserId;         return this;     }      public String getLoginUserName() {         return loginUserName;     }      public BaseDTO setLoginUserName(String loginUserName) {         this.loginUserName = loginUserName;         return this;     }  }

Pager分页类

public class Pager<T extends Serializable> implements Serializable {      private static final long serialVersionUID = -6557244954523041805L;      /**      * 当前页数      */     private int currPage;      /**      * 每页记录数      */     private int pageSize;      /**      * 总页数      */     private int totalPage;      /**      * 总记录数      */     private int totalCount;      /**      * 列表数据      */     private List<T> list;      public Pager(int currPage, int pageSize) {         this.currPage = currPage;         this.pageSize = pageSize;     }      public Pager(int currPage, int pageSize, int totalCount, List<T> list) {         this.currPage = currPage;         this.pageSize = pageSize;         this.totalPage = (int) Math.ceil((double) totalCount / pageSize);;         this.totalCount = totalCount;         this.list = list;     }      public int getCurrPage() {         return currPage;     }      public Pager setCurrPage(int currPage) {         this.currPage = currPage;         return this;     }      public int getPageSize() {         return pageSize;     }      public Pager setPageSize(int pageSize) {         this.pageSize = pageSize;         return this;     }      public int getTotalPage() {         return totalPage;     }      public Pager setTotalPage(int totalPage) {         this.totalPage = totalPage;         return this;     }      public int getTotalCount() {         return totalCount;     }      public Pager setTotalCount(int totalCount) {         this.totalCount = totalCount;         return this;     }      public List<T> getList() {         return list;     }      public Pager setList(List<T> list) {         this.list = list;         return this;     } }

IdRequest公共请求类

public class IdRequest extends BaseDTO {      private Long id;      public Long getId() {         return id;     }      public IdRequest setId(Long id) {         this.id = id;         return this;     } }

2.3、编写代码生成器

前两部分主要介绍的是如何获取对应的表结构,以及代码器运行之前的准备工作。

其实代码生成器,很简单,其实就是一个main方法,没有想象中的那么复杂。

处理思路也很简单,过程如下:

1、定义基本变量,例如包名路径、模块名、表名、转换后的实体类、以及数据库连接配置,我们可以将其写入配置文件

2、读取配置文件,封装对应的模板中定义的变量

3、根据对应的模板文件和变量,生成对应的java文件

2.3.1、创建配置文件,定义变量

小编我用的是application.properties配置文件来定义变量,这个没啥规定,你也可以自定义文件名,内容如下:

#包前缀 packageNamePre=com.example.generator #模块名称 moduleName=test #表 tableName=test_db #实体类名称 entityName=TestEntity #主键ID primaryId=id #作者 authorName=pzblog #数据库名称 databaseName=yjgj_base  #数据库服务器IP地址 ipName=127.0.0.1 #数据库服务器端口 portName=3306 #用户名 userName=root #密码 passWord=123456  #文件输出路径,支持自定义输出路径,如果为空,默认取当前工程的src/main/java路径 outUrl=

2.3.2、根据模板生成对应的java代码

首先,读取配置文件变量

public class SystemConstant {      private static Properties properties = new Properties();      static {         try {             // 加载上传文件设置参数:配置文件             properties.load(SystemConstant.class.getClassLoader().getResourceAsStream("application.properties"));         } catch (IOException e) {             e.printStackTrace();         }     }      public static final String tableName = properties.getProperty("tableName");     public static final String entityName = properties.getProperty("entityName");     public static final String packageNamePre = properties.getProperty("packageNamePre");     public static final String outUrl = properties.getProperty("outUrl");     public static final String databaseName = properties.getProperty("databaseName");     public static final String ipName = properties.getProperty("ipName");     public static final String portName = properties.getProperty("portName");     public static final String userName = properties.getProperty("userName");     public static final String passWord = properties.getProperty("passWord");     public static final String authorName = properties.getProperty("authorName");      public static final String primaryId = properties.getProperty("primaryId");      public static final String moduleName = properties.getProperty("moduleName"); }

然后,封装对应的模板中定义的变量

public class CodeService {      public void generate(Map<String, Object> templateData) {         //包前缀         String packagePreAndModuleName = getPackagePreAndModuleName(templateData);          //支持对应实体插入在前面,需要带上%s         templateData.put("entityPackageName", String.format(packagePreAndModuleName + ".entity",                 templateData.get("entityName").toString().toLowerCase()));          templateData.put("dtoPackageName", String.format(packagePreAndModuleName + ".dto",                 templateData.get("entityName").toString().toLowerCase()));          templateData.put("voPackageName", String.format(packagePreAndModuleName + ".vo",                 templateData.get("entityName").toString().toLowerCase()));          templateData.put("daoPackageName", String.format(packagePreAndModuleName + ".dao",                 templateData.get("entityName").toString().toLowerCase()));          templateData.put("mapperPackageName", packagePreAndModuleName + ".mapper");           templateData.put("servicePackageName", String.format(packagePreAndModuleName + ".service",                 templateData.get("entityName").toString().toLowerCase()));          templateData.put("serviceImplPackageName", String.format(packagePreAndModuleName + ".service.impl",                 templateData.get("entityName").toString().toLowerCase()));          templateData.put("controllerPackageName", String.format(packagePreAndModuleName + ".web",                 templateData.get("entityName").toString().toLowerCase()));          templateData.put("apiTestPackageName", String.format(packagePreAndModuleName + ".junit",                 templateData.get("entityName").toString().toLowerCase()));           templateData.put("currentTime", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));          //======================生成文件配置======================         try {             // 生成Entity             String entityName = String.format("%s", templateData.get("entityName").toString());             generateFile("entity.ftl", templateData, templateData.get("entityPackageName").toString(), entityName+".java");              // 生成dto             String dtoName = String.format("%sDTO", templateData.get("entityName").toString());             templateData.put("dtoName", dtoName);             generateFile("dto.ftl", templateData, templateData.get("dtoPackageName").toString(),                     dtoName + ".java");              // 生成VO             String voName = String.format("%sVO", templateData.get("entityName").toString());             templateData.put("voName", voName);             generateFile("vo.ftl", templateData, templateData.get("voPackageName").toString(),                     voName + ".java");              // 生成DAO             String daoName = String.format("%sDao", templateData.get("entityName").toString());             templateData.put("daoName", daoName);             generateFile("dao.ftl", templateData, templateData.get("daoPackageName").toString(),                     daoName + ".java");              // 生成Mapper             String mapperName = String.format("%sMapper", templateData.get("entityName").toString());             generateFile("mapper.ftl", templateData, templateData.get("mapperPackageName").toString(),                     mapperName+".xml");               // 生成Service             String serviceName = String.format("%sService", templateData.get("entityName").toString());             templateData.put("serviceName", serviceName);             generateFile("service.ftl", templateData, templateData.get("servicePackageName").toString(),                     serviceName + ".java");              // 生成ServiceImpl    String serviceImplName = String.format("%sServiceImpl", templateData.get("entityName").toString());    templateData.put("serviceImplName", serviceImplName);    generateFile("serviceImpl.ftl", templateData, templateData.get("serviceImplPackageName").toString(),                     serviceImplName + ".java");              // 生成Controller    String controllerName = String.format("%sController", templateData.get("entityName").toString());    templateData.put("controllerName", controllerName);    generateFile("controller.ftl", templateData, templateData.get("controllerPackageName").toString(),                     controllerName + ".java");  //   // 生成junit测试类 //            String apiTestName = String.format("%sApiTest", templateData.get("entityName").toString()); //            templateData.put("apiTestName", apiTestName); //            generateFile("test.ftl", templateData, templateData.get("apiTestPackageName").toString(), //                    apiTestName + ".java");          } catch (Exception e) {             e.printStackTrace();         }     }      /**      * 生成文件      * @param templateName 模板名称      * @param templateData 参数名      * @param packageName 包名      * @param fileName 文件名      */     public void generateFile(String templateName, Map<String, Object> templateData, String packageName, String fileName) {         templateData.put("fileName", fileName);          DaseService dbService = new DaseService(templateData);          // 获取数据库参数         if("entity.ftl".equals(templateName) || "mapper.ftl".equals(templateName)){             dbService.getAllColumns(templateData);         }         try {             // 默认生成文件的路径             FreeMakerUtil freeMakerUtil = new FreeMakerUtil();             freeMakerUtil.generateFile(templateName, templateData, packageName, fileName);         } catch (Exception e) {             e.printStackTrace();         }     }      /**      * 封装包名前缀      * @return      */     private String getPackagePreAndModuleName(Map<String, Object> templateData){         String packageNamePre = templateData.get("packageNamePre").toString();         String moduleName = templateData.get("moduleName").toString();         if(StringUtils.isNotBlank(moduleName)){             return packageNamePre + "." + moduleName;         }         return packageNamePre;     }  }

接着,获取模板文件,并生成相应的模板文件

public class FreeMakerUtil {       /**      * 根据Freemark模板,生成文件      * @param templateName:模板名      * @param root:数据原型      * @throws Exception      */     public void generateFile(String templateName, Map<String, Object> root, String packageName, String fileName) throws Exception {         FileOutputStream fos=null;         Writer out =null;         try {             // 通过一个文件输出流,就可以写到相应的文件中,此处用的是绝对路径             String entityName = (String) root.get("entityName");             String fileFullName = String.format(fileName, entityName);             packageName = String.format(packageName, entityName.toLowerCase());              String fileStylePackageName = packageName.replaceAll("\\.", "/");             File file = new File(root.get("outUrl").toString() + "/" + fileStylePackageName + "/" + fileFullName);             if (!file.getParentFile().exists()) {                 file.getParentFile().mkdirs();             }             file.createNewFile();              Template template = getTemplate(templateName);             fos = new FileOutputStream(file);             out = new OutputStreamWriter(fos);             template.process(root, out);             out.flush();         } catch (Exception e) {             e.printStackTrace();         } finally {             try {                 if (fos != null){                     fos.close();                 }                 if(out != null){                     out.close();                 }             } catch (IOException e) {                 e.printStackTrace();             }         }     }      /**      *      * 获取模板文件      *      * @param name      * @return      */     public Template getTemplate(String name) {         try {             Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);             cfg.setClassForTemplateLoading(this.getClass(), "/ftl");             Template template = cfg.getTemplate(name);             return template;         } catch (IOException e) {             e.printStackTrace();         }         return null;     } }

最后,我们编写一个main方法,看看运行之后的效果

public class GeneratorMain {      public static void main(String[] args) {         System.out.println("生成代码start......");          //获取页面或者配置文件的参数         Map<String, Object> templateData = new HashMap<String, Object>();         templateData.put("tableName", SystemConstant.tableName);         System.out.println("表名=="+ SystemConstant.tableName);          templateData.put("entityName", SystemConstant.entityName);         System.out.println("实体类名称=="+ SystemConstant.entityName);          templateData.put("packageNamePre", SystemConstant.packageNamePre);         System.out.println("包名前缀=="+ SystemConstant.packageNamePre);          //支持自定义输出路径         if(StringUtils.isNotBlank(SystemConstant.outUrl)){             templateData.put("outUrl", SystemConstant.outUrl);         } else {             String path = GeneratorMain.class.getClassLoader().getResource("").getPath() + "../../src/main/java";             templateData.put("outUrl", path);         }         System.out.println("生成文件路径为=="+ templateData.get("outUrl"));          templateData.put("authorName", SystemConstant.authorName);         System.out.println("以后代码出问题找=="+ SystemConstant.authorName);           templateData.put("databaseName", SystemConstant.databaseName);         templateData.put("ipName", SystemConstant.ipName);         templateData.put("portName", SystemConstant.portName);         templateData.put("userName", SystemConstant.userName);         templateData.put("passWord", SystemConstant.passWord);          //主键ID         templateData.put("primaryId", SystemConstant.primaryId);          //模块名称         templateData.put("moduleName", SystemConstant.moduleName);         CodeService dataService = new CodeService();          try {             //生成代码文件             dataService.generate(templateData);         } catch (Exception e) {             e.printStackTrace();         }          System.out.println("生成代码end......");     } }

结果如下:

生成的 Controller 层代码如下

/**  *  * @ClassName: TestEntityController  * @Description: 外部访问接口  * @author pzblog  * @date 2020-11-16  *  */ @RestController @RequestMapping("/testEntity") public class TestEntityController {   @Autowired  private TestEntityService testEntityService;   /**   * 分页列表查询   * @param request   */  @PostMapping(value = "/getPage")  public Pager<TestEntityVO> getPage(@RequestBody TestEntityDTO request){   return testEntityService.getPage(request);  }   /**   * 查询详情   * @param request   */  @PostMapping(value = "/getDetail")  public TestEntityVO getDetail(@RequestBody IdRequest request){   TestEntity source = testEntityService.selectById(request.getId());   if(Objects.nonNull(source)){    TestEntityVO result = new TestEntityVO();    BeanUtils.copyProperties(source, result);    return result;   }   return null;  }   /**   * 新增操作   * @param request   */  @PostMapping(value = "/save")  public void save(TestEntityDTO request){   TestEntity entity = new TestEntity();   BeanUtils.copyProperties(request, entity);   testEntityService.insert(entity);  }   /**   * 编辑操作   * @param request   */  @PostMapping(value = "/edit")  public void edit(TestEntityDTO request){   TestEntity entity = new TestEntity();   BeanUtils.copyProperties(request, entity);   testEntityService.updateById(entity);  }   /**   * 删除操作   * @param request   */  @PostMapping(value = "/delete")  public void delete(IdRequest request){   testEntityService.deleteById(request.getId());  } }

至此,一张单表的90%的基础工作量全部开发完毕!

三、总结

代码生成器,在实际的项目开发中应用非常的广,本文主要以freemaker模板引擎为基础,开发的一套全自动代码生成器,一张单表的CRUD,只需要5秒钟就可以完成!

最后多说一句,如果你是项目负责人,那么代码生成器会是一个比较好的提升项目开发效率的工具,希望能帮到各位!