SpringBoot集成MyBatis-Plus生成代码

SpringBoot集成MyBatis-Plus代码生成器

背景

​ MyBatis-Plus代码生成器相较于MyBatis代码生成器,可以多生成controller层和service层,并且配置更丰富,通过对Freemarker默认模板的修改和增加自定义模板配置适配,可提升开发效率

操作步骤

  • 项目目录结构
    • MyFreemarkerTemplateEngine继承FreemarkerTemplateEngine,用于自定义模板设置
    • templates.generator目录下为自定义的Freemarker模板
    • MybatisCeneratorTest为测试类

image-20240613091141166

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>

生成代码结构如下:

image-20240614091141627

  • 自定义模板文件

​ 查看源码com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine,如果有自定义文件,先生成自定义文件,再生成默认模板,所以自定义文件不能替换默认模板

image-20240614091354472

  • 默认模板自定义模板路径

​ 查看源码com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine中mapper和xml文件生成部分可知自定义模板由getMapperTemplate方法获取

image-20240614092457819

​ 默认获取自常量ConstVal.TEMPLATE_MAPPER

image-20240614092818566

​ 可通过方法mapperTemplate方法覆盖

image-20240614092757247

参考

posted @ 2024-06-14 09:31  litayun  阅读(193)  评论(0编辑  收藏  举报