专栏名称: Java极客技术
Java 人的社区,专注 Java 一百年!
51好读  ›  专栏  ›  Java极客技术

【非广告】你还在手写crud吗,看完这篇文章,绝对赚了

Java极客技术  · 公众号  ·  · 2020-11-17 07:30

正文

每天早上 七点三十 ,准时推送干货



一、介绍

我记得最早刚步入互联网行业的时候,当时按照 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(20unsigned NOT NULL COMMENT '主键ID',
  name varchar(50COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '名称',
  is_delete tinyint(4NOT 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>
  • 编写 dao.ftl 数据访问模板
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});
}
  • 编写 service.ftl 服务接口模板
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);
 }
}
  • 编写 controller.ftl 控制层模板
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());
 }
}
  • 编写 entity.ftl 实体类模板
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>
}
  • 编写 dto.ftl 实体类模板
package ${dtoPackageName};

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

/**
 * @ClassName: ${dtoName}
 * @Description: 请求实体类
 * @author ${authorName}
 * @date ${currentTime}
 *
 */

public class ${dtoName} extends BaseDTO {

}
  • 编写 vo.ftl 视图实体类模板
package ${voPackageName};

import java.io.Serializable;

/**
 * @ClassName: ${voName}
 * @Description: 返回视图实体类
 * @author ${authorName}
 * @date ${currentTime}
 *
 */

public class ${voName} implements Serializable {

 private static final long serialVersionUID = 1L;
}

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

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

  • BaseMapper ,主要负责将 dao 层的公共方法抽出来
package com.example.generator.core;

import org.apache.ibatis.annotations.Param;

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

/**
 * @author pzblog
 * @Description
 * @since 2020-11-11
 */

public interface BaseMapper<T{

    /**
     * 批量插入
     * @param list
     * @return
     */

    int insertList(@Param("list") List 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
     */

    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
     */

    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)






请到「今天看啥」查看全文