/crud-generator

【效率工具】CRUD代码生成器。支持Oracle/MySQL/SQL Server/PostgreSQL等数据库。

Primary LanguageJavaMIT LicenseMIT

CRUD代码生成器

概述

  • 适用于 Spring Boot 架构
  • 基于数据表结构定义,自动生成 CRUD 代码,省时省力
  • 自动检测数据表字段类型、字段长度、数值精度、主键字段、唯一索引字段
  • 支持 Oracle、MySQL(Percona/MariaDB)、Microsoft SQLServer、PostgreSQL 等四种类型数据库
  • 支持生成原版 Mybatis 以及 Mybatis通用MapperMybatisPlus相关代码
  • 支持生成 Mybatis 分页代码(基于 Mybatis-PageHelper)
  • 支持生成基于 Hibernate Validator 的参数校验注解
  • 支持生成基于 Spring Cloud OpenFeign 的服务消费端代码
  • 支持生成基于 Swagger 的API接口及参数注解
  • 支持生成 Postman 可导入的API接口JSON定义文件
  • 支持生成 JUnit 所支持的单元测试用例文件
  • 支持生成基于 CURL 的命令行测试用例脚本文件

文件说明

模板名 含义 路径 备注 表字段变化后是否重新生成
vo.ftl VO类 java/vo/ 用于接收/输出数据
queryVo.ftl 查询条件 java/vo/ 用于接收查询条件
dto.ftl DTO类 java/dto/ 适用于service层
queryDto.ftl 查询条件 java/dto/ 适用于service层
controller.ftl 控制器代码 java/controller/ 对外暴露HTTP接口 该文件只需生成1次
serviceInterface.ftl 服务接口定义 java/service/ 该文件只需生成1次
origDomain.ftl 实体类 java/entity/ 适用于原版mybatis
origMapperClass.ftl Mapper接口 java/dao/ 适用于原版mybatis 该文件只需生成1次
origMapperXml.ftl Mapper XML resources/ 适用于原版mybatis
origServiceImpl.ftl 服务接口实现 java/service/ 适用于原版mybatis
tkDomain.ftl Domain实体类定义 java/domain/ 适用于mybatis通用Mapper
tkMapperClass.ftl Mapper接口 java/dao/ 适用于mybatis通用Mapper 该文件只需生成1次
tkMapperXml.ftl Mapper XML resources/ 适用于mybatis通用Mapper 该文件只需生成1次
tkServiceImpl.ftl 服务接口实现 java/service/ 适用于mybatis通用Mapper
mpDomain.ftl Domain实体类定义 java/domain/ 适用于MybatisPlus
mpMapperClass.ftl Mapper接口 java/dao/ 适用于MybatisPlus 该文件只需生成1次
mpMapperXml.ftl Mapper XML resources/ 适用于MybatisPlus 该文件只需生成1次
mpServiceImpl.ftl 服务接口实现 java/service/ 适用于MybatisPlus
feignClient.ftl FeignClient服务接口 java/feign/ 适用于Spring Cloud消费者端 该文件只需生成1次
feignClientFallback.ftl FeignClient服务接口Fallback实现 java/feign/ 适用于Spring Cloud消费者端 该文件只需生成1次
commonConverter.ftl 通用对象转换工具 java/util/ 用于VO/DTO/DO等对象之间的转换 该文件只需生成1次
converter.ftl 对象转换工具类 java/util/ 用于VO/DTO/DO等对象之间的转换
validatorInsertGroup.ftl 校验分组(插入) java/validator/ 用于Hibernate Validator分组校验 该文件只需生成1次
validatorUpdateGroup.ftl 校验分组(更新) java/validator/ 用于Hibernate Validator分组校验 该文件只需生成1次
unitTestCase.ftl JUnit单元测试用例 java/test/ 用于JUnit单元测试
postmanCollection.ftl Postman接口定义 json/ 使用方法:Postman>Import
postmanEnvironment.ftl Postman环境变量定义 json/ 使用方法:Postman>Manage Environment>Import 该文件只需生成1次
curl-add.ftl CURL新增记录命令及参数 other/ 使用方法:直接命令行调用
curl-update.ftl CURL更新记录命令及参数 other/ 使用方法:直接命令行调用
curl-delete.ftl CURL删除记录命令及参数 other/ 使用方法:直接命令行调用
html-form.ftl 标准HTML表单 other/ 包含各个字段的标准HTML表单

主键字段检测规则

  1. 数据表有主键字段时,程序将直接使用该字段
  2. 数据表无主键字段时,程序将使用最后一个具有唯一索引的字段
  3. 数据表既无主键字段也无唯一索引字段时,程序将使用 TableContext 对象中 primaryKeyColumn 参数所指定的字段

使用范例

public class App {

    public static void main(String[] args) throws Exception {
        JdbcInfo param = new JdbcInfo();
        
        //指定数据库类型
        param.setDbType(DatabaseType.ORACLE);
        
        //数据库主机名或IP
        param.setHost("192.168.2.102");
        
        //数据库端口号
        param.setPort("1521");
        
        //schema名称(oracle和PostgreSQL填写Schema名称,mysql或sqlserver则填写数据库名称)
        param.setSchema("sys_biz");
        
        //数据库用户名
        param.setUsername("biz_manager");
        
        //数据库用户密码
        param.setPassword("123456");
        
        //数据库实例名(oracle填写实例名,PostgreSQL填写数据库名称,mysql或sqlserver留空)
        param.setServiceName("newbizdb");
    
        TableCodeGenerator generator = new TableCodeGenerator(param);    
        RunParam rp = new RunParam();
        rp.setOutputPath("E:\\tmp\\generated");
        
        //表名
        TableContext table = TableContext.withName("T_ORDER_INFO");
        //需去掉的表名前缀
        table.setTableNamePrefixToRemove("T_");  
        rp.addTable(table);

        //结果使用Result类来包装,如 MyResult<Boolean>
        rp.setResultClass("com.test.common.MyResult");
    
        //使用Mybatis通用Mapper作为dao层中间件
        generator.setDaoType(DaoType.TkMyBatis);

        //将表名从下划线转驼峰后再加上Entity后缀作为类名
        generator.setClassNameGenerator(t -> org.apache.commons.text.CaseUtils.toCamelCase(t, true, '_') + "Entity");
        
        //生成
        generator.run(rp);
    }
}

RunParam 的可选参数设置:

方法名 含义 备注
setAuthor() 指定生成的javadoc注释中author的名称 留空则默认使用当前操作系统用户名
setBasePkgName() 指定java基础包名 留空则默认使用com.example.myapp
setOutputPath() 指定输出目录的绝对路径 留空则默认输出到当前用户主目录
setBaseEntityClass() 如果VO/DO/DTO等实体类需要继承某个基础类,可以在此指定基础类的完整路径 留空则默认无
setResultClass() 如果service层及接口层返回值需要使用某个基础类进行包装,可以在此指定该类的完整路径 留空则默认无

TableContext 的可选参数设置:

方法名 含义 备注
setTableNamePrefixToRemove() 指定需去掉的表名前缀 留空则不去掉任何前缀
setLikeColumns() 指定启用模糊查询的字段名(用于字符类型字段,多个以逗号隔开) 如果字段为字符类型则在相应QueryDTO类中增加 xxxLike 属性
setRangeColumns() 指定启用取值范围查询的字段名(用于时间或数字类型字段,多个以逗号隔开) 如果字段为时间或数字类型则在相应QueryDTO类中增加 xxxMin/xxxMax 属性
setInColumns() 指定启用 IN 查询的字段名(多个以逗号隔开) 在相应QueryDTO类中增加 xxxIn 属性
setNotInColumns() 指定启用 NOT IN 查询的字段名(多个以逗号隔开) 在相应QueryDTO类中增加 xxxNotIn 属性
setPrimaryKeyColumn() 手动指定主键字段名(不区分大小写) 如果程序无法自动检测到主键字段,则在此参数指定(适用于无主键且无唯一索引的表)
setVersionColumn() 如果该表有乐观锁,可在此指定其字段名(不区分大小写) 留空则默认无
setPageSize() 指定默认分页大小(一个大于0的整数) 留空则默认为10
setSequenceName() 针对Oracle数据库,可以指定序列名称 留空则默认使用 SEQ_表名 作为序列名称
setLogicDeleteColumn() 如果该表需要实现逻辑删除功能,指定相应字段名,所有删除操作不再是物理删除而是逻辑删除 留空则无逻辑删除功能

TableCodeGenerator 的可选参数设置:

方法名 含义 备注
setGlobalTableNamePrefixToRemove() 需去掉的表名前缀(全局) 如果需要去掉的表名前缀均相同,则可以全局配置它,不再需要在TableContext中逐个配置前缀; 如果二者同时有值,则使用TableContext中的值
setUseDubboService() 是否使用 Dubbo 的@Service注解 留空则默认使用 Spring 的@Service注解
setUseSwagger() 是否生成swagger相关注解 留空则默认true
setDaoType() 指定dao层中间件的类型(三选一:原版MyBatis/Mybatis通用Mapper/MyBatisPlus) 留空则默认使用原版MyBatis
setClassNameGenerator() 指定类名的自定义生成规则 留空则默认将表名从下划线形式转成驼峰形式作为类名

最佳实践

使用建议

  • 如果您采用原版 Mybatis,不应在 resources/XXXMapper.xml 中编写自己的业务逻辑(因为一旦重新生成代码,该文件也会一起重新生成,会丢失您自行编写的业务代码);建议自行继承 XXXMapper,然后在新的xml映射文件中编写自己的业务逻辑
  • 如果您采用 Mybatis通用Mapper,可以在 resources/XXXCommonMapper.xml 中编写自己的业务逻辑(该文件不会重新生成)
  • 如果数据表字段变化比较频繁,建议采用 Mybatis通用Mapper 或 MybatisPlus
  • 个人意见:MybatisPlus 本身是一个优秀的增强插件,但它的开发团队想做的事情太多,且其中某些开发人员缺乏对开源项目负责任的职业态度,导致其API相对较乱(如版本平滑升级等),相比之下,如果希望项目规模扩大后依然规范,且需要长期维护,那么建议选用目前的 Mybatis通用Mapper!
  • 如果使用了Mybatis通用Mapper或MybatisPlus,则您的java版本需要1.8或更高(因为代码中使用了Lambda表达式)

常见类型映射说明

数据库中的数据类型 实体类中Java变量类型 备注
bigint Long
binary Byte[]
bit Boolean
blob Byte[]
char String
date Date
datetime Date
decimal BigDecimal
double Double
float Float
image Byte[]
int Integer
int4 Integer
int8 Long
longblob Byte[]
mediumblob Byte[]
money BigDecimal
nchar String
ntext String
number Long 无小数时
number BigDecimal 有小数时
numeric BigDecimal
nvarchar String
nvarchar2 String
real Float
smalldatetime Date
smallint Integer
smallmoney BigDecimal
sql_variant String
text String
timestamp Date
timestamp(6) Date
tinyint Integer
uniqueidentifier String
varbinary Byte[]
varchar String
varchar2 String

其它说明

  • 数据表应当包含1个唯一索引或主键字段(可以与具体业务无关)
  • 如果指定Oracle数据库,则表名、字段名、序列名会统一为大写形式
  • 如果指定MySQL数据库,则表名、字段名会统一为小写形式,且会自动加上反引号

扩展

适配更多数据库

  1. 编写自定义的SQL语句(用于查询数据库中的表名、表注释、字段名、字段注释、字段类型、字段长度、主键、唯一索引等),约定保存路径为 resources/sql_XXX.xml
  2. 编写您自定义的DbUtil类继承 AbstractDbUtil 抽象类,如数据库有特殊逻辑,也可在该类中编写
  3. 在 dbutils-config.json 文件中配置您自定义的DbUtil类映射及驱动名称等信息
  4. 不要忘记在 pom.xml 中加入相应的 jdbc 驱动

更多代码模板

代码模板基于 Freemarker模板引擎 编写,因此您可以遵循该模板的语法自行实现新的代码模板。提供的上下文变量包括:

变量 含义 变量类型
${basePkgName} 基础包名 String
${baseEntityClass} 基础实体类的完整路径 String
${timeZone} 时区名称 String
${useDubboServiceAnnotation} 是否启用Dubbo服务(0否/1是) int
${useSwagger} 是否启用SwaggerUI(0否/1是) int
${table} 数据表根对象 Object
${table.name} 数据表原始名称 String
${table.kebabCaseName} kebabCase形式的表名 String
${table.dbType} 数据库类型(oracle/mysql/sqlserver/postgresql,小写形式) String
${table.subPkgName} 子包名 String
${table.javaClassName} Java类名称(首字母大写) String
${table.javaClassNameLower} Java类名称(首字母小写) String
${table.comments} 数据表注释 String
${table.imports} Java类中需要import的类名集合 Set
${table.author} 生成的javadoc注释中author的名称 String
${table.pageSize} 默认分页大小 int
${table.versionColumn} 乐观锁版本号字段名 String
${table.columns} 数据表中的所有字段信息列表 List

字段信息包括:

变量 含义 变量类型
${column.columnName} 原始列名 String
${column.columnCamelNameLower} 驼峰形式列名(首字母小写) String
${column.columnCamelNameUpper} 驼峰形式列名(首字母大写) String
${column.columnComment} 列注释 String
${column.columnType} 列类型(数据库中的类型) String
${column.columnJavaType} 列类型对应的Java类型 String
${column.columnMyBatisType} 列类型对应的mybatis jdbcType String
${column.columnLength} 列长度 int
${column.columnPrecision} 列精度(针对数字列) int
${column.columnScale} 列小数位数(针对数字列) int
${column.nullable} 是否可空(0否/1是) int
${column.charLength} 列长度(针对字符列) int
${column.defaultValue} 列默认值 String
${column.isNumber} 是否为数字列(0否/1是) int
${column.isChar} 是否为字符列(0否/1是) int
${column.isPrimaryKey} 是否为主键(0否/1是) int
${column.enableLike} 是否启用 LIKE 模糊查询(0否/1是,仅限于字符列) int
${column.enableIn} 是否启用 IN 查询(0否/1是) int
${column.enableRange} 是否启用范围(大于等于、小于等于)查询(0否/1是,仅限于数字和日期时间类型的列) int
${column.enableNotIn} 是否启用 NOT IN 查询(0否/1是) int

编写完模板文件之后,在 template-config.json 文件中配置该模板的相关信息即可。

附录

Hibernate Validator分组校验说明

  • 针对数据插入操作,根据 InsertGroup 分组进行校验;
  • 针对数据更新操作,根据 UpdateGroup 分组进行校验;
  • 其它的共有校验规则(如字段长度限制等),根据 Default 分组进行校验;

处理Hibernate Validator校验异常

通过 @ExceptionHandler 捕获 MethodArgumentNotValidException 和 BindException 异常即可。

区别:

  • 如果使用表单对象(Form)形式接收参数(如查询操作),则出现 BindException 异常
  • 如果使用 @RequestBody 形式接收参数(如插入操作),则出现 MethodArgumentNotValidException 异常

参考代码:

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
    @ResponseBody
    public String errorHandler(Exception ex) {
        if (ex instanceof MethodArgumentNotValidException) {
            MethodArgumentNotValidException me = (MethodArgumentNotValidException) ex;
            return getFieldErrors(me.getBindingResult().getFieldErrors());
        } else if (ex instanceof BindException) {
            BindException be = (BindException) ex;
            return getFieldErrors(be.getBindingResult().getFieldErrors());
        } else {
            return ex.getMessage();
        }
    }

    private String getFieldErrors(List<FieldError> fieldErrors) {
        String msg = "error";
        if (!fieldErrors.isEmpty()) {
            List<String> errorMsgs = fieldErrors.stream().map(FieldError::getDefaultMessage).distinct().collect(Collectors.toList());
            msg = String.join(";", errorMsgs);
        }
        return msg;
    }
}

默认情况下 Hibernate Validator 使用普通模式:校验器会校验完所有的属性,然后返回所有的验证错误信息。

如果希望使用 Fail-fast(快速失败) 模式,则需要增加额外配置:

@Configuration
public class HibernateValidatorConfig {
    public HibernateValidatorConfig() {
    }

    @Bean
    public Validator myValidatorFactory() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class).configure().failFast(true).buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}

使用该模式之后,当校验器遇到第1个不满足条件的参数时就立即结束校验工作,只返回这一个参数对应的错误信息。

SwaggerUI配置参考

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }
}

第三方依赖

自动生成的代码中用到了一些第三方开源组件,它们的maven坐标如下(版本号请自行匹配):

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
    <version>X.X.X</version>
</dependency>

<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>X.X.X</version>
</dependency>

<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger2</artifactId>
   <version>X.X.X</version>
</dependency>

<dependency>
   <groupId>io.springfox</groupId>
   <artifactId>springfox-swagger-ui</artifactId>
   <version>X.X.X</version>
</dependency>

<dependency>
   <groupId>com.google.guava</groupId>
   <artifactId>guava</artifactId>
   <version>X.X.X</version>
</dependency>

<dependency>
   <groupId>org.mybatis.spring.boot</groupId>
   <artifactId>mybatis-spring-boot-starter</artifactId>
   <version>X.X.X</version>
</dependency>

<dependency>
    <groupId>tk.mybatis</groupId>
    <artifactId>mapper-spring-boot-starter</artifactId>
    <version>X.X.X</version>
</dependency>

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>X.X.X</version>
</dependency>

<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>X.X.X</version>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus</artifactId>
    <version>X.X.X</version>
</dependency>

数据库版本

实际单元测试中用到的数据库版本:

  • Oracle 11g
  • MySQL 5.5/5.6/5.7/5.8
  • MariaDB 10.2.x/10.3.x/10.4.x
  • Microsoft SQL Server 2008 R2
  • PostgreSQL 12.3
  • Percona Server(未实际测试,理论上也兼容)