从零开始搭建企业管理系统(八):Spring Data Jpa 代码生成器

前言

本来是想接着写权限的CRUD的,但是能偷点懒就偷点懒吧,就想先写一个代码生成器来生成一下代码,就免得写一些重复的基本的CRUD操作了。

我之前写过一篇 MyBatis Plus 代码生成器的使用文档,MyBatis-Plus 代码生成器,这个就是使用 MyBatis Plus 的时候能用的上的代码生成,但是我们的项目是一个 Spring Data Jpa 的项目,所以就想着二次开发一下,自己定义模板来生产Jpa的代码,OK,开始上代码。

导入依赖

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
	<scope>provided</scope>
</dependency>

<!-- spring boot 依赖管理 2.3.32 -->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
</dependency>

编写自定义代码模板

controller.java.ftl

package ${package.Controller};

import ${package.Entity}.${entity};
import ${package.Service}.${table.serviceName};
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * ${table.comment!} 控制类
 *
 * @author ${author}
 * @date ${date}
 */
@Tag(name = "${table.comment!}")
@RestController
@RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
public class ${table.controllerName} {

    @Resource
    private ${table.serviceName} service;

    @GetMapping("/page")
    @Operation(summary = "分页查询")
    public Page<${entity}> page(int page, int size) {
        return service.page(PageRequest.of(page - 1, size));
    }

    @GetMapping("/{id}")
    @Operation(summary = "根据用户ID查询")
    public ${entity} get(@PathVariable Long id) {
        return service.get(id);
    }

    @GetMapping("/name/{name}")
    @Operation(summary = "根据用户名称查询")
    public ${entity} getByName(@PathVariable String name) {
        return service.getByName(name);
    }

    @PostMapping
    @Operation(summary = "新增|修改")
    public void upsert(@RequestBody List<${entity}> users) {
        service.upsert(users);
    }

    @DeleteMapping("/{id}")
    @Operation(summary = "根据用户ID删除")
    public void delete(@PathVariable Long id) {
        service.delete(id);
    }

}

entity.java.ftl

不开启生成实体类,因为我们是先创建实体类生成表,所以实体类已经存在

package ${package.Entity};

import ${cfg.baseEntity};
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
import org.hibernate.annotations.DynamicUpdate;

import java.io.Serializable;
import java.util.Date;

/**
 * ${table.comment!} 实体类
 *
 * @author ${author}
 * @date ${date}
 */
@Data
@Entity
@DynamicUpdate
@Table(name = "${table.name}")
@Schema(description="${table.comment!}")
<#if superEntityClass??>
public class ${entity} extends ${superEntityClass} implements Serializable {
<#elseif activeRecord>
public class ${entity} extends Model<${entity}> {
<#else>
public class ${entity} implements Serializable {
</#if>

<#if entitySerialVersionUID>
    private static final long serialVersionUID = 1L;
</#if>
<#-- ----------  BEGIN 字段循环遍历  ---------->
<#list table.fields as field>
    <#if field.keyFlag>
        <#assign keyPropertyName="${field.propertyName}"/>
    </#if>

    <#if field.comment!?length gt 0>
        <#if swagger2>
    @Schema(description = "${field.comment}")
        <#else>
    /**
     * ${field.comment}
     */
        </#if>
    </#if>
    <#if field.keyFlag>
        <#-- 普通字段 -->
    <#elseif field.fill??>
    @TableField("${field.annotationColumnName}")
    </#if>
    private ${field.propertyType} ${field.propertyName};
</#list>
<#------------  END 字段循环遍历  ---------->
}

repository.java.ftl

package ${package.Mapper};

import ${package.Entity}.${entity};
import ${cfg.jpaRepository};
import org.springframework.stereotype.Repository;

import java.io.Serializable;

/**
 * ${table.comment!} repository 接口
 *
 * @author ${author}
 * @date ${date}
 */
@Repository
public interface ${table.mapperName} extends ${superMapperClass}<${entity}, Long>, Serializable {

    /**
     * 根据名称查询
     *
     * @param name 用户名称
     * @return 用户信息
     */
    ${entity} findByName(String name);
}

service.java.ftl

package ${package.Service};

import ${package.Entity}.${entity};
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.List;

/**
 * ${table.comment!} 服务类
 *
 * @author ${author}
 * @date ${date}
 */
public interface ${table.serviceName} {

    /**
     * 分页查询
     *
     * @param pageable 分页参数
     * @return Page<${entity}> 分页数据
     */
    Page<${entity}> page(Pageable pageable);

    /**
     * 根据ID查询
     *
     * @param id ID
     * @return ${entity} 数据
     */
    ${entity} get(Long id);

    /**
     * 根据名称查询
     *
     * @param name 名称
     * @return ${entity} 数据
     */
    ${entity} getByName(String name);

    /**
     * 保存/更新
     *
     * @param params 参数
     */
    void upsert(List<${entity}> params);

    /**
     * 删除
     *
     * @param id id
     */
    void delete(Long id);

}

serviceImpl.java.ftl

package ${package.ServiceImpl};

import com.xm.common.util.JpaUtils;
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import jakarta.annotation.Resource;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

/**
 * ${table.comment!} 服务实现类
 *
 * @author ${author}
 * @date ${date}
 */
@Service
public class ${table.serviceImplName} implements ${table.serviceName} {

    @Resource
    private ${table.mapperName} repository;

    @Override
    public Page<${entity}> page(Pageable pageable) {
        return repository.findAll(pageable);
    }

    @Override
    public ${entity} get(Long id) {
        return repository.findById(id).orElse(null);
    }

    @Override
    public ${entity} getByName(String name) {
        return repository.findByName(name);
    }

    @Override
    public void upsert(List<${entity}> data) {
        data.forEach(d -> {
            Optional<${entity}> entity = repository.findById(d.getId());
            if (entity.isPresent()) {
                // 修改
                JpaUtils.copyNotNullProperties(d, entity.get());
                repository.save(entity.get());
            } else {
                // 新增
                repository.save(d);
            }
        });
    }

    @Override
    public void delete(Long id) {
        repository.deleteById(id);
    }

}

CodeGenerator

public class CodeGenerator {

    /**
     * 功能描述: 读取控制台内容
     *
     * @param tip 控制台输入
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入" + tip + ":");
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotEmpty(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }

    /**
     * 功能描述: 代码生成
     */
    public static void main(String[] args) {

        // 1、创建代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 2、创建全局配置
        GlobalConfig gc = new GlobalConfig();
        // 获取当前项目的路径
        String projectPath = System.getProperty("user.dir");
        // 文件输出路径
        gc.setOutputDir(projectPath + "/src/main/java");
        // 指定作者
        gc.setAuthor("xiaoming");
        // 生成后是否打开资源管理器
        gc.setOpen(false);
        // 重新生成时文件是否覆盖
        gc.setFileOverride(false);
        // 去掉Service接口的首字母I
        gc.setServiceName("%sService");
        // 修改 Mapper 名称为 Repository
        gc.setMapperName("%sRepository");
        // 添加 Entity 后缀
        gc.setEntityName("%sEntity");
        // 主键生成策略
        gc.setIdType(IdType.AUTO);
        // 定义生成的实体类中日期类型
        gc.setDateType(DateType.ONLY_DATE);
        // 实体属性 Swagger2 注解
        gc.setSwagger2(true);
        // 将全局配置设置到代码生成器对象中
        mpg.setGlobalConfig(gc);


        // 3、创建数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        // 数据库连接
        dsc.setUrl("jdbc:mysql://localhost:3306/xm_admin?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=GMT%2B8");
        // 数据库驱动
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        // 用户名
        dsc.setUsername("root");
        // 密码
        dsc.setPassword("root");
        // 数据库类型
        dsc.setDbType(DbType.MYSQL);
        // 将数据库配置设置到代码生成器对象中
        mpg.setDataSource(dsc);


        // 4、创建包配置
        PackageConfig pc = new PackageConfig();
        // 模块名(在控制台输入)
        pc.setModuleName(scanner("模块名"));
        // 生成文件的上级包
        pc.setParent("com.xm.module");
        // 设置具体包名
        pc.setController("controller");
        pc.setEntity("entity");
        pc.setService("service");
        pc.setMapper("repository");
        // 将包配置设置到代码生成器对象中
        mpg.setPackageInfo(pc);

        // 5、自定义配置
        InjectionConfig customConfig = new InjectionConfig() {
            @Override
            public void initMap() {
                Map<String, Object> map = new HashMap<>();
                map.put("baseEntity", "com.xm.common.base.BaseEntity");
                map.put("jpaRepository", "org.springframework.data.jpa.repository.JpaRepository");
                this.setMap(map);
            }
        };
        mpg.setCfg(customConfig);

        // 6、配置自定义输出模板
        TemplateConfig tc = new TemplateConfig();
        // 指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
        tc.setController("freemarker/controller.java");
        tc.setService("freemarker/service.java");
        tc.setServiceImpl("freemarker/serviceImpl.java");
        tc.setMapper("freemarker/repository.java");
        // 设置为null不会生成
        tc.setEntity(null);
        tc.setXml(null);
        mpg.setTemplate(tc);


        // 7、创建策略配置
        StrategyConfig strategy = new StrategyConfig();
        // 数据库表名下划线转驼峰命名
        strategy.setNaming(NamingStrategy.underline_to_camel);
        // 数据库列名下划线转驼峰命名
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        // 设置lombok模型
        strategy.setEntityLombokModel(true);
        // restful api风格控制器
        strategy.setRestControllerStyle(true);
        // 表名(控制台输入)
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        // url中驼峰转连字符
        strategy.setControllerMappingHyphenStyle(true);
        // 生成实体时去掉表前缀
        strategy.setTablePrefix(pc.getModuleName() + "_");
        // 传入实体类和repository接口的父类
        strategy.setSuperEntityClass("BaseEntity");
        strategy.setSuperMapperClass("JpaRepository");
        // 将生成策略配置设置到代码生成器对象中
        mpg.setStrategy(strategy);

        // 8、修改模板引擎
        mpg.setTemplateEngine(new FreemarkerTemplateEngine());
        
        // 9、开始生成代码
        mpg.execute();
    }
}

启动测试

在这里插入图片描述

启动之后需要输入模块名称和表明,同时也可以看到我们的module下是没有角色和菜单的业务类的,回车执行代码生成。

在这里插入图片描述

可以看到代码执行完毕,同时我们的代码也生成成功了,但是还有点细节需要修改,这次就是稍微改了改,能用就行,后面可以考虑将这个代码生成做成一个通用的模块。

问题修改

  • controller 类的 @RequestMapping的值需要修改,默认会将entity带上,需要删除。
  • 如果有生成entity类的话,需要删除一些公共字段,没有坐过滤。