• 刚入职没多久,连夜手写了一个代码生成器,项目开发速度瞬间屌炸了!


    一、简介

    最近刚入职一个新团队,还没来得及熟悉业务,甲方爸爸就要求项目要在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】即可获取!

  • 相关阅读:
    微软Enterprise Library 4.0将支持依赖注入
    javascript cookies 存、取、删除实例
    动态调用 WebService(转)
    IE缓存是什么?cookies是什么?
    序列化
    PKI
    ASP.NET的(HttpModule,HttpHandler)(转)
    PKI基础 二.PKI基础5.数字证书及应用(转,一个加密解密的全过程)
    AOP技术基础(转)
    getChildByName()与getChildAt()效率比较
  • 原文地址:https://www.cnblogs.com/dxflqm/p/15855081.html
Copyright © 2020-2023  润新知