写了个工具,让 CRUD 开发效率提升100倍,开源咯!

互联网架构师

共 30573字,需浏览 62分钟

 ·

2024-04-12 02:14

点击关注公众号:互联网架构师,后台回复 2T获取2TB学习资源!
上一篇:2T架构师学习资料干货分享

大家好,我是互联网架构师!

1缘起

最近在做一个项目,需要新建20多张表
相信大多数同学应该和我一样,都是比较讨厌创建新表这个工作的,因为每创建一张表,都要去创建实体类、创建增删改查的接口、编写增删改查的SQL代码等等,把这些事情做完,2个小时也就过去了
我就在思考啊,每一张表,就要耗费两个小时,20多张表,那就是40多个小时,一周的时间就这样过去了。
这谁受得了呀,所以我打算写一个工具,把这些代码都自动生成出来!
在连续爆肝了5个晚上之后,我做出了这款自动生成业务代码的工具!

部署后的,可直接使用:

https://utilsbox.cn/

2使用演示

下面给大家演示一下

假设我们现在创建一个商品表

首先填写表名和表的中文名称

然后我们添加商品表对应的字段,这些操作和我们日常使用的数据库工具差不多

把字段填写好了之后,点击“一键生成代码”

高潮的地方来了
DB层、业务层、甚至是controller层的代码,工具都帮我们自动生成了!
来看下代码,建表sql、增删改查sql、表对应的实体类、DB层的接口类、业务层的实体类、业务层的接口类、业务接口的实现类、业务层实体类和db层实体类的转换器,最后是controller层,这些代码全都帮我们生成好了。

这是什么概念呢

现在我们只需要把这些代码拷贝到项目里,简单设置一下类的引用,增删改查这些基本功能,就已经可以直接给前端去调用了。

很多同学可能会问,如果是现有的数据表,再来这里添加一次感觉太麻烦了
这个问题我也遇到了,所以我做了一个识别建表SQL的功能,常见的数据库工具对于已经创建好的表,都提供建表语句的复制功能,只需要把建表SQL粘贴到这个文本框里。
点击“识别”按钮,这个SQL的信息会自动识别过来,这时候只需要把表的中文名称填写上去,点击“一键生成代码”,就可以马上得到这个表的业务代码。

3通用性的思考

不知道大家觉得怎么样哈,如果只是我自己用,我觉着已经非常好了,但既然是开放出来给大家用,通用性肯定还是不够的。
因为现在看到的这一套自动生成的代码,可能只是适合我,但不同的人、不同的公司,它们项目结构的分层、代码的细节,都是不一样的
所以,我还开发了代码模版配置的功能
点击“代码模版配置”按钮,在这个弹窗里,我们可以新增、删除代码的分类。同样的,也可以新增、删除和编辑具体的代码模版。

4代码生成的原理

在说代码模版具体怎么配置之前,先给大家讲一下代码自动生成的原理
它由代码模版和动态参数组成,核心就是匹配 - 替换
首先,工具提供了很多动态参数,这些动态参数,对应的是我们填写的表名、字段名等等这些信息的原始值,或者处理过后的值。
然后,代码模版由用户自行定义,在代码的关键位置,插入动态参数,即可形成一份代码模版。
最后,工具通过匹配和替换动态参数,最终生成了代码。
理论上,不论什么语言,你都可以配置专属于你自己的代码模版。
举个简单的栗子:
我们配置了一份这样的代码模版,如下:
/**
 * $table_desc$Model模型
 * Created by 创建人 on $current_time$.
 */

public class $table_name_hump_A$Model extends ToString{
}

可以看到,模版里分别使用了$table_desc$(表中文名)、$current_time$(当前时间)、$table_name_hump_A$(表名转首字母大写驼峰) 这三个动态参数。

当我们设置 表名 = goods_order表中文名 = 商品订单 时,代码生成的结果如下所示:
/**
 * 商品订单Model模型
 * Created by 创建人 on 2023-02-05 17:12:32.
 */

public class GoodsOrderModel extends ToString{
}

动态参数部分,全部替换成了我们输入的表信息。

5代码模版参考

以下是当前工具里默认的代码模版,你可以通过参考这些模版,举一反三,配置出专属于你自己的代码模版。
工具提供了模版的导入和导出功能,如果你创建好了一份非常满意的模版,可以导出,然后发给你的同事直接使用,这样它就不用重复创建了,你也可以忽悠你的同事来创建,然后分享给你。
建表SQL模版
CREATE TABLE `$table_name$` (
  $create_table_field_list$
  PRIMARY KEY (`$primary_key$`)
ENGINE=$db_engine$ DEFAULT CHARSET=$db_encoded$;
实体类模版

主体

/**
 * $table_desc$DTO模型
 * Created by 创建人 on $current_time$.
 */

public class $table_name_hump_A$DO {
    $member_param_list$
    $get_set_method_list$
}

成员代码块 成员变量列表(member_param_list)

/** $field_comment$ */
private $field_type_java$ $field_name_hump$;

成员代码块 GetSet方法列表(get_set_method_list)

public $field_type_java$ get$field_name_hump_A$() {
    return $field_name_hump$;
}

public void set$field_name_hump_A$($field_type_java$ $field_name_hump$) {
    this.$field_name_hump$ = $field_name_hump$;
}
接口类模版(DB层)
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Map;

/**
 * $table_desc$DB接口
 * Created by 创建人 on $current_time$
 */

@Mapper
public interface $table_name_hump_A$DAO {

    /**
     * 添加
     * @param data
     * return 影响行数
     */

    int insert($table_name_hump_A$DO data);

    /**
     * 修改
     * @param data
     * return 影响行数
     */

    int update($table_name_hump_A$DO data);

    /**
     * 分页查询
     * @param param
     * return 结果列表
     */

    List<$table_name_hump_A$DO> pageQuery(Map param);

    /**
     * 查询count
     * @param param
     * return count条数
     */

    Long pageQueryCount(Map param);

    /**
     * 根据ID查询
     * @param id
     * return 结果DO对象
     */

    $table_name_hump_A$DO queryById(@Param("$primary_key_hump$") $primary_key_type_java$ $primary_key_hump$);

    /**
     * 根据ID查询(带锁)
     * @param id
     * return 结果DO对象
     */

    $table_name_hump_A$DO queryByIdLock(@Param("$primary_key_hump$") $primary_key_type_java$ $primary_key_hump$);
}
CRUD SQL模版
<?xml version="1.0" encoding="UTF-8"?>
<!-- $table_name_hump_A$Mapper.xml -->
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxx.$table_name_hump_A$DAO">

    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO $table_name$($insert_field_name_list$)
        VALUES ($insert_field_value_list$);
    </insert>

    <update id="update">
        UPDATE $table_name$ SET
            $update_field_list$
        WHERE $primary_key$ = #{$primary_key_hump$};
    </update>

    <select id="pageQuery" resultType="com.xxx.$table_name_hump_A$DO">
        SELECT $select_field_list$ FROM $table_name$
        WHERE 1=1
        $while_field_list$

        ORDER BY $primary_key$ DESC

        <if test="pageIndex != null and pageSize != null">
            LIMIT #{offset},#{rows}
        </if>
    </select>

    <select id="pageQueryCount" resultType="java.lang.Long">
        SELECT COUNT(1) as total FROM $table_name$
        WHERE 1=1
        $while_field_list$
    </select>

    <select id="queryById" resultType="com.xxx.$table_name_hump_A$DO">
        SELECT $select_field_list$ FROM $table_name$
        WHERE $primary_key$ = #{$primary_key_hump$};
    </select>

    <select id="queryByIdLock" resultType="com.xxx.$table_name_hump_A$DO">
        SELECT $select_field_list$ FROM $table_name$
        WHERE $primary_key$ = #{$primary_key_hump$} FOR UPDATE;
    </select>

</mapper>
模型转换器模版
不同层级间,可能会有不同的实体类,那么它们相互之间的数据转换,也是一个重复且浪费时间的工作,所以也可以配置一个模版。
import org.springframework.util.CollectionUtils;

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

/**
 * $table_desc$模型转换器
 * Created by 创建人 on $current_time$
 */

public class $table_name_hump_A$Converter {

    /**
     * dto转do
     * @param source
     * return do模型
     */

    public static $table_name_hump_A$DO toDo($table_name_hump_A$DTO source){
        $table_name_hump_A$DO target = new $table_name_hump_A$DO();
        $converter_source_to_target_params_list$
        return target;
    }

    /**
     * do转dto
     * @param source
     * return dto模型
     */

    public static $table_name_hump_A$DTO toDto($table_name_hump_A$DO source){
        $table_name_hump_A$DTO target = new $table_name_hump_A$DTO();
        $converter_source_to_target_params_list$
        return target;
    }

    /**
     * do list 转 dto
     * @param data
     * return list dto模型
     */

    public static List<$table_name_hump_A$DTO> toDtoList(List<$table_name_hump_A$DO> data){
        if (CollectionUtils.isEmpty(data)){
            return null;
        }
        List<$table_name_hump_A$DTO> list = new ArrayList<>();
        for ($table_name_hump_A$DO item : data){
            list.add($table_name_hump_A$Converter.toDto(item));
        }
        return list;
    }
}

成员代码块 模型转换器参数列表(converter_source_to_target_params_list)

target.set$field_name_hump_A$(source.get$field_name_hump_A$());
业务接口实现类模版
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

/**
 * $table_desc$业务实现
 * Created by 创建人 on $current_time$
 */

@Service
public class $table_name_hump_A$ServiceImpl implements $table_name_hump_A$Service {

    private static final Logger LOGGER = LoggerFactory.getLogger($table_name_hump_A$ServiceImpl.class);

    @Resource
    private $table_name_hump_A$DAO $table_name_hump$DAO;

    @Override
    public CommonResult create(JSONObject request) {
        
        // 入参转成对应类型
        CommonAssert.isNoEmptyObj(request, "请求参数不可空");
        $table_name_hump_A$DTO dto = JSON.toJavaObject(request, $table_name_hump_A$DTO.class);
  
        // 参数校验
     $biz_check_required_params$
        
        // 转成do模型
        $table_name_hump_A$DO dataDo = $table_name_hump_A$Converter.toDo(dto);

        // 落库
        int count = $table_name_hump$DAO.insert(dataDo);
        CommonAssert.isTrue(count > 0"创建失败,请重试");

        // 返回创建成功的ID数据
        return new CommonResult(dataDo.get$primary_key_hump_A$());
    }

    @Override
    public CommonResult modify(JSONObject request) {
        
        // 入参转成对应类型
        CommonAssert.isNoEmptyObj(request, "请求参数不可空");
        $table_name_hump_A$DTO dto = JSON.toJavaObject(request, $table_name_hump_A$DTO.class);
  
        // 参数校验
     $biz_check_required_params$
        
        // 转成do模型
        $table_name_hump_A$DO dataDo = $table_name_hump_A$Converter.toDo(dto);

        // 落库
        int count = $table_name_hump$DAO.update(dataDo);
        CommonAssert.greaterThanZero(count, "修改失败,请重试");

        // 返回修改成功的ID数据
        return new CommonResult(dataDo.get$primary_key_hump_A$());
    }

    @Override
    public CommonResult pageQuery(JSONObject request) {
        
        // 入参基本校验
        CommonAssert.isNoEmptyObj(request, "请求参数不可空");
        
        // 分页信息校验
        PaginationDO pagination = PaginationDO.setRequestOffsetAndRows(request);
        CommonAssert.isNoEmptyObj(pagination, "分页参数 $pageIndex、$pageSize 不可空");
        CommonAssert.isTrue(pagination.getPageSize() <= 100"单次查询条数不可超过100条");

        // 取分页列表数据
        List<$table_name_hump_A$DO> dbResult = $table_name_hump$DAO.pageQuery(request);

        // 取count数据
        long count = $table_name_hump$DAO.pageQueryCount(request);

        // 空返回
        if (CollectionUtils.isEmpty(dbResult)){
            return new CommonResult(new PaginationResult(null, request, count));
        }

        // 模型转换
        List<$table_name_hump_A$DTO> list = $table_name_hump_A$Converter.toDtoList(dbResult);

        // 返回分页结果
        return new CommonResult(new PaginationResult(list, request, count));
    }

    @Override
    public CommonResult queryById(JSONObject request) {
        
        // 取入参
        CommonAssert.isNoEmptyObj(request, "请求参数不可空");

        // 根据ID获取数据
        $primary_key_type_java$ $primary_key_hump$ = request.get$primary_key_type_java$("$primary_key_hump$");
        CommonAssert.isNoEmptyObj($primary_key_hump$, "参数错误,无法执行查询");
        $table_name_hump_A$DO dbResult = $table_name_hump$DAO.queryById($primary_key_hump$);
        
        // 未查询到结果,返回
        if (null == dbResult){
            return new CommonResult();
        }

        // 模型转换
        $table_name_hump_A$DTO result = $table_name_hump_A$Converter.toDto(dbResult);

        // 返回结果对象
        return new CommonResult(result);
    }
}

成员代码块 必填项业务校验(biz_check_required_params)

CommonAssert.$java_type_adapter_assert_method$(dto.get$field_name_hump_A$(), "$field_comment$不可空");
Controller模版
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * $table_desc$HTTP请求控制器
 * Created by 创建人 on $current_time$.
 */

@CrossOrigin
@RestController
@RequestMapping(value = "/$table_name_hump$/")
public class $table_name_hump_A$Controller {

    private static final Logger LOGGER = LoggerFactory.getLogger($table_name_hump_A$Controller.class);

    @Resource
    private $table_name_hump_A$Service $table_name_hump$Service;

    /**
     * 创建
     * @param request
     * @return
     */

    @RequestMapping(value = "create.json", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public Object create(@RequestBody JSONObject request){
        return CommonTemplate.run(LOGGER, new CommonTemplate() {
            @Override
            protected Object business() {
                return $table_name_hump$Service.create(request);
            }
        }, request);
    }

    /**
     * 修改
     * @param request
     * @return
     */

    @RequestMapping(value = "modify.json", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public Object modify(@RequestBody JSONObject request){
        return CommonTemplate.run(LOGGER, new CommonTemplate() {
            @Override
            protected Object business() {
                return $table_name_hump$Service.modify(request);
            }
        }, request);
    }

    /**
     * 分页查询
     * @param request
     * @return
     */

    @RequestMapping(value = "pageQuery.json", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public Object pageQuery(@RequestBody JSONObject request){
        return CommonTemplate.run(LOGGER, new CommonTemplate() {
            @Override
            protected Object business() {
                return $table_name_hump$Service.pageQuery(request);
            }
        }, request);
    }

    /**
     * 通过主键查询
     * @param request
     * @return
     */

    @RequestMapping(value = "queryById.json", method = {RequestMethod.GET, RequestMethod.POST})
    @ResponseBody
    public Object queryById(@RequestBody JSONObject request){
        return CommonTemplate.run(LOGGER, new CommonTemplate() {
            @Override
            protected Object business() {
                return $table_name_hump$Service.queryById(request);
            }
        }, request);
    }
}

动态参数

  • 原始表名 $table_name$

用户输入的表名,不做任何处理。
  • 表名驼峰首字母小写 $table_name_hump$
用户输入的表名,经过驼峰处理。例如输入:goods_order ,输出:goodsOrder
  • 表名驼峰首字母大写 $table_name_hump_A$
用户输入的表名,经过驼峰处理。例如输入:goods_order ,输出:GoodsOrder
  • 表中文名 $table_desc$

用户输入的表中文名,不做任何处理。

  • 数据库引擎 $db_engine$

用户输入的表数据库引擎,不做任何处理。

  • 数据库编码 $db_encoded$

用户输入的表数据库编码,不做任何处理。

  • 原始字段名 $field_name$

用户输入的表字段名,不做任何处理。

  • 字段名驼峰首字母小写 $field_name_hump$

用户输入的表字段名,经过驼峰处理。例如输入:order_number ,输出:orderNumber

  • 字段名驼峰首字母大写 $field_name_hump_A$

用户输入的表字段名,经过驼峰处理。例如输入:order_number ,输出:OrderNumber

  • 字段说明 $field_comment$

用户输入的表字段说明,不做任何处理。

  • 字段数据类型(对应DB)$field_type_db$

用户选择的表字段类型,不做任何处理。

  • 字段数据类型(对应Java)$field_type_java$

用户选择的表字段类型,会自动匹配对应的Java类型。例如输入:VARCHAR(),输出:String

  • 主键字段名 $primary_key$

用户选择的主键字段,不做任何处理。例如用户定义的主键是id,那么输出就是:id

  • 主键字段名驼峰首字母小写 $primary_key_hump$

用户选择的主键字段,经过驼峰处理。例如输入:order_number ,输出:orderNumber

  • 主键字段名驼峰首字母大写 $primary_key_hump_A$

用户选择的主键字段,经过驼峰处理。例如输入:order_number ,输出:OrderNumber

  • 主键字段数据类型(对应Java)$primary_key_type_java$

用户选择的主键字段类型,会自动匹配对应的Java类型。例如输入:VARCHAR(),输出:String

  • 插入数据sql,字段名列表 $insert_field_name_list$

例如表中有三个字段:id(主键)、goods_name、price,输出:goods_name,price 。会自动排除主键 不仅插入数据时可用,任何需要排除ID,然后使用英文逗号拼接表所有字段信息的地方,都可以使用它

  • 插入数据sql,字段值列表 $insert_field_value_list$

例如表中有三个字段:id(主键)、goods_name、price,输出:#{goodsName},#{price} 。会自动排除主键 不仅插入数据时可用,任何需要排除ID,然后使用英文逗号拼接表所有字段信息的地方,都可以使用它

  • 修改数据sql,字段名列表 $update_field_list$

例如表中有三个字段:id(主键)、goods_name、price 输出(会自动换行,会携带以下缩进,会自动排除主键):

goods_name = #{goodsName},
price = #{price}
  • 查询数据sql,字段名列表 $select_field_list$

例如表中有三个字段:id(主键)、goods_name、price,输出:id,goods_name,price 。带主键

  • whele字段条件sql,字段名列表 $where_field_list$

例如表中有三个字段:id(主键)、goods_name、price, 输出(会自动换行,会携带以下缩进):

<if test="id != null">
    AND id = #{id}
</if>
<if test="goodsName != null">
    AND goods_name = #{goodsName}
</if>
<if test="price != null">
    AND price = #{price}
</if>
  • 创建表-表字段列表 $create_table_field_list$

例如表中有三个字段:id(自增主键)、goods_name、price 输出(会自动换行,会携带以下缩进):

`id` INT(11) NOT NULL AUTO_INCREMENT,
`goods_name` VARCHAR(128) NOT NULL DEFAULT 'NULL',
`price` DECIMAL(10,2) NOT NULL DEFAULT 'NULL'
  • 当前时间 $current_time$

自动获取当前时间,格式:yyyy-MM-DD hh:mm:ss,示例值:2023-02-05 17:31:18

  • 自动根据数据类型,匹配断言方法 $java_type_adapter_assert_method$

对于Java数据类型等于String的字段,使用isNoBlankStr方法,其他的类型都使用isNoEmptyObj方法 这是一个定制化很高的动态参数,可忽略它。

动态代码块

动态代码块由用户自行定义,代码块中也可以设置动态参数,目前仅提供4种明确的动态代码块

  • 成员变量列表 $member_param_list$

例如表中有三个字段:id(主键)、goods_name、price

假设用户定义了如下代码块内容:

/** $field_comment$ */
private $field_type_java$ $field_name_hump$;

输出(会自动根据字段数量,累加输出自定义代码块内容):

/** id */
private Integer id;
/** 商品名称 */
private String goodsName;
/** 商品价格 */
private String price;
  • GetSet方法列表 $get_set_method_list$

例如表中有三个字段:id(主键)、goods_name、price

假设用户定义了如下代码块内容:

public $field_type_java$ get$field_name_hump_A$() {
    return $field_name_hump$;
}

public void set$field_name_hump_A$($field_type_java$ $field_name_hump$) {
    this.$field_name_hump$ = $field_name_hump$;
}

输出(会自动根据字段数量,累加输出自定义代码块内容):

public Integer getId() {
    return id;
}

public void setId(Integer id) {
    this.id = id;
}

public String getGoodsName() {
    return goodsName;
}

public void setGoodsName(String goodsName) {
    this.goodsName = goodsName;
}

public String getPrice() {
    return price;
}

public void setPrice(String price) {
    this.price = price;
}
  • 模型转换器参数列表 $converter_source_to_target_params_list$

例如表中有三个字段:id(主键)、goods_name、price

假设用户定义了如下代码块内容:

target.set$field_name_hump_A$(source.get$field_name_hump_A$());

输出(会自动根据字段数量,累加输出自定义代码块内容):

target.setId(source.getId());
target.setGoodsName(source.getGoodsName());
target.setPrice(source.getPrice());
  • 必填项业务校验 $biz_check_required_params$

例如表中有三个字段:id(主键)、goods_name、price

假设用户定义了如下代码块内容:

CommonAssert.$java_type_adapter_assert_method$(dto.get$field_name_hump_A$(), "$field_comment$不可空");

输出(会自动根据字段数量,累加输出自定义代码块内容):

CommonAssert.isNoBlankStr(dto.getGoodsName(), "goodsName不可空");
CommonAssert.isNoBlankStr(dto.getPrice(), "price不可空");

这个工具已经开源,可以移步:

https://github.com/GooseCoding/utilsbox

感谢阅读,希望对你有所帮助 :)   来源:juejin.cn/post/7200552990737432633


最后,关注公众号互联网架构师,在后台回复:2T,可以获取我整理的 Java 系列面试题和答案,非常齐全。


正文结束


推荐阅读 ↓↓↓

1.JetBrains 如何看待自己的软件在中国被频繁破解?

2.无意中发现了一位清华妹子的资料库!

3.程序员一般可以从什么平台接私活?

4.40岁,刚被裁,想说点啥。

5.为什么国内 996 干不过国外的 955呢?

6.中国的铁路订票系统在世界上属于什么水平?                        

7.15张图看懂瞎忙和高效的区别!


浏览 15
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报