剛入職沒多久,連夜手寫了一個程式碼生成器,專案開發速度瞬間屌炸了!

程式設計師志哥發表於2022-02-03

一、簡介

最近剛入職一個新團隊,還沒來得及熟悉業務,甲方爸爸就要求專案要在2個月內完成開發並上線!

本想著往後推遲1個月在交付,但是甲方爸爸不同意,只能趕鴨子上架了!

然後根據業務需求,設計出了大概30多張表,如果這30多張表,全靠開發人員手寫 crud,開發所需的時間肯定會大大的延長,甚至可能直接會影響交付時間!

於是就想著,能不能通過程式碼生成器一鍵搞定全部的 crud?

本來計劃是用mybatis-plus的,但是生成的程式碼,根據現有的框架標準,很多程式碼也需要自己改,有些地方還不如自己手寫用的舒服,因此就決定手寫一套程式碼生成器!

很多新手會覺得程式碼生成器很個高深的東西。其實不然,一點都不高深,當你看完本文的時候,你會完全掌握程式碼生成器的邏輯,甚至可以根據自己的專案情況,進行深度定製。

廢話也不多說了,直接程式碼擼上!

二、實現思路

下面我就以SpringBoot專案為例,資料持久化操作採用Mybatis,資料庫採用Mysql,編寫一個自動生成增、刪、改、查等基礎功能的程式碼生成器,內容包括controllerservicedaoentitydtovo等資訊。

實現思路如下:

  • 第一步:獲取表欄位名稱、型別、表註釋等資訊
  • 第二步:基於 freemarker 模板引擎,編寫相應的模板
  • 第三步:根據對應的模板,生成相應的 java 程式碼

2.1、獲取表結構

首先我們建立一張test_db表,指令碼如下:

CREATE TABLE test_db (
  id bigint(20) unsigned NOT NULL COMMENT '主鍵ID',
  name varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名稱',
  is_delete tinyint(4) NOT NULL DEFAULT '0' COMMENT '是否刪除 1:已刪除;0:未刪除',
  create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
  update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
  PRIMARY KEY (id),
  KEY idx_create_time (create_time) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='測試表';

表建立完成之後,基於test_db表,我們查詢對應的表結果欄位名稱、型別、備註資訊,這些資訊收集將用於後續進行程式碼生成器所使用

# 獲取對應表結構
SELECT column_name, data_type, column_comment FROM information_schema.columns WHERE table_schema = 'yjgj_base' AND table_name = 'test_db'

同時,獲取對應表註釋,用於生成備註資訊

# 獲取對應表註釋
SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = 'yjgj_base' AND table_name = 'test_db'

2.2、編寫模板

  • 編寫mapper模板,涵蓋新增、修改、刪除、查詢等資訊
<?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="${daoPackageName}.${daoName}">

	<!--BaseResultMap-->
	<resultMap id="BaseResultMap" type="${entityPackageName}.${entityName}">
        <#list columns as pro>
            <#if pro.proName == primaryId>
				<id column="${primaryId}" property="${primaryId}" jdbcType="${pro.fieldType}"/>
            <#else>
				<result column="${pro.fieldName}" property="${pro.proName}" jdbcType="${pro.fieldType}"/>
            </#if>
        </#list>
	</resultMap>

	<!--Base_Column_List-->
	<sql id="Base_Column_List">
        <#list columns as pro>
            <#if pro_index == 0>${pro.fieldName}<#else>,${pro.fieldName}</#if>
        </#list>
	</sql>

	<!--批量插入-->
	<insert id="insertList" parameterType="java.util.List">
		insert into ${tableName} (
        <#list columns as pro>
            <#if pro_index == 0>${pro.fieldName},<#elseif pro_index == 1>${pro.fieldName}<#else>,${pro.fieldName}</#if>
        </#list>
		)
		values
		<foreach collection ="list" item="obj" separator =",">
			<trim prefix=" (" suffix=")" suffixOverrides=",">
                <#list columns as pro>
                    ${r"#{obj." + pro.proName + r"}"},
                </#list>
			</trim>
		</foreach >
	</insert>

	<!--按需新增-->
	<insert id="insertPrimaryKeySelective" parameterType="${entityPackageName}.${entityName}">
		insert into ${tableName}
		<trim prefix="(" suffix=")" suffixOverrides=",">
            <#list columns as pro>
				<if test="${pro.proName} != null">
                    ${pro.fieldName},
				</if>
            </#list>
		</trim>
		<trim prefix="values (" suffix=")" suffixOverrides=",">
            <#list columns as pro>
				<if test="${pro.proName} != null">
                    ${r"#{" + pro.proName + r",jdbcType=" + pro.fieldType +r"}"},
				</if>
            </#list>
		</trim>
	</insert>

	<!-- 按需修改-->
	<update id="updatePrimaryKeySelective" parameterType="${entityPackageName}.${entityName}">
		update ${tableName}
		<set>
            <#list columns as pro>
                <#if pro.fieldName != primaryId && pro.fieldName != primaryId>
					<if test="${pro.proName} != null">
                        ${pro.fieldName} = ${r"#{" + pro.proName + r",jdbcType=" + pro.fieldType +r"}"},
					</if>
                </#if>
            </#list>
		</set>
		where ${primaryId} = ${r"#{" + "${primaryId}" + r",jdbcType=BIGINT}"}
	</update>

	<!-- 按需批量修改-->
	<update id="updateBatchByIds" parameterType="java.util.List">
		update ${tableName}
		<trim prefix="set" suffixOverrides=",">
            <#list columns as pro>
                <#if pro.fieldName != primaryId && pro.fieldName != primaryId>
					<trim prefix="${pro.fieldName}=case" suffix="end,">
						<foreach collection="list" item="obj" index="index">
							<if test="obj.${pro.proName} != null">
								when id = ${r"#{" + "obj.id" + r"}"}
								then  ${r"#{obj." + pro.proName + r",jdbcType=" + pro.fieldType +r"}"}
							</if>
						</foreach>
					</trim>
                </#if>
            </#list>
		</trim>
		where
		<foreach collection="list" separator="or" item="obj" index="index" >
			id = ${r"#{" + "obj.id" + r"}"}
		</foreach>
	</update>

	<!-- 刪除-->
	<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
		delete from ${tableName}
		where ${primaryId} = ${r"#{" + "${primaryId}" + r",jdbcType=BIGINT}"}
	</delete>

	<!-- 查詢詳情 -->
	<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Long">
		select
		<include refid="Base_Column_List"/>
		from ${tableName}
		where ${primaryId} = ${r"#{" + "${primaryId}" + r",jdbcType=BIGINT}"}
	</select>

	<!-- 按需查詢 -->
	<select id="selectByPrimaryKeySelective" resultMap="BaseResultMap" parameterType="${entityPackageName}.${entityName}">
		select
		<include refid="Base_Column_List"/>
		from ${tableName}
	</select>

	<!-- 批量查詢-->
	<select id="selectByIds" resultMap="BaseResultMap" parameterType="java.util.List">
		select
		<include refid="Base_Column_List"/>
		from ${tableName}
		<where>
			<if test="ids != null">
				and ${primaryId} in
				<foreach item="item" index="index" collection="ids" open="(" separator="," close=")">
                    ${r"#{" + "item" + r"}"}
				</foreach>
			</if>
		</where>
	</select>

	<!-- 根據條件查詢 -->
	<select id="selectByMap" resultMap="BaseResultMap" parameterType="java.util.Map">
		select
		<include refid="Base_Column_List"/>
		from ${tableName}
	</select>

	<!-- 查詢${entityName}總和 -->
	<select id="countPage" resultType="int" parameterType="${dtoPackageName}.${dtoName}">
		select count(${primaryId})
		from ${tableName}
	</select>

	<!-- 查詢${entityName}列表 -->
	<select id="selectPage" resultMap="BaseResultMap" parameterType="${dtoPackageName}.${dtoName}">
		select
		<include refid="Base_Column_List"/>
		from ${tableName}
		limit ${r"#{" + "start,jdbcType=INTEGER" + r"}"},${r"#{" + "end,jdbcType=INTEGER" + r"}"}
	</select>

</mapper>
  • 編寫dao資料訪問模板
package ${daoPackageName};

import com.example.generator.core.BaseMapper;
import java.util.List;
import ${entityPackageName}.${entityName};
import ${dtoPackageName}.${dtoName};

/**
*
* @ClassName: ${daoName}
* @Description: 資料訪問介面
* @author ${authorName}
* @date ${currentTime}
*
*/
public interface ${daoName} extends BaseMapper<${entityName}>{

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

	List<${entityName}> selectPage(${dtoName} ${dtoName?uncap_first});
}
  • 編寫service服務介面模板
package ${servicePackageName};

import com.example.generator.core.BaseService;
import com.example.generator.common.Pager;
import ${voPackageName}.${voName};
import ${dtoPackageName}.${dtoName};
import ${entityPackageName}.${entityName};

/**
 *
 * @ClassName: ${serviceName}
 * @Description: ${entityName}業務訪問介面
 * @author ${authorName}
 * @date ${currentTime}
 *
 */
public interface ${serviceName} extends BaseService<${entityName}> {

	/**
	 * 分頁列表查詢
	 * @param request
	 */
	Pager<${voName}> getPage(${dtoName} request);
}
  • 編寫serviceImpl服務實現類别範本
package ${serviceImplPackageName};

import com.example.generator.common.Pager;
import com.example.generator.core.BaseServiceImpl;
import com.example.generator.test.service.TestEntityService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;


import ${daoPackageName}.${daoName};
import ${entityPackageName}.${entityName};
import ${dtoPackageName}.${dtoName};
import ${voPackageName}.${voName};


@Service
public class ${serviceImplName} extends BaseServiceImpl<${daoName}, ${entityName}> implements ${serviceName} {

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

	/**
	 * 分頁列表查詢
	 * @param request
	 */
	public Pager<${voName}> getPage(${dtoName} request) {
		List<${voName}> resultList = new ArrayList();
		int count = super.baseMapper.countPage(request);
		List<${entityName}> dbList = count > 0 ? super.baseMapper.selectPage(request) : new ArrayList<>();
		if(!CollectionUtils.isEmpty(dbList)){
			dbList.forEach(source->{
				${voName} target = new ${voName}();
				BeanUtils.copyProperties(source, target);
				resultList.add(target);
			});
		}
		return new Pager(request.getCurrPage(), request.getPageSize(), count, resultList);
	}
}
  • 編寫controller控制層模板
package ${controllerPackageName};

import com.example.generator.common.IdRequest;
import com.example.generator.common.Pager;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;

import ${servicePackageName}.${serviceName};
import ${entityPackageName}.${entityName};
import ${dtoPackageName}.${dtoName};
import ${voPackageName}.${voName};

/**
 *
 * @ClassName: ${controllerName}
 * @Description: 外部訪問介面
 * @author ${authorName}
 * @date ${currentTime}
 *
 */
@RestController
@RequestMapping("/${entityName?uncap_first}")
public class ${controllerName} {

	@Autowired
	private ${serviceName} ${serviceName?uncap_first};

	/**
	 * 分頁列表查詢
	 * @param request
	 */
	@PostMapping(value = "/getPage")
	public Pager<${voName}> getPage(@RequestBody ${dtoName} request){
		return ${serviceName?uncap_first}.getPage(request);
	}

	/**
	 * 查詢詳情
	 * @param request
	 */
	@PostMapping(value = "/getDetail")
	public ${voName} getDetail(@RequestBody IdRequest request){
		${entityName} source = ${serviceName?uncap_first}.selectById(request.getId());
		if(Objects.nonNull(source)){
			${voName} result = new ${voName}();
			BeanUtils.copyProperties(source, result);
			return result;
		}
		return null;
	}

	/**
	 * 新增操作
	 * @param request
	 */
	@PostMapping(value = "/save")
	public void save(${dtoName} request){
		${entityName} entity = new ${entityName}();
		BeanUtils.copyProperties(request, entity);
		${serviceName?uncap_first}.insert(entity);
	}

	/**
	 * 編輯操作
	 * @param request
	 */
	@PostMapping(value = "/edit")
	public void edit(${dtoName} request){
		${entityName} entity = new ${entityName}();
		BeanUtils.copyProperties(request, entity);
		${serviceName?uncap_first}.updateById(entity);
	}

	/**
	 * 刪除操作
	 * @param request
	 */
	@PostMapping(value = "/delete")
	public void delete(IdRequest request){
		${serviceName?uncap_first}.deleteById(request.getId());
	}
}
  • 編寫entity實體類别範本
package ${entityPackageName};

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.Date;

/**
 *
 * @ClassName: ${entityName}
 * @Description: ${tableDes!}實體類
 * @author ${authorName}
 * @date ${currentTime}
 *
 */
public class ${entityName} implements Serializable {

	private static final long serialVersionUID = 1L;
	
	<#--屬性遍歷-->
	<#list columns as pro>
	<#--<#if pro.proName != primaryId
	&& pro.proName != 'remarks'
	&& pro.proName != 'createBy'
	&& pro.proName != 'createDate'
	&& pro.proName != 'updateBy'
	&& pro.proName != 'updateDate'
	&& pro.proName != 'delFlag'
	&& pro.proName != 'currentUser'
	&& pro.proName != 'page'
	&& pro.proName != 'sqlMap'
	&& pro.proName != 'isNewRecord'
	></#if>-->
	/**
	 * ${pro.proDes!}
	 */
	private ${pro.proType} ${pro.proName};
	</#list>

	<#--屬性get||set方法-->
	<#list columns as pro>
	public ${pro.proType} get${pro.proName?cap_first}() {
		return this.${pro.proName};
	}

	public ${entityName} set${pro.proName?cap_first}(${pro.proType} ${pro.proName}) {
		this.${pro.proName} = ${pro.proName};
		return this;
	}
	</#list>
}
  • 編寫dto實體類别範本
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檢視實體類别範本
package ${voPackageName};

import java.io.Serializable;

/**
 * @ClassName: ${voName}
 * @Description: 返回檢視實體類
 * @author ${authorName}
 * @date ${currentTime}
 *
 */
public class ${voName} implements Serializable {

	private static final long serialVersionUID = 1L;
}

可能細心的網友已經看到了,在模板中我們用到了BaseMapperBaseServiceBaseServiceImpl等等服務類。

之所以有這三個類,是因為在模板中,我們有大量的相同的方法名包括邏輯也相似,除了所在實體類不一樣意以外,其他都一樣,因此我們可以藉助泛型類來將這些服務抽成公共的部分。

  • 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);

}
  • BaseServiceImplservice層的公共方法具體實現類
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秒鐘就可以完成!

最後多說一句,如果你是專案中的核心開發,那麼掌握程式碼生成器的規則,對專案開發效率的提升會有非常直觀的幫助!

如果想要獲取原始碼,關注下方公眾號,並回復【cccc4】即可獲取!

相關文章