Not long after I first joined the company, I wrote a code generator overnight, and the speed of project development exploded instantly!

Posted by dp777 on Thu, 03 Feb 2022 20:13:35 +0100

1, Introduction

Recently, a new team has just joined. Before getting familiar with the business, Party A's father asked that the project be developed and launched within 2 months!

I wanted to postpone the delivery for one month, but Party A's father didn't agree, so I had to rush the duck to the shelf!

Then, according to the business requirements, more than 30 tables are designed. If the more than 30 tables are all handwritten crud by developers, the time required for development will be greatly prolonged, and may even directly affect the delivery time!

So I thought, can I get all crud with one click through the code generator?

The original plan was to use mybatis plus, but the generated code, according to the existing framework standards, many codes also need to be changed by themselves. In some places, it is not as comfortable as writing by yourself, so I decided to write a set of code generator by hand!

Many novices will think that code generator is a profound thing. In fact, it's not profound at all. When you finish reading this article, you will fully master the logic of the code generator, and you can even make in-depth customization according to your own project situation.

No more nonsense, just put it on the code!

2, Realization idea

Next, I will take the SpringBoot project as an example. The data persistence operation adopts Mybatis and the database adopts Mysql. I will write a code generator that automatically generates basic functions such as addition, deletion, modification and query, including controller, service, dao, entity, dto, vo and other information.

The realization idea is as follows:

  • Step 1: get the table field name, type, table comment and other information
  • Step 2: write the corresponding template based on freemaker template engine
  • Step 3: generate the corresponding java code according to the corresponding template

2.1. Get table structure

First, we create a test_db table, the script is as follows:

CREATE TABLE test_db (
  id bigint(20) unsigned NOT NULL COMMENT 'Primary key ID',
  name varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'name',
  is_delete tinyint(4) NOT NULL DEFAULT '0' COMMENT 'Delete 1: deleted; 0: not deleted',
  create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time',
  update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update time',
  PRIMARY KEY (id),
  KEY idx_create_time (create_time) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Test table';

After the table is created, it is based on test_db table, we query the corresponding table result field name, type and comment information. These information collection will be used for subsequent code generator!

# Get corresponding table structure
SELECT column_name, data_type, column_comment FROM information_schema.columns WHERE table_schema = 'yjgj_base' AND table_name = 'test_db'

At the same time, get the notes of the corresponding table, which can be used to generate notes!

# Get corresponding table comments
SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = 'yjgj_base' AND table_name = 'test_db'

2.2. Template preparation

  • Write mapper template, covering information such as adding, modifying, deleting and querying
<?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>

	<!--Batch insert-->
	<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>

	<!--Add on demand-->
	<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>

	<!-- Modify as needed-->
	<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>

	<!-- Batch modification on demand-->
	<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-->
	<delete id="deleteByPrimaryKey" parameterType="java.lang.Long">
		delete from ${tableName}
		where ${primaryId} = ${r"#{" + "${primaryId}" + r",jdbcType=BIGINT}"}
	</delete>

	<!-- Query details -->
	<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>

	<!-- On demand query -->
	<select id="selectByPrimaryKeySelective" resultMap="BaseResultMap" parameterType="${entityPackageName}.${entityName}">
		select
		<include refid="Base_Column_List"/>
		from ${tableName}
	</select>

	<!-- Batch query-->
	<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>

	<!-- Query by criteria -->
	<select id="selectByMap" resultMap="BaseResultMap" parameterType="java.util.Map">
		select
		<include refid="Base_Column_List"/>
		from ${tableName}
	</select>

	<!-- query ${entityName}the sum -->
	<select id="countPage" resultType="int" parameterType="${dtoPackageName}.${dtoName}">
		select count(${primaryId})
		from ${tableName}
	</select>

	<!-- query ${entityName}list -->
	<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>
  • Write dao data access template
package ${daoPackageName};

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

/**
*
* @ClassName: ${daoName}
* @Description: Data access interface
* @author ${authorName}
* @date ${currentTime}
*
*/
public interface ${daoName} extends BaseMapper<${entityName}>{

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

	List<${entityName}> selectPage(${dtoName} ${dtoName?uncap_first});
}
  • Write service interface template
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}Service access interface
 * @author ${authorName}
 * @date ${currentTime}
 *
 */
public interface ${serviceName} extends BaseService<${entityName}> {

	/**
	 * Paging list query
	 * @param request
	 */
	Pager<${voName}> getPage(${dtoName} request);
}
  • Write serviceImpl service implementation class template
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);

	/**
	 * Paging list query
	 * @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);
	}
}
  • Write controller control layer template
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: External access interface
 * @author ${authorName}
 * @date ${currentTime}
 *
 */
@RestController
@RequestMapping("/${entityName?uncap_first}")
public class ${controllerName} {

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

	/**
	 * Paging list query
	 * @param request
	 */
	@PostMapping(value = "/getPage")
	public Pager<${voName}> getPage(@RequestBody ${dtoName} request){
		return ${serviceName?uncap_first}.getPage(request);
	}

	/**
	 * Query details
	 * @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;
	}

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

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

	/**
	 * Delete operation
	 * @param request
	 */
	@PostMapping(value = "/delete")
	public void delete(IdRequest request){
		${serviceName?uncap_first}.deleteById(request.getId());
	}
}
  • Write entity class template
package ${entityPackageName};

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

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

	private static final long serialVersionUID = 1L;
	
	<#--Attribute traversal -- >
	<#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>

	<#--Property get | set method -- >
	<#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>
}
  • Write dto entity class template
package ${dtoPackageName};

import com.example.generator.core.BaseDTO;
import java.io.Serializable;

/**
 * @ClassName: ${dtoName}
 * @Description: Request entity class
 * @author ${authorName}
 * @date ${currentTime}
 *
 */
public class ${dtoName} extends BaseDTO {

}
  • Write vo view entity class template
package ${voPackageName};

import java.io.Serializable;

/**
 * @ClassName: ${voName}
 * @Description: Return view entity class
 * @author ${authorName}
 * @date ${currentTime}
 *
 */
public class ${voName} implements Serializable {

	private static final long serialVersionUID = 1L;
}

Maybe careful netizens have seen that we use BaseMapper, BaseService, BaseServiceImpl and other service classes in the template.

The reason for these three classes is that in the template, we have a large number of the same method names, including similar logic. Except for the different meaning of the entity class, the others are the same. Therefore, we can draw these services into a common part with the help of generic classes.

  • BaseMapper is mainly responsible for extracting the public methods of dao layer
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> {

    /**
     * Batch insert
     * @param list
     * @return
     */
    int insertList(@Param("list") List<T> list);

    /**
     * Insert a record as needed
     * @param entity
     * @return
     */
    int insertPrimaryKeySelective(T entity);

    /**
     * Modify a record as needed (through the primary key ID)
     * @return
     */
    int updatePrimaryKeySelective(T entity);

    /**
     * Batch modify records as needed (via primary key ID)
     * @param list
     * @return
     */
    int updateBatchByIds(@Param("list") List<T> list);

    /**
     * Delete by ID
     * @param id Primary key ID
     * @return
     */
    int deleteByPrimaryKey(Serializable id);

    /**
     * Query by ID
     * @param id Primary key ID
     * @return
     */
    T selectByPrimaryKey(Serializable id);

    /**
     * On demand query
     * @param entity
     * @return
     */
    List<T> selectByPrimaryKeySelective(T entity);

    /**
     * Batch query
     * @param ids Primary key ID collection
     * @return
     */
    List<T> selectByIds(@Param("ids") List<? extends Serializable> ids);

    /**
     * Query (based on columnMap criteria)
     * @param columnMap Table field map object
     * @return
     */
    List<T> selectByMap(Map<String, Object> columnMap);
}
  • BaseService is mainly responsible for extracting the public methods of the service layer
package com.example.generator.core;

import java.io.Serializable;
import java.util.List;
import java.util.Map;

/**
 * @author pzblog
 * @Description Service class
 * @since 2020-11-11
 */
public interface BaseService<T> {

    /**
     * newly added
     * @param entity
     * @return boolean
     */
    boolean insert(T entity);

    /**
     * Batch add
     * @param list
     * @return boolean
     */
    boolean insertList(List<T> list);

    /**
     * Modify records through ID (if you want to update all, just ensure that the fields are not NULL)
     * @param entity
     * @return boolean
     */
    boolean updateById(T entity);

    /**
     * Batch modify records through ID (if you want to update all, just ensure that the fields are not NULL)
     * @param list
     * @return boolean
     */
    boolean updateBatchByIds(List<T> list);

    /**
     * Delete by ID
     * @param id Primary key ID
     * @return boolean
     */
    boolean deleteById(Serializable id);

    /**
     * Query by ID
     * @param id Primary key ID
     * @return
     */
    T selectById(Serializable id);

    /**
     * On demand query
     * @param entity
     * @return
     */
    List<T> selectByPrimaryKeySelective(T entity);

    /**
     * Batch query
     * @param ids
     * @return
     */
    List<T> selectByIds(List<? extends Serializable> ids);

    /**
     * Query by criteria
     * @param columnMap
     * @return
     */
    List<T> selectByMap(Map<String, Object> columnMap);

}
  • BaseServiceImpl, public method implementation class of service layer
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 Implementation class (generic description: M is mapper object, T is entity)
 * @since 2020-11-11
 */
public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements BaseService<T>{

    @Autowired
    protected M baseMapper;

    /**
     * newly added
     * @param entity
     * @return boolean
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean insert(T entity){
        return returnBool(baseMapper.insertPrimaryKeySelective(entity));
    }

    /**
     * Batch add
     * @param list
     * @return boolean
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean insertList(List<T> list){
        return returnBool(baseMapper.insertList(list));
    }

    /**
     * Modify records through ID (if you want to update all, just ensure that the fields are not NULL)
     * @param entity
     * @return boolean
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean updateById(T entity){
        return returnBool(baseMapper.updatePrimaryKeySelective(entity));
    }

    /**
     * Batch modify records through ID (if you want to update all, just ensure that the fields are not NULL)
     * @param list
     * @return boolean
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean updateBatchByIds(List<T> list){
        return returnBool(baseMapper.updateBatchByIds(list));
    }

    /**
     * Delete by ID
     * @param id Primary key ID
     * @return boolean
     */
    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean deleteById(Serializable id){
        return returnBool(baseMapper.deleteByPrimaryKey(id));
    }

    /**
     * Query by ID
     * @param id Primary key ID
     * @return
     */
    @Override
    public T selectById(Serializable id){
        return baseMapper.selectByPrimaryKey(id);
    }

    /**
     * On demand query
     * @param entity
     * @return
     */
    @Override
    public List<T> selectByPrimaryKeySelective(T entity){
        return baseMapper.selectByPrimaryKeySelective(entity);
    }

    /**
     * Batch query
     * @param ids
     * @return
     */
    @Override
    public List<T> selectByIds(List<? extends Serializable> ids){
        return baseMapper.selectByIds(ids);
    }

    /**
     * Query by criteria
     * @param columnMap
     * @return
     */
    @Override
    public List<T> selectByMap(Map<String, Object> columnMap){
        return baseMapper.selectByMap(columnMap);
    }

    /**
     * Judge whether the database operation is successful
     * @param result Number of affected items returned by database operation
     * @return boolean
     */
    protected boolean returnBool(Integer result) {
        return null != result && result >= 1;
    }

}

Here, other classes are also encapsulated, such as dto public class BaseDTO, paging class Pager, and id request class IdRequest.

  • BaseDTO public class
public class BaseDTO implements Serializable {

    /**
     * Request token
     */
    private String token;

    /**
     * Current number of pages
     */
    private Integer currPage = 1;

    /**
     * Records per page
     */
    private Integer pageSize = 20;

    /**
     * Paging parameters (lines)
     */
    private Integer start;

    /**
     * Paging parameters (number of rows)
     */
    private Integer end;

    /**
     * Login ID
     */
    private String loginUserId;

    /**
     * Login name
     */
    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 paging class
public class Pager<T extends Serializable> implements Serializable {

    private static final long serialVersionUID = -6557244954523041805L;

    /**
     * Current number of pages
     */
    private int currPage;

    /**
     * Records per page
     */
    private int pageSize;

    /**
     * PageCount 
     */
    private int totalPage;

    /**
     * Total records
     */
    private int totalCount;

    /**
     * List data
     */
    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 request class
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. Code generator

The first two parts mainly introduce how to obtain the corresponding table structure and the preparation before the coder runs.

In fact, the code generator is very simple. In fact, it is a main method, which is not as complex as expected.

The processing idea is also very simple, and the process is as follows:

  • 1. Define basic variables, such as package name, path, module name, table name, converted entity class, and database connection configuration, which can be written into the configuration file
  • 2. Read the configuration file and encapsulate the variables defined in the corresponding template
  • 3. Generate corresponding java files according to corresponding template files and variables
2.3.1. Create a configuration file and define variables

I use application Properties configuration file to define variables. There are no regulations. You can also customize the file name, as follows:

#Package prefix
packageNamePre=com.example.generator
#Module name
moduleName=test
#surface
tableName=test_db
#Entity class name
entityName=TestEntity
#Primary key ID
primaryId=id
#author
authorName=pzblog
#Database name
databaseName=yjgj_base

#Database server IP address
ipName=127.0.0.1
#Database server port
portName=3306
#user name
userName=root
#password
passWord=123456

#The file output path supports user-defined output paths. If it is empty, the src/main/java path of the current project is taken by default
outUrl=
2.3.2. Generate corresponding java code according to the template
  • First, read the configuration file variables
public class SystemConstant {

    private static Properties properties = new Properties();

    static {
        try {
            // Loading upload file setting parameters: configuration file
            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");
}
  • Then, encapsulate the variables defined in the corresponding template
public class CodeService {

    public void generate(Map<String, Object> templateData) {
        //Package prefix
        String packagePreAndModuleName = getPackagePreAndModuleName(templateData);

        //The corresponding entity can be inserted in the front. You need to bring% 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()));

        //======================Generate file configuration======================
        try {
            // Generate Entity
            String entityName = String.format("%s", templateData.get("entityName").toString());
            generateFile("entity.ftl", templateData, templateData.get("entityPackageName").toString(), entityName+".java");

            // Generate dto
            String dtoName = String.format("%sDTO", templateData.get("entityName").toString());
            templateData.put("dtoName", dtoName);
            generateFile("dto.ftl", templateData, templateData.get("dtoPackageName").toString(),
                    dtoName + ".java");

            // Generate VO
            String voName = String.format("%sVO", templateData.get("entityName").toString());
            templateData.put("voName", voName);
            generateFile("vo.ftl", templateData, templateData.get("voPackageName").toString(),
                    voName + ".java");

            // Generate DAO
            String daoName = String.format("%sDao", templateData.get("entityName").toString());
            templateData.put("daoName", daoName);
            generateFile("dao.ftl", templateData, templateData.get("daoPackageName").toString(),
                    daoName + ".java");

            // Generate Mapper
            String mapperName = String.format("%sMapper", templateData.get("entityName").toString());
            generateFile("mapper.ftl", templateData, templateData.get("mapperPackageName").toString(),
                    mapperName+".xml");


            // Generate Service
            String serviceName = String.format("%sService", templateData.get("entityName").toString());
            templateData.put("serviceName", serviceName);
            generateFile("service.ftl", templateData, templateData.get("servicePackageName").toString(),
                    serviceName + ".java");

            // Generate ServiceImpl
			String serviceImplName = String.format("%sServiceImpl", templateData.get("entityName").toString());
			templateData.put("serviceImplName", serviceImplName);
			generateFile("serviceImpl.ftl", templateData, templateData.get("serviceImplPackageName").toString(),
                    serviceImplName + ".java");

            // Generate Controller
			String controllerName = String.format("%sController", templateData.get("entityName").toString());
			templateData.put("controllerName", controllerName);
			generateFile("controller.ftl", templateData, templateData.get("controllerPackageName").toString(),
                    controllerName + ".java");

//			//Generate junit test class
//            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();
        }
    }

    /**
     * Generate file
     * @param templateName Template name
     * @param templateData Parameter name
     * @param packageName Package name
     * @param fileName file name
     */
    public void generateFile(String templateName, Map<String, Object> templateData, String packageName, String fileName) {
        templateData.put("fileName", fileName);

        DaseService dbService = new DaseService(templateData);

        // Get database parameters
        if("entity.ftl".equals(templateName) || "mapper.ftl".equals(templateName)){
            dbService.getAllColumns(templateData);
        }
        try {
            // The path to the default build file
            FreeMakerUtil freeMakerUtil = new FreeMakerUtil();
            freeMakerUtil.generateFile(templateName, templateData, packageName, fileName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Package name prefix
     * @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;
    }

}
  • Then, obtain the template file and generate the corresponding template file
public class FreeMakerUtil {


    /**
     * Generate files according to Freemark template
     * @param templateName:Template name
     * @param root: Data prototype
     * @throws Exception
     */
    public void generateFile(String templateName, Map<String, Object> root, String packageName, String fileName) throws Exception {
        FileOutputStream fos=null;
        Writer out =null;
        try {
            // Through a file output stream, you can write to the corresponding file. Here, the absolute path is used
            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();
            }
        }
    }

    /**
     *
     * Get template file
     *
     * @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;
    }
}
  • Finally, we write a main method to see the effect after running
public class GeneratorMain {

    public static void main(String[] args) {
        System.out.println("Generate code start......");

        //Get the parameters of the page or configuration file
        Map<String, Object> templateData = new HashMap<String, Object>();
        templateData.put("tableName", SystemConstant.tableName);
        System.out.println("Table name=="+ SystemConstant.tableName);

        templateData.put("entityName", SystemConstant.entityName);
        System.out.println("Entity class name=="+ SystemConstant.entityName);

        templateData.put("packageNamePre", SystemConstant.packageNamePre);
        System.out.println("Package name prefix=="+ SystemConstant.packageNamePre);

        //Support custom output path
        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("The path of the generated file is=="+ templateData.get("outUrl"));

        templateData.put("authorName", SystemConstant.authorName);
        System.out.println("Find out if there is a problem with the code in the future=="+ 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);

        //Primary key ID
        templateData.put("primaryId", SystemConstant.primaryId);

        //Module name
        templateData.put("moduleName", SystemConstant.moduleName);
        CodeService dataService = new CodeService();

        try {
            //Generate code file
            dataService.generate(templateData);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("Generate code end......");
    }
}

The results are as follows:

  • The generated Controller layer code is as follows
/**
 *
 * @ClassName: TestEntityController
 * @Description: External access interface
 * @author pzblog
 * @date 2020-11-16
 *
 */
@RestController
@RequestMapping("/testEntity")
public class TestEntityController {

	@Autowired
	private TestEntityService testEntityService;

	/**
	 * Paging list query
	 * @param request
	 */
	@PostMapping(value = "/getPage")
	public Pager<TestEntityVO> getPage(@RequestBody TestEntityDTO request){
		return testEntityService.getPage(request);
	}

	/**
	 * Query details
	 * @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;
	}

	/**
	 * Add operation
	 * @param request
	 */
	@PostMapping(value = "/save")
	public void save(TestEntityDTO request){
		TestEntity entity = new TestEntity();
		BeanUtils.copyProperties(request, entity);
		testEntityService.insert(entity);
	}

	/**
	 * Edit operation
	 * @param request
	 */
	@PostMapping(value = "/edit")
	public void edit(TestEntityDTO request){
		TestEntity entity = new TestEntity();
		BeanUtils.copyProperties(request, entity);
		testEntityService.updateById(entity);
	}

	/**
	 * Delete operation
	 * @param request
	 */
	@PostMapping(value = "/delete")
	public void delete(IdRequest request){
		testEntityService.deleteById(request.getId());
	}
}

So far, 90% of the basic workload of a single table has been developed!

3, Summary

Code generator is widely used in actual project development. Based on freemaker template engine, this paper develops a set of automatic code generator, CRUD of a single table, which can be completed in only 5 seconds!

Finally, if you are the core development of the project, mastering the rules of code generator will help to improve the efficiency of project development!

If you want to get source code, pay attention to the official account below and reply to cccc4.