编辑
2025-08-20
JAVA-Springboot
00
请注意,本文编写于 118 天前,最后修改于 118 天前,其中某些信息可能已经过时。

目录

DTO、DO、VO,你是怎么转换的?
MyBatis-Plus 代码生成器 + MapStruct + SpringDoc(OpenAPI/Swagger)
1.首先添加必要的依赖(包含 SpringDoc)
2. SpringDoc 配置类
3. 代码生成器配置(集成 SpringDoc 注解)
4. 模板文件(包含 SpringDoc 注解)
1. entity.java.vm(实体类模板,带 Swagger 注解)
2. dto.java.vm(DTO 模板,带 Swagger 注解)
3. vo.java.vm(VO 模板,带 Swagger 注解)
4. controller.java.vm(控制器模板,带 Swagger 注解)
5. convert.java.vm(MapStruct 转换器模板)
5. 使用说明

DTO、DO、VO,你是怎么转换的?

DTO、DO、VO这些东西看着复杂,核心就俩字:省事。

先说说DTO、DO、VO到底解决啥问题

刚开始写代码,总是一个实体类用到底:查数据库用它,接口入参用它,返回给前端还⽤它。直到有一次,产品突然说要大改接口返回字段,改着改着发现不对劲——数据库实体(比如UserDO)里有密码、创建时间这些敏感字段,之前直接返回给前端了!更坑的是,因为实体类被Service、Controller、前端共用,改一个字段得全链路检查,差点没改崩。

DTO、DO、VO分层,本质是给不同层划清界限:

DO(Data Object):和数据库表一一对应,只在Dao层和Service层之间用,里面全是数据库字段(比如user_id、password、create_time)。

DTO(Data Transfer Object):前端传给后端的参数载体,比如用户登录时传的username和password,只包含接口需要的字段,多余的一概不要。

VO(View Object):后端返回给前端的结果,会根据前端需求“裁剪”DO里的字段,比如隐藏密码,只返回username、nickname这些前端需要展示的。

这么一分层,好处立马就显出来了。就像我之前遇到的产品大改:Service层逻辑全重写了,查的表都换了,但因为Controller层只认DTO和VO,我只要保证入参DTO和返回VO的格式不变,前端完全不用改,Swagger文档也没动——这就是解耦的威力。

MyBatis-Plus 代码生成器 + MapStruct + SpringDoc(OpenAPI/Swagger)

下面我将为你提供一个集成了 MyBatis-Plus 代码生成器、MapStruct 和 SpringDoc(OpenAPI/Swagger)的完整方案,使用模板配置方式实现。

1.首先添加必要的依赖(包含 SpringDoc)

<!-- MyBatis-Plus 相关依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.3.1</version> </dependency> <!-- 模板引擎 --> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency> <!-- MapStruct --> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.3.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.3.Final</version> <scope>provided</scope> </dependency> <!-- SpringDoc (OpenAPI/Swagger) --> <dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.1.0</version> </dependency> <!-- 数据库驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> <scope>runtime</scope> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.24</version> <optional>true</optional> </dependency>

2. SpringDoc 配置类

import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SpringDocConfig { @Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title("API文档") .version("1.0.0") .description("系统API接口文档") .contact(new Contact().name("开发团队").email("dev@example.com")) .license(new License().name("Apache 2.0").url("https://www.apache.org/licenses/LICENSE-2.0"))); } }

3. 代码生成器配置(集成 SpringDoc 注解)

import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class CodeGenerator { // 数据库连接信息 private static final String URL = "jdbc:mysql://localhost:3306/your_database?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai"; private static final String USERNAME = "root"; private static final String PASSWORD = "password"; // 父包名 private static final String PARENT_PACKAGE = "com.example.demo"; // 模块名 private static final String MODULE_NAME = "system"; // 作者 private static final String AUTHOR = "your name"; // 需要生成的表名 private static final List<String> TABLE_NAMES = Collections.singletonList("sys_user"); // XML文件输出路径 private static final String XML_OUTPUT_DIR = System.getProperty("user.dir") + "/src/main/resources/mapper/" + MODULE_NAME; public static void main(String[] args) { generateCode(); } public static void generateCode() { FastAutoGenerator.create(URL, USERNAME, PASSWORD) // 全局配置 .globalConfig(builder -> { builder.author(AUTHOR) // 作者 .outputDir(System.getProperty("user.dir") + "/src/main/java") // 输出路径 .commentDate("yyyy-MM-dd") // 注释日期 .disableOpenDir() // 生成后不打开文件夹 .fileOverride(); // 覆盖已生成文件 }) // 包配置 .packageConfig(builder -> { builder.parent(PARENT_PACKAGE) // 父包名 .moduleName(MODULE_NAME) // 模块名 .entity("entity") // 实体类包名 .service("service") // service包名 .serviceImpl("service.impl") // serviceImpl包名 .mapper("mapper") // mapper包名 .xml("mapper.xml") // mapper.xml包名 .controller("controller") // controller包名 .other("convert") // MapStruct转换器包名 .pathInfo(Collections.singletonMap(OutputFile.xml, XML_OUTPUT_DIR)); // XML路径 }) // 策略配置 .strategyConfig(builder -> { builder.addInclude(TABLE_NAMES) // 生成的表 .addTablePrefix("sys_") // 表前缀,生成实体时去掉前缀 // 实体类策略 .entityBuilder() .enableLombok() // 开启lombok .enableChainModel() // 开启链式模式 .enableTableFieldAnnotation() // 开启字段注解 // Controller策略 .controllerBuilder() .enableRestStyle() // 开启restful风格 .enableHyphenStyle() // 开启连字符风格 // Service策略 .serviceBuilder() .formatServiceFileName("%sService") .formatServiceImplFileName("%sServiceImpl"); }) // 自定义模板配置,生成DTO、VO、MapStruct转换器 .templateEngine(new VelocityTemplateEngine()) .injectionConfig(consumer -> { List<com.baomidou.mybatisplus.generator.config.po.TableInfo> tableInfos = consumer.getInjectionConfig().getTableInfoList(); List<com.baomidou.mybatisplus.generator.config.FileOutConfig> fileOutConfigs = new ArrayList<>(); // 生成DTO fileOutConfigs.add(new com.baomidou.mybatisplus.generator.config.FileOutConfig("/templates/dto.java.vm") { @Override public String outputFile(com.baomidou.mybatisplus.generator.config.po.TableInfo tableInfo) { return System.getProperty("user.dir") + "/src/main/java/" + PARENT_PACKAGE.replace(".", "/") + "/" + MODULE_NAME + "/dto/" + tableInfo.getEntityName() + "DTO.java"; } }); // 生成VO fileOutConfigs.add(new com.baomidou.mybatisplus.generator.config.FileOutConfig("/templates/vo.java.vm") { @Override public String outputFile(com.baomidou.mybatisplus.generator.config.po.TableInfo tableInfo) { return System.getProperty("user.dir") + "/src/main/java/" + PARENT_PACKAGE.replace(".", "/") + "/" + MODULE_NAME + "/vo/" + tableInfo.getEntityName() + "VO.java"; } }); // 生成MapStruct转换器 fileOutConfigs.add(new com.baomidou.mybatisplus.generator.config.FileOutConfig("/templates/convert.java.vm") { @Override public String outputFile(com.baomidou.mybatisplus.generator.config.po.TableInfo tableInfo) { return System.getProperty("user.dir") + "/src/main/java/" + PARENT_PACKAGE.replace(".", "/") + "/" + MODULE_NAME + "/convert/" + tableInfo.getEntityName() + "Convert.java"; } }); consumer.getInjectionConfig().setFileOutConfigList(fileOutConfigs); }) // 模板配置 .templateConfig(builder -> { builder.mapperXml("/templates/mapper.xml.vm") .entity("/templates/entity.java.vm") .service("/templates/service.java.vm") .serviceImpl("/templates/serviceImpl.java.vm") .mapper("/templates/mapper.java.vm") .controller("/templates/controller.java.vm"); }) .execute(); } }

4. 模板文件(包含 SpringDoc 注解)

在src/main/resources/templates目录下创建以下模板文件:

1. entity.java.vm(实体类模板,带 Swagger 注解)

package ${package.Entity}; #foreach($pkg in ${table.importPackages}) import ${pkg}; #end import lombok.Data; import io.swagger.v3.oas.annotations.media.Schema; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; /** * ${table.comment!} * * @author ${author} * @date ${date} */ @Data @TableName("${table.name}") @Schema(description = "${table.comment!}实体类") public class ${entity} { #foreach($field in ${table.fields}) #if(${field.keyFlag}) #set($keyPropertyName = ${field.propertyName}) #end @TableId(value = "${field.name}", type = ${field.idType}) @Schema(description = "${field.comment!}") private ${field.propertyType} ${field.propertyName}; #end #if(${entityLombokModel}) #else #foreach($field in ${table.fields}) public ${field.propertyType} get${field.capitalName}() { return ${field.propertyName}; } public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) { this.${field.propertyName} = ${field.propertyName}; } #end #end }

2. dto.java.vm(DTO 模板,带 Swagger 注解)

package ${package.Parent}.${moduleName}.dto; import lombok.Data; import java.io.Serializable; import io.swagger.v3.oas.annotations.media.Schema; #foreach($field in $table.fields) import ${field.type}; #end /** * ${table.comment!}数据传输对象 * * @author ${author} * @date ${date} */ @Data @Schema(description = "${table.comment!}数据传输对象") public class ${table.entityName}DTO implements Serializable { private static final long serialVersionUID = 1L; #foreach($field in $table.fields) @Schema(description = "${field.comment!}") private ${field.propertyType} ${field.propertyName}; #end }

3. vo.java.vm(VO 模板,带 Swagger 注解)

package ${package.Parent}.${moduleName}.vo; import lombok.Data; import java.io.Serializable; import io.swagger.v3.oas.annotations.media.Schema; #foreach($field in $table.fields) import ${field.type}; #end /** * ${table.comment!}视图对象 * * @author ${author} * @date ${date} */ @Data @Schema(description = "${table.comment!}视图对象") public class ${table.entityName}VO implements Serializable { private static final long serialVersionUID = 1L; #foreach($field in $table.fields) @Schema(description = "${field.comment!}") private ${field.propertyType} ${field.propertyName}; #end }

4. controller.java.vm(控制器模板,带 Swagger 注解)

package ${package.Controller}; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.List; import ${package.Service}.${table.serviceName}; import ${package.Entity}.${entity}; import ${package.Parent}.${moduleName}.vo.${table.entityName}VO; import ${package.Parent}.${moduleName}.dto.${table.entityName}DTO; import ${package.Parent}.${moduleName}.convert.${table.entityName}Convert; /** * ${table.comment!}控制器 * * @author ${author} * @date ${date} */ @RestController @RequestMapping("/${moduleName}/${table.entityPath}") @Tag(name = "${table.comment!}接口", description = "${table.comment!}相关操作") public class ${table.controllerName} { @Resource private ${table.serviceName} ${table.entityName?uncapitalize}Service; /** * 查询详情 */ @GetMapping("/{id}") @Operation(summary = "查询详情", description = "根据ID查询${table.comment!}详情") public ${table.entityName}VO getById( @Parameter(description = "主键ID", required = true) @PathVariable Long id) { ${entity} entity = ${table.entityName?uncapitalize}Service.getById(id); return ${table.entityName}Convert.INSTANCE.toVO(entity); } /** * 列表查询 */ @GetMapping("/list") @Operation(summary = "列表查询", description = "查询${table.comment!}列表") public List<${table.entityName}VO> list() { List<${entity}> entityList = ${table.entityName?uncapitalize}Service.list(); return ${table.entityName}Convert.INSTANCE.toVOList(entityList); } /** * 新增 */ @PostMapping @Operation(summary = "新增", description = "新增${table.comment!}") public boolean save( @Parameter(description = "${table.comment!}信息", required = true) @RequestBody ${table.entityName}DTO dto) { ${entity} entity = ${table.entityName}Convert.INSTANCE.toEntity(dto); return ${table.entityName?uncapitalize}Service.save(entity); } /** * 修改 */ @PutMapping @Operation(summary = "修改", description = "修改${table.comment!}") public boolean updateById( @Parameter(description = "${table.comment!}信息", required = true) @RequestBody ${table.entityName}DTO dto) { ${entity} entity = ${table.entityName}Convert.INSTANCE.toEntity(dto); return ${table.entityName?uncapitalize}Service.updateById(entity); } /** * 删除 */ @DeleteMapping("/{id}") @Operation(summary = "删除", description = "根据ID删除${table.comment!}") public boolean removeById( @Parameter(description = "主键ID", required = true) @PathVariable Long id) { return ${table.entityName?uncapitalize}Service.removeById(id); } }

5. convert.java.vm(MapStruct 转换器模板)

package ${package.Parent}.${moduleName}.convert; import org.mapstruct.Mapper; import org.mapstruct.factory.Mappers; import ${package.Entity}.${table.entityName}; import ${package.Parent}.${moduleName}.dto.${table.entityName}DTO; import ${package.Parent}.${moduleName}.vo.${table.entityName}VO; import java.util.List; /** * ${table.entityName}对象转换器 * * @author ${author} * @date ${date} */ @Mapper public interface ${table.entityName}Convert { ${table.entityName}Convert INSTANCE = Mappers.getMapper(${table.entityName}Convert.class); /** * DO转DTO */ ${table.entityName}DTO toDTO(${table.entityName} entity); /** * DTO转DO */ ${table.entityName} toEntity(${table.entityName}DTO dto); /** * DO转VO */ ${table.entityName}VO toVO(${table.entityName} entity); /** * DO列表转DTO列表 */ List<${table.entityName}DTO> toDTOList(List<${table.entityName}> entityList); /** * DO列表转VO列表 */ List<${table.entityName}VO> toVOList(List<${table.entityName}> entityList); }

5. 使用说明

修改CodeGenerator中的数据库连接信息、包名等配置 运行CodeGenerator的main方法,生成所有代码 启动项目后,访问 SpringDoc 的 Swagger UI 界面:http://localhost:8080/swagger-ui/index.html

生成的代码将包含:

带有@Schema注解的实体类、DTO 和 VO

带有@Tag、@Operation、@Parameter等注解的 Controller

自动生成的 MapStruct 转换器

完整的 MyBatis-Plus 服务层和数据访问层代码

这种集成方式实现了:

数据库表到实体类的自动映射

DO、DTO、VO 之间的类型安全转换

API 文档的自动生成和可视化

标准 CRUD 接口的快速开发

通过一次配置,即可生成符合规范的代码架构,极大提高开发效率。

本文作者:Allen Tang

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!