当前位置: 欣欣网 > 码农

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

2023-02-05码农

最近在做一个项目,需要新建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
<iftest="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
returndo模型
*/
public static $table_name_hump_A$DO toDo($table_name_hump_A$DTOsource){
$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$DOsource){
$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, 输出(会自动换行,会携带以下缩进):

    <iftest="id != null">
    AND id = #{id}
    </if>
    <iftest="goodsName != null">
    AND goods_name = #{goodsName}
    </if>
    <iftest="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

    >>

    END

    精品资料,超赞福利,免费领

    微信扫码/长按识别 添加【技术交流群

    群内每天分享精品学习资料

    最近开发整理了一个用于速刷面试题的小程序;其中收录了上千道常见面试题及答案(包含基础并发JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud消息队列等多个类型),欢迎您的使用。

    👇👇

    👇点击"阅读原文",获取更多资料(持续更新中