一、介绍
我记得最早刚步入互联网行业的时候,当时按照 MVC 的思想和模型,每次开发新功能,会依次编写
dao、service、controller
相关服务类,包括对应的
dto、entity、vo
等等实体类,如果有多张单表,也会重复的编写相似的代码,现在回想起来,
感觉当时自己好像处于石器时代
!
实际上,当仔细的总结一下,对于任何一张单表的操作,基本都是围绕
增(Create )、删(Delete )、改(Update )、查(Retrieve )四个方向
进行数据操作,简称 CRUD!
他们除了表名和存储空间不一样,基本的 CRUD 思路基本都是一样的。
为了解决这些重复劳动的痛点,业界逐渐开源了一批代码生成器,目的也很简单,就是
为了减少手工操作的繁琐,集中精力在业务开发上,提升开发效率
。
而今天,我们所要介绍的也是
代码生成器
,很多初学者可能觉得代码生成器很高深。代码生成器其实是一个很简单的东西,一点都不高深。
当你看完本文的时候,你会完全掌握代码生成器的逻辑,甚至可以根据自己的项目情况,进行深度定制
。
二、实现思路
下面我们就以
SpringBoot
项目为例,数据持久化操作采用
Mybatis
,数据库采用
Mysql
,编写一个自动生成增、删、改、查等基础功能的代码生成器,内容包括
controller
、
service
、
dao
、
entity
、
dto
、
vo
等信息。
实现思路如下:
-
-
第二步:基于 freemarker 模板引擎,编写相应的模板
-
第三步:根据对应的模板,生成相应的 java 代码
2.1、获取表结构
首先我们创建一张
test_db
表,脚本如下:
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.ftl
模板,涵盖新增、修改、删除、查询等信息
mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="${daoPackageName}.${daoName}">
<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>
<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>
<select id="countPage" resultType="int" parameterType="${dtoPackageName}.${dtoName}">
select count(${primaryId})
from ${tableName}
select>
<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>
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{
int countPage(${dtoName} ${dtoName?uncap_first});
List selectPage(${dtoName} ${dtoName?uncap_first});
}
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 {
/**
* 分页列表查询
* @param request
*/
Pager getPage(${dtoName} request);
}
-
编写
serviceImpl.ftl
服务实现类模板
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 implements ${serviceName} {
private static final Logger log = LoggerFactory.getLogger(${serviceImplName}.class);
/**
* 分页列表查询
* @param request
*/
public Pager getPage(${dtoName} request) {
List resultList = new
ArrayList();
int count = super.baseMapper.countPage(request);
List 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);
}
}
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 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());
}
}
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;
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>
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>
}
package ${dtoPackageName};
import com.example.generator.core.BaseDTO;
import java.io.Serializable;
/**
* @ClassName: ${dtoName}
* @Description: 请求实体类
* @author ${authorName}
* @date ${currentTime}
*
*/
public class ${dtoName} extends BaseDTO {
}
package ${voPackageName};
import java.io.Serializable;
/**
* @ClassName: ${voName}
* @Description: 返回视图实体类
* @author ${authorName}
* @date ${currentTime}
*
*/
public class ${voName} implements Serializable {
private static final long serialVersionUID = 1L;
}
可能细心的网友已经看到了,在模板中我们用到了
BaseMapper
、
BaseService
、
BaseServiceImpl
等等服务类。
之所以有这三个类,是因为在模板中,我们有大量的相同的方法名包括逻辑也相似,除了所在实体类不一样以外,其他都一样,因此我们可以借助泛型类来将这些服务抽成公共的部分。
-
BaseMapper
,主要负责将
dao
层的公共方法抽出来
package com.example.generator.core;
import org.apache.ibatis.annotations.Param;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
/**
* @author pzblog
* @Description
* @since 2020-11-11
*/
public interface BaseMapper<T> {
/**
* 批量插入
* @param list
* @return
*/
int insertList(@Param("list") List list);
/**
* 按需插入一条记录
* @param entity
* @return
*/
int insertPrimaryKeySelective(T entity);
/**
* 按需修改一条记录(通过主键ID)
* @return
*/
int updatePrimaryKeySelective(T entity);
/**
* 批量按需修改记录(通过主键ID)
* @param list
* @return
*/
int updateBatchByIds(@Param("list") List list);
/**
* 根据ID删除
* @param id 主键ID
* @return
*/
int deleteByPrimaryKey(Serializable id);
/**
* 根据ID查询
* @param id 主键ID
* @return
*/
T selectByPrimaryKey(Serializable id);
/**
* 按需查询
* @param entity
* @return
*/
List selectByPrimaryKeySelective(T entity);
/**
* 批量查询
* @param ids 主键ID集合
* @return
*/
List selectByIds(@Param("ids") List extends Serializable> ids);
/**
* 查询(根据 columnMap 条件)
* @param columnMap 表字段 map 对象
* @return
*/
List selectByMap(Map 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 list);
/**
* 通过ID修改记录(如果想全部更新,只需保证字段都不为NULL)
* @param entity
* @return boolean
*/
boolean updateById(T entity);
/**
* 通过ID批量修改记录(如果想全部更新,只需保证字段都不为NULL)
* @param list
* @return boolean
*/
boolean updateBatchByIds(List list);
/**
* 根据ID删除
* @param id 主键ID
* @return boolean
*/
boolean deleteById(Serializable id);
/**
* 根据ID查询
* @param id 主键ID
* @return
*/
T selectById(Serializable id);
/**
* 按需查询
* @param entity
* @return
*/
List selectByPrimaryKeySelective(T entity);
/**
* 批量查询
* @param ids
* @return
*/
List selectByIds(List extends Serializable> ids);
/**
* 根据条件查询
* @param columnMap
* @return
*/
List selectByMap(Map columnMap)