SpringBoot集成MyBatis-Plus生成代码
SpringBoot集成MyBatis-Plus代码生成器
背景
MyBatis-Plus代码生成器相较于MyBatis代码生成器,可以多生成controller层和service层,并且配置更丰富,通过对Freemarker默认模板的修改和增加自定义模板配置适配,可提升开发效率
操作步骤
- 项目目录结构
- MyFreemarkerTemplateEngine继承FreemarkerTemplateEngine,用于自定义模板设置
- templates.generator目录下为自定义的Freemarker模板
- MybatisCeneratorTest为测试类
1.pom文件中增加依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.3</version>
</dependency>
2.在test目录下新增测试类用于生成文件
- 代码中的mysql连接,用户名和密码需要做相应替换
- 具体配置参考代码生成器配置
@SpringBootTest
public class MybatisGeneratorTest {
@Test
public void generatorSqlFile() {
// 使用 FastAutoGenerator 快速配置代码生成器
FastAutoGenerator fastAutoGenerator = FastAutoGenerator.create("jdbc:mysql://192.168.11.128:3306/pai_coding?serverTimezone=GMT%2B8&tinyInt1isBit=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true", "lichao", "Huawei12#$")
.globalConfig(builder -> {
builder.author("lichao") // 设置作者
.outputDir("src\\main\\java") // 输出目录
.enableSwagger(); // 开启 Swagger 模式
})
.dataSourceConfig(builder ->
builder.databaseQueryClass(SQLQuery.class)
.typeConvert(new MySqlTypeConvert())
.dbQuery(new MySqlQuery())
)
.packageConfig(builder -> {
builder.parent("com.lichao.tobebetter") // 设置父包名
.entity("entity") // 设置实体类包名
.mapper("mapper") // 设置 Mapper 接口包名
.service("service") // 设置 Service 接口包名
.serviceImpl("service.impl") // 设置 Service 实现类包名
.pathInfo(Collections.singletonMap(OutputFile.xml,
"src\\main\\resources\\mapper")); // 设置 Mapper XML 文件包名, 取代.xml()避免使用父包名前缀
});
// 增加自定义模板配置
fastAutoGenerator.injectionConfig(consumer -> {
List<CustomFile> customFileList = new ArrayList<>();
CustomFile customFile = new CustomFile.Builder().fileName("RetJson.java")
.templatePath("/templates/generator/retjson.java.ftl")
// .enableFileOverride()
.build();
customFileList.add(customFile);
consumer.customFile(customFileList);
});
// 该部分仅用于模板调试测试,用于禁止对应类生成
// fastAutoGenerator.templateConfig(builder -> {
// builder//.disable(TemplateType.XML) // 禁用实体类生成
// .disable(TemplateType.CONTROLLER)
// .disable(TemplateType.SERVICE)
// .disable(TemplateType.SERVICE_IMPL)
//// .disable(TemplateType.MAPPER);
// });
// Entity策略配置
fastAutoGenerator.strategyConfig(builder -> {
builder.addInclude("banner") // 设置需要生成的表名
.entityBuilder()
.disableSerialVersionUID()
.enableLombok()
.enableRemoveIsPrefix()
.enableTableFieldAnnotation()
.enableFileOverride()
.logicDeleteColumnName("deleted")
.naming(NamingStrategy.no_change)
.columnNaming(NamingStrategy.underline_to_camel)
.addSuperEntityColumns("created_by", "created_time", "updated_by", "updated_time")
.formatFileName("%sEntity");
});
// Controller 策略配置
fastAutoGenerator.strategyConfig(builder -> {
builder.controllerBuilder()
.template("/templates/generator/controller.java")
// .enableHyphenStyle()
.enableRestStyle()
.formatFileName("%sAction")
.enableFileOverride();
});
// Service 策略配置
fastAutoGenerator.strategyConfig(builder -> {
builder.serviceBuilder()
.serviceTemplate("/templates/generator/service.java")
.serviceImplTemplate("/templates/generator/serviceImpl.java")
.enableFileOverride()
.formatServiceFileName("%sService")
.formatServiceImplFileName("%sServiceImpl");
});
// Mapper和Xml 策略配置
fastAutoGenerator.strategyConfig(builder -> {
builder.mapperBuilder()
.mapperAnnotation(Mapper.class) // 开启 @Mapper 注解
.mapperTemplate("/templates/generator/mapper.java") // 指定mapper模板路径
.mapperXmlTemplate("/templates/generator/mapper.xml") // 指定xml文件模板路径
.enableFileOverride() // 覆盖已生成文件
.enableBaseResultMap() // 启用 BaseResultMap 生成
.enableBaseColumnList() // 启用 BaseColumnList
.formatMapperFileName("%sMapper") // 格式化 Mapper 文件名称
.formatXmlFileName("%sMapper"); // 格式化 XML 实现类文件名称
});
// 使用Freemarker 模板引擎,MyFreemarkerTemplateEngine继承FreemarkerTemplateEngine
fastAutoGenerator.templateEngine(new MyFreemarkerTemplateEngine());
fastAutoGenerator.execute();
}
}
3.为了指定输出目录覆盖了原有的自定义模板输出方法,增加了RetJson模板用于通用返回,RetJson不覆盖输出,配置修改在MybatisGeneratorTest.java增加自定义模板配置部分
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.apache.commons.lang3.StringUtils;
import javax.validation.constraints.NotNull;
import java.io.File;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;
public class MyFreemarkerTemplateEngine extends FreemarkerTemplateEngine {
public static final String UTIL_CLASS_STR = "retjson.java;";
@Override
protected void outputCustomFile(@NotNull List<CustomFile> customFiles, @NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap) {
String entityName = tableInfo.getEntityName();
String parentPath = getPathInfo(OutputFile.parent);
customFiles.forEach(file -> {
if (UTIL_CLASS_STR.contains(file.getFileName().toLowerCase(Locale.ENGLISH))) {
// 对自定义模板控制输出路径,其他自定义模板按原来逻辑处理
String fileName = String.format(parentPath + File.separator + "utils" + File.separator + "%s", file.getFileName());
outputFile(new File(fileName), objectMap, file.getTemplatePath(), file.isFileOverride());
} else {
String filePath = StringUtils.isNotBlank(file.getFilePath()) ? file.getFilePath() : parentPath;
if (StringUtils.isNotBlank(file.getPackageName())) {
filePath = filePath + File.separator + file.getPackageName().replaceAll("\\.", StringPool.BACK_SLASH + File.separator);
}
Function<TableInfo, String> formatNameFunction = file.getFormatNameFunction();
String fileName = filePath + File.separator + (null != formatNameFunction ? formatNameFunction.apply(tableInfo) : entityName) + file.getFileName();
outputFile(new File(fileName), objectMap, file.getTemplatePath(), file.isFileOverride());
}
});
}
}
4.模板代码
- 模板代码修改了原始的继承基类,只适配简单的增删改查
- Controller层使用RetJson类作为通用返回
- SQL新增方法默认数据库主键属性为id,数据库主键自增
controller.java.ftl
package ${package.Controller};
import ${package.Entity}.${entity};
import ${package.Service}.${table.serviceName};
import ${package.Parent}.utils.RetJson;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
<#if restControllerStyle>
import org.springframework.web.bind.annotation.RestController;
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>
import javax.validation.Valid;
/**
* <p>
* ${table.comment!} 前端控制器
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
<#if kotlin>
class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
<#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
<#else>
public class ${table.controllerName} {
</#if>
@Autowired
private ${table.serviceName} ${table.serviceName?uncap_first}Impl;
@ApiOperation(value = "${table.comment}详情", response = ${entity}.class)
@GetMapping(value = "/info/{id}")
public Object info(@PathVariable Long id) {
Object data = ${table.serviceName?uncap_first}Impl.info(id);
return RetJson.ok(data);
}
@ApiOperation(value = "${table.comment}新增")
@PostMapping(value = "/add")
public Object add(@Valid @RequestBody ${entity} param) {
${table.serviceName?uncap_first}Impl.save(param);
return RetJson.ok();
}
@ApiOperation(value = "${table.comment}修改")
@PostMapping(value = "/modify")
public Object modify(@Valid @RequestBody ${entity} param) {
${table.serviceName?uncap_first}Impl.modify(param);
return RetJson.ok();
}
@ApiOperation(value = "${table.comment}删除(单个条目)")
@GetMapping(value = "/remove/{id}")
public Object remove(@PathVariable Long id) {
${table.serviceName?uncap_first}Impl.remove(id);
return RetJson.ok();
}
}
</#if>
entity.java.ftl
package ${package.Entity};
<#list table.importPackages as pkg>
import ${pkg};
</#list>
<#if springdoc>
import io.swagger.v3.oas.annotations.media.Schema;
<#elseif swagger>
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
</#if>
<#if entityLombokModel>
import lombok.Getter;
import lombok.Setter;
<#if chainModel>
import lombok.experimental.Accessors;
</#if>
</#if>
/**
* <p>
* ${table.comment!}
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if entityLombokModel>
@Getter
@Setter
<#if chainModel>
@Accessors(chain = true)
</#if>
</#if>
<#if table.convert>
@TableName("${schemaName}${table.name}")
</#if>
<#if springdoc>
@Schema(name = "${entity}", description = "${table.comment!}")
<#elseif swagger>
@ApiModel(value = "${entity}对象", description = "${table.comment!}")
</#if>
<#if superEntityClass??>
public class ${entity} extends ${superEntityClass}<#if activeRecord><${entity}></#if> {
<#elseif activeRecord>
public class ${entity} extends Model<${entity}> {
<#elseif entitySerialVersionUID>
public class ${entity} implements Serializable {
<#else>
public class ${entity} {
</#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 springdoc>
@Schema(description = "${field.comment}")
<#elseif swagger>
@ApiModelProperty("${field.comment}")
<#else>
/**
* ${field.comment}
*/
</#if>
</#if>
<#if field.keyFlag>
<#-- 主键 -->
<#if field.keyIdentityFlag>
@TableId(value = "${field.annotationColumnName}", type = IdType.AUTO)
<#elseif idType??>
@TableId(value = "${field.annotationColumnName}", type = IdType.${idType})
<#elseif field.convert>
@TableId("${field.annotationColumnName}")
</#if>
<#-- 普通字段 -->
<#elseif field.fill??>
<#-- ----- 存在字段填充设置 ----->
<#if field.convert>
@TableField(value = "${field.annotationColumnName}", fill = FieldFill.${field.fill})
<#else>
@TableField(fill = FieldFill.${field.fill})
</#if>
<#elseif field.convert>
@TableField("${field.annotationColumnName}")
</#if>
<#-- 乐观锁注解 -->
<#if field.versionField>
@Version
</#if>
<#-- 逻辑删除注解 -->
<#if field.logicDeleteField>
@TableLogic
</#if>
private ${field.propertyType} ${field.propertyName};
</#list>
<#------------ END 字段循环遍历 ---------->
<#if !entityLombokModel>
<#list table.fields as field>
<#if field.propertyType == "boolean">
<#assign getprefix="is"/>
<#else>
<#assign getprefix="get"/>
</#if>
public ${field.propertyType} ${getprefix}${field.capitalName}() {
return ${field.propertyName};
}
<#if chainModel>
public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
<#else>
public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
</#if>
this.${field.propertyName} = ${field.propertyName};
<#if chainModel>
return this;
</#if>
}
</#list>
</#if>
<#if entityColumnConstant>
<#list table.fields as field>
public static final String ${field.name?upper_case} = "${field.name}";
</#list>
</#if>
<#if activeRecord>
@Override
public Serializable pkVal() {
<#if keyPropertyName??>
return this.${keyPropertyName};
<#else>
return null;
</#if>
}
</#if>
<#if !entityLombokModel>
@Override
public String toString() {
return "${entity}{" +
<#list table.fields as field>
<#if field_index==0>
"${field.propertyName} = " + ${field.propertyName} +
<#else>
", ${field.propertyName} = " + ${field.propertyName} +
</#if>
</#list>
"}";
}
</#if>
}
mapper.java.ftl
package ${package.Mapper};
import ${package.Entity}.${entity};
<#if mapperAnnotationClass??>
import ${mapperAnnotationClass.name};
</#if>
import java.util.List;
/**
* <p>
* ${table.comment!} Mapper 接口
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if mapperAnnotationClass??>
@${mapperAnnotationClass.simpleName}
</#if>
<#if kotlin>
interface ${table.mapperName}
<#else>
public interface ${table.mapperName} {
/**
* ${table.comment!}详情
* @param id
* @return
*/
${entity} getById(Long id);
/**
* ${table.comment!}新增
* @param param 根据需要进行传值
* @return
*/
void save(${entity} param);
/**
* ${table.comment!}修改
* @param param 根据需要进行传值
* @return
*/
void modify(${entity} param);
/**
* ${table.comment!}删除(单个条目)
* @param id
* @return
*/
void removeById(Long id);
}
</#if>
mapper.xml.ftl
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package.Mapper}.${table.mapperName}">
<#if enableCache>
<!-- 开启二级缓存 -->
<cache type="${cacheClassName}"/>
</#if>
<#if baseResultMap>
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
<#list table.fields as field>
<#if field.keyFlag><#--生成主键排在第一位-->
<id column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
<#list table.commonFields as field><#--生成公共字段 -->
<result column="${field.name}" property="${field.propertyName}" />
</#list>
<#list table.fields as field>
<#if !field.keyFlag><#--生成普通字段 -->
<result column="${field.name}" property="${field.propertyName}" />
</#if>
</#list>
</resultMap>
</#if>
<#if baseColumnList>
<!-- 通用查询结果列 -->
<sql id="Base_Column_List">
<#list table.fields as field>
${r"`"}${field.columnName}${r"`"}<#if field_has_next>,</#if>
</#list>
</sql>
<#if baseColumnList>
<select id="getById" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from ${table.name}
where id = ${r"#{id}"}
</select>
<#else>
<select id="getById" resultType="${package.Entity}.${entity}">
select
<#list table.fields as field>
${r"`"}${field.columnName}${r"`"}<#if field_has_next>,</#if>
</#list>
${table.fieldNames}
from ${table.name}
where id = ${r"#{id}"}
</select>
</#if>
<insert id="save">
insert into ${table.name}
(
<#list table.fields as field>
<#if field.columnName == "id">
<#else>
${r"`"}${field.columnName}${r"`"}<#if field_has_next>,</#if>
</#if>
</#list>
)
values (
<#list table.fields as field>
<#if field.columnName == "id">
<#elseif field.columnName == 'create_time' || field.columnName == 'update_time'>
now()<#if field_has_next>,</#if>
<#else >
${r"#{"}${field.propertyName}${r"}"}<#if field_has_next>,</#if>
</#if>
</#list>
)
</insert>
<update id="modify">
update ${table.name}
<set>
<#list table.fields as field>
<#if field.columnName == "id" || field.columnName == 'create_time'>
<#else >
<if test="${field.propertyName} != null and ${field.propertyName} != ''">
${r"`"}${field.columnName}${r"`"} = ${r"#{"}${field.propertyName}${r"}"},
</if>
</#if>
</#list>
</set>
where id = ${r"#{id}"}
</update>
<delete id="removeById">
delete from ${table.name} where id = ${r"#{id}"}
</delete>
</#if>
</mapper>
retjson.java.ftl
package ${package.Parent}.utils;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* <p>
* ${table.comment!} 返回工具类
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if kotlin>
open class RetJSON {
}
<#else>
@Getter
@Setter
@ToString
public class RetJson {
public static final int SUCCESS_CODE = 0;
public static final int FAILED_CODE = -1;
/** 状态码 */
private int code;
/** 消息 */
private String msg;
/** 数据 */
private Object data;
public RetJson() {
this.code = SUCCESS_CODE;
this.msg = "success";
}
public RetJson(Object data) {
this.data = data;
this.code = SUCCESS_CODE;
this.msg = "success";
}
public RetJson(int code, String msg) {
this.code = code;
this.msg = msg;
}
/**
* 执行成功
*/
public static Object ok(){
return new RetJson();
}
/**
* 执行成功
* @param data 返回数据
*/
public static Object ok(Object data){
return new RetJson(data);
}
/**
* 执行失败
* @param msg 消息
*/
public static Object err(String msg){
return new RetJson(FAILED_CODE, msg);
}
}
</#if>
service.java.ftl
package ${package.Service};
import ${package.Entity}.${entity};
import ${superServiceClassPackage};
import java.util.List;
/**
* <p>
* ${table.comment!} 服务类
* </p>
*
* @author ${author}
* @since ${date}
*/
<#if kotlin>
interface ${table.serviceName}
<#else>
public interface ${table.serviceName} {
/**
* ${table.comment!}详情
* @param id 主键
* @return ${entity}
*/
${entity} info(Long id);
/**
* ${table.comment!}新增
* @param param 根据需要进行传值
*/
void save(${entity} param);
/**
* ${table.comment!}修改
* @param param 根据需要进行传值
*/
void modify(${entity} param);
/**
* ${table.comment!}删除(单个条目)
* @param id
*/
void remove(Long id);
}
</#if>
serviceImpl.java.ftl
package ${package.ServiceImpl};
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
<#if generateService>
import ${package.Service}.${table.serviceName};
</#if>
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* <p>
* ${table.comment!} 服务实现类
* </p>
*
* @author ${author}
* @since ${date}
*/
@Service
<#if kotlin>
open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>()<#if generateService>, ${table.serviceName}</#if> {
}
<#else>
public class ${table.serviceImplName} <#if generateService> implements ${table.serviceName}</#if> {
@Autowired
private ${table.mapperName} ${table.mapperName?uncap_first};
/**
* ${table.comment!}详情
* @param id 主键
* @return ${entity}
*/
@Override
public ${entity} info(Long id) {
return ${table.mapperName?uncap_first}.getById(id);
}
/**
* ${table.comment!}新增
* @param param 根据需要进行传值
*/
@Override
public void save(${entity} param) {
${table.mapperName?uncap_first}.save(param);
}
/**
* ${table.comment!}修改
* @param param 根据需要进行传值
*/
@Override
public void modify(${entity} param) {
${table.mapperName?uncap_first}.modify(param);
}
/**
* ${table.comment!}删除(单个条目)
* @param id
*/
@Override
public void remove(Long id) {
${table.mapperName?uncap_first}.removeById(id);
}
}
</#if>
生成代码结构如下:
附
- 自定义模板文件:
查看源码com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine,如果有自定义文件,先生成自定义文件,再生成默认模板,所以自定义文件不能替换默认模板
- 默认模板自定义模板路径:
查看源码com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine中mapper和xml文件生成部分可知自定义模板由getMapperTemplate方法获取
默认获取自常量ConstVal.TEMPLATE_MAPPER
可通过方法mapperTemplate方法覆盖