从零开始搭建企业管理系统(八):Spring Data Jpa 代码生成器
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类的话,需要删除一些公共字段,没有坐过滤。