从零搭建SpringBoot后台框架(十)——集成mapstruct转换器

2023年 10月 11日 74.3k 0

一、本节内容

  • 本文将修改id的生成方式为雪花算法(方便分布式扩展去重)
  • 引入mapstruct:mapstruct可以实现javabean之间对象转换,可以自定义转换规则
  • 解决雪花算法生成的id前端显示失真的问题,js的int类型,不能保存雪花算法id(长度19)的数值,尾部几位会显示为000,导致不能正常交互
  • 配置mybatisPlus自动填充创建日期、创建人、更新日期、更新人
  • 微调controller.java.ftl模板
  • 添加乐观锁拦截器
  • 二、修改ID为雪花算法

  • 修改字段生成类MybatisPlusGenerator配置
  • //全局主键类型
    .idType(IdType.ASSIGN_ID)
    

    image.png

  • 重新运行MybatisPlusGenerator,发现SystemLog.java有更新
  • image.png

  • 执行save方法时,mybatis plus会自动使用内置的雪花算法,为id赋值
  • 三、引入mapstruct

    1. 添加依赖

    
        org.mapstruct
        mapstruct
        1.5.5.Final
        provided
    
    

    2. 添加构建工具

    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.8.1
                
                    1.8
                    1.8
                    
                        
                        
                            org.projectlombok
                            lombok
                            1.18.30
                        
                        
                            org.mapstruct
                            mapstruct-processor
                            1.5.5.Final
                        
                        
                    
                
            
        
    
    

    3. 调整spring-boot-starter-freemarkermybatis-plus-generatorscopetest(非必须)

    这2个jar仅在测试阶段,自动生成文件使用,因此可以指定为test属性

    
        org.springframework.boot
        spring-boot-starter-freemarker
        test
    
    
        com.baomidou
        mybatis-plus-generator
        3.5.3.2
        test
    
    

    4. 添加validation依赖,后面校验会用到

    
        org.springframework.boot
        spring-boot-starter-validation
    
    

    四、调整controller.java.ftl模板

  • 添加swagger注解
  • 指定save和updateById接收入参为json(添加@RequestBody注解),增加校验@Validated
  • save反参、removeById入参id、getById入参id,接收字符串String,内部转成Long执行业务逻辑。原因:方便前端接口定义,防止js long int失真
  • package ${package.Controller};
    
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.example.demo.common.http.RestResult;
    import ${package.Entity}.${entity};
    import ${package.Service}.${table.serviceName};
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import org.springframework.web.bind.annotation.RestController;
    
    import org.springframework.stereotype.Controller;
    
    
    import ${superControllerClassPackage};
    
    
    import javax.annotation.Resource;
    
    /**
     * 
     * ${table.comment!} 前端控制器
     * 
     *
     * @author ${author}
     * @since ${date}
     */
    
    @RestController
    
    @Controller
    
    @RequestMapping("/${package.ModuleName}/${controllerMappingHyphen}${table.entityPath}")
    
        class ${table.controllerName} : ${superControllerClass}()
    
    @Api(tags = "${table.comment!}")
        
    public class ${table.controllerName} extends ${superControllerClass} {
        
    public class ${table.controllerName} {
        
    
        @Resource
        private ${table.serviceName} ${table.serviceName?uncap_first};
    
        @PostMapping("/save")
        @ApiOperation(value = "新增${table.comment!}")
        public RestResult save(@RequestBody @Validated ${entity} ${entity?uncap_first}) {
            ${table.serviceName?uncap_first}.save(${entity?uncap_first});
            return RestResult.getSuccessResult(String.valueOf(${entity?uncap_first}.getId()));
        }
    
        @PostMapping("/removeById")
        @ApiOperation(value = "根据id删除${table.comment!}")
        public RestResult removeById(String id) {
            boolean success = ${table.serviceName?uncap_first}.removeById(Long.parseLong(id));
            return RestResult.getSuccessResult(success);
        }
    
        @PostMapping("/updateById")
        @ApiOperation(value = "根据id更新${table.comment!}")
        public RestResult updateById(@RequestBody @Validated ${entity} ${entity?uncap_first}) {
            boolean success = ${table.serviceName?uncap_first}.updateById(${entity?uncap_first});
            return RestResult.getSuccessResult(success);
        }
    
        @PostMapping("/getById")
        @ApiOperation(value = "根据id查询${table.comment!}")
        public RestResult getById(String id) {
            ${entity} ${entity?uncap_first} = ${table.serviceName?uncap_first}.getById(Long.parseLong(id));
            return RestResult.getSuccessResult(${entity?uncap_first});
        }
    
        @PostMapping("/page")
        @ApiOperation(value = "分页查询${table.comment!}")
        public RestResult page(Integer pageNum, Integer pageSize) {
            Page page = ${table.serviceName?uncap_first}.page(new Page(pageNum, pageSize));
            return RestResult.getSuccessResult(page);
        }
    }
    
    

    重新生成的SystemLogController如下:

    package com.example.demo.controller;
    
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.example.demo.common.http.RestResult;
    import com.example.demo.model.SystemLog;
    import com.example.demo.service.SystemLogService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * 

    * 系统日志表 前端控制器 *

    * * @author chenzl * @since 2023-10-11 */ @RestController @RequestMapping("/systemLog") @Api(tags = "系统日志表") public class SystemLogController { @Resource private SystemLogService systemLogService; @PostMapping("/save") @ApiOperation(value = "新增系统日志表") public RestResult save(@RequestBody @Validated SystemLog systemLog) { systemLogService.save(systemLog); return RestResult.getSuccessResult(String.valueOf(systemLog.getId())); } @PostMapping("/removeById") @ApiOperation(value = "根据id删除系统日志表") public RestResult removeById(String id) { boolean success = systemLogService.removeById(Long.parseLong(id)); return RestResult.getSuccessResult(success); } @PostMapping("/updateById") @ApiOperation(value = "根据id更新系统日志表") public RestResult updateById(@RequestBody @Validated SystemLog systemLog) { boolean success = systemLogService.updateById(systemLog); return RestResult.getSuccessResult(success); } @PostMapping("/getById") @ApiOperation(value = "根据id查询系统日志表") public RestResult getById(String id) { SystemLog systemLog = systemLogService.getById(Long.parseLong(id)); return RestResult.getSuccessResult(systemLog); } @PostMapping("/page") @ApiOperation(value = "分页查询系统日志表") public RestResult page(Integer pageNum, Integer pageSize) { Page page = systemLogService.page(new Page(pageNum, pageSize)); return RestResult.getSuccessResult(page); } }

    五、使用mapstruct

    1. 新建创建入参类SystemLogSaveCommand.java

  • 从com.example.demo.model.SystemLog复制得到
  • 注意要修改@ApiModel的value值,否则swagger不能正常生成前端对象
  • 删除属性id、deleteFlag、createTime、updateTime、updatedBy、version六个属性
  • 删除com.baomidou.mybatisplus.annotation相关注解内容
  • package com.example.demo.model.command;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Getter;
    import lombok.Setter;
    
    @Getter
    @Setter
    @ApiModel(value = "SystemLogSaveCommand", description = "系统日志表新增入参")
    public class SystemLogSaveCommand {
    
        @ApiModelProperty("日志信息描述")
        private String description;
    
        @ApiModelProperty("方法名称")
        private String method;
    
        @ApiModelProperty("日志类型 0是正常,1是异常")
        private String logType;
    
        @ApiModelProperty("请求的ip")
        private String requestIp;
    
        @ApiModelProperty("异常错误码")
        private String exceptionCode;
    
        @ApiModelProperty("异常详情")
        private String exceptionDetail;
    
        @ApiModelProperty("请求参数")
        private String params;
    
        @ApiModelProperty("请求的用户id")
        private String userId;
    
    
        @ApiModelProperty("创建人")
        private String createdBy;
    }
    

    2. 新建更新入参类SystemLogUpdateCommand.java

  • 从com.example.demo.model.SystemLog复制得到
  • 删除属性deleteFlag、createTime、createdBy、updateTime四个属性
  • 删除com.baomidou.mybatisplus.annotation相关注解内容
  • id增加注解@NotBlank(message = "id不能为空"),控制更新时id必传
  • id类型改为String,解决前端失真问题
  • package com.example.demo.model.command;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Getter;
    import lombok.Setter;
    
    import javax.validation.constraints.NotBlank;
    
    @Getter
    @Setter
    @ApiModel(value = "SystemLogUpdateCommand", description = "系统日志表")
    public class SystemLogUpdateCommand {
        @NotBlank(message = "id不能为空")
        private String id;
    
        @ApiModelProperty("日志信息描述")
        private String description;
    
        @ApiModelProperty("方法名称")
        private String method;
    
        @ApiModelProperty("日志类型 0是正常,1是异常")
        private String logType;
    
        @ApiModelProperty("请求的ip")
        private String requestIp;
    
        @ApiModelProperty("异常错误码")
        private String exceptionCode;
    
        @ApiModelProperty("异常详情")
        private String exceptionDetail;
    
        @ApiModelProperty("请求参数")
        private String params;
    
        @ApiModelProperty("请求的用户id")
        private String userId;
    
        @ApiModelProperty("最后修改人")
        private String updatedBy;
    
        @ApiModelProperty("版本号")
        private Integer version;
    }
    

    3. 新建详情反参类SystemLogDetailResp.java

  • 从com.example.demo.model.SystemLog复制得到
  • 删除属性deleteFlag一个属性
  • 删除com.baomidou.mybatisplus.annotation相关注解内容
  • id类型改为String,解决前端失真问题
  • package com.example.demo.model.resp;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Getter;
    import lombok.Setter;
    
    import java.time.LocalDateTime;
    
    @Getter
    @Setter
    @ApiModel(value = "SystemLogDetailResp", description = "系统日志表")
    public class SystemLogDetailResp {
    
        private String id;
    
        @ApiModelProperty("日志信息描述")
        private String description;
    
        @ApiModelProperty("方法名称")
        private String method;
    
        @ApiModelProperty("日志类型 0是正常,1是异常")
        private String logType;
    
        @ApiModelProperty("请求的ip")
        private String requestIp;
    
        @ApiModelProperty("异常错误码")
        private String exceptionCode;
    
        @ApiModelProperty("异常详情")
        private String exceptionDetail;
    
        @ApiModelProperty("请求参数")
        private String params;
    
        @ApiModelProperty("请求的用户id")
        private String userId;
    
        @ApiModelProperty("创建时间")
        private LocalDateTime createTime;
    
        @ApiModelProperty("创建人")
        private String createdBy;
    
        @ApiModelProperty("最后修改时间")
        private LocalDateTime updateTime;
    
        @ApiModelProperty("最后修改人")
        private String updatedBy;
    
        @ApiModelProperty("版本号")
        private Integer version;
    }
    

    4. 新建分页反参类SystemLogPageResp.java

  • 从com.example.demo.model.SystemLog复制得到
  • 删除属性deleteFlag一个属性
  • 删除com.baomidou.mybatisplus.annotation相关注解内容
  • id类型改为String,解决前端失真问题
  • 这里和详情返回字段相同,实际业务场景分页仅需要部分字段,可以根据需求进行删除字段
  • package com.example.demo.model.resp;
    
    import io.swagger.annotations.ApiModel;
    import io.swagger.annotations.ApiModelProperty;
    import lombok.Getter;
    import lombok.Setter;
    
    import java.time.LocalDateTime;
    
    @Getter
    @Setter
    @ApiModel(value = "SystemLogPageResp", description = "系统日志表")
    public class SystemLogPageResp {
    
        private String id;
    
        @ApiModelProperty("日志信息描述")
        private String description;
    
        @ApiModelProperty("方法名称")
        private String method;
    
        @ApiModelProperty("日志类型 0是正常,1是异常")
        private String logType;
    
        @ApiModelProperty("请求的ip")
        private String requestIp;
    
        @ApiModelProperty("异常错误码")
        private String exceptionCode;
    
        @ApiModelProperty("异常详情")
        private String exceptionDetail;
    
        @ApiModelProperty("请求参数")
        private String params;
    
        @ApiModelProperty("请求的用户id")
        private String userId;
    
        @ApiModelProperty("创建时间")
        private LocalDateTime createTime;
    
        @ApiModelProperty("创建人")
        private String createdBy;
    
        @ApiModelProperty("最后修改时间")
        private LocalDateTime updateTime;
    
        @ApiModelProperty("最后修改人")
        private String updatedBy;
    
        @ApiModelProperty("版本号")
        private Integer version;
    }
    

    5. 新建mapstruct转换类SystemLogConverter

    package com.example.demo.model.converter;
    
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.example.demo.model.SystemLog;
    import com.example.demo.model.command.SystemLogSaveCommand;
    import com.example.demo.model.command.SystemLogUpdateCommand;
    import com.example.demo.model.resp.SystemLogDetailResp;
    import com.example.demo.model.resp.SystemLogPageResp;
    import org.mapstruct.Mapper;
    import org.mapstruct.factory.Mappers;
    
    @Mapper
    public interface SystemLogConverter {
        SystemLogConverter INSTANCE = Mappers.getMapper(SystemLogConverter.class);
    
        SystemLog convert(SystemLogSaveCommand command);
    
        SystemLog convert(SystemLogUpdateCommand command);
    
        SystemLogDetailResp convert2DetailResp(SystemLog systemLog);
    
        Page convert2PageResp(Page page);
    }
    

    6. 改造出入参SystemLogController

  • save方法入参修改为SystemLogSaveCommand,使用SystemLogConverter转换为SystemLog执行后续操作,可以拷贝所有属性到SystemLog
  • updateById方法入参修改为SystemLogUpdateCommand,使用SystemLogConverter转换为SystemLog执行后续操作,可以拷贝所有属性到SystemLog,同时将id从String转换为Long参与后续逻辑
  • getById方法出参使用SystemLogConverter转换为SystemLogDetailResp后返回,可以把id转换为String,同时不返回deleteFlag属性
  • page方法出参使用SystemLogConverter转换为SystemLogPageResp后返回,可以把id转换为String,同时不返回deleteFlag属性
  • package com.example.demo.controller;
    
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.example.demo.common.http.RestResult;
    import com.example.demo.model.SystemLog;
    import com.example.demo.model.command.SystemLogSaveCommand;
    import com.example.demo.model.command.SystemLogUpdateCommand;
    import com.example.demo.model.converter.SystemLogConverter;
    import com.example.demo.model.resp.SystemLogDetailResp;
    import com.example.demo.model.resp.SystemLogPageResp;
    import com.example.demo.service.SystemLogService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    
    /**
     * 

    * 系统日志表 前端控制器 *

    * * @author chenzl * @since 2023-10-11 */ @RestController @RequestMapping("/systemLog") @Api(tags = "系统日志表") public class SystemLogController { @Resource private SystemLogService systemLogService; @PostMapping("/save") @ApiOperation(value = "新增系统日志表") public RestResult save(@RequestBody @Validated SystemLogSaveCommand command) { SystemLog systemLog = SystemLogConverter.INSTANCE.convert(command); systemLogService.save(systemLog); return RestResult.getSuccessResult(String.valueOf(systemLog.getId())); } @PostMapping("/removeById") @ApiOperation(value = "根据id删除系统日志表") public RestResult removeById(String id) { boolean success = systemLogService.removeById(Long.parseLong(id)); return RestResult.getSuccessResult(success); } @PostMapping("/updateById") @ApiOperation(value = "根据id更新系统日志表") public RestResult updateById(@RequestBody @Validated SystemLogUpdateCommand command) { SystemLog systemLog = SystemLogConverter.INSTANCE.convert(command); boolean success = systemLogService.updateById(systemLog); return RestResult.getSuccessResult(success); } @PostMapping("/getById") @ApiOperation(value = "根据id查询系统日志表") public RestResult getById(String id) { SystemLog systemLog = systemLogService.getById(Long.parseLong(id)); SystemLogDetailResp resp = SystemLogConverter.INSTANCE.convert2DetailResp(systemLog); return RestResult.getSuccessResult(resp); } @PostMapping("/page") @ApiOperation(value = "分页查询系统日志表") public RestResult page(Integer pageNum, Integer pageSize) { Page page = systemLogService.page(new Page(pageNum, pageSize)); Page resp = SystemLogConverter.INSTANCE.convert2PageResp(page); return RestResult.getSuccessResult(resp); } }

    六、添加乐观锁拦截器

  • com.example.demo.config.MybatisPlusConfig新增乐观锁拦截器OptimisticLockerInnerInterceptor
  • package com.example.demo.config;
    
    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class MybatisPlusConfig {
        /**
         * 添加分页插件
         */
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
            //如果配置多个插件,切记分页最后添加
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            //如果有多数据源可以不配具体类型 否则都建议配上具体的DbType
            //interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
            return interceptor;
        }
    }
    

    七、自动填充创建和更新时间和操作人

  • 自动获取当前时间填充创建和更新时间(无值时有效)
  • 创建人和更新人写死为system,实际业务系统会获取当前操作人写入
  • package com.example.demo.config;
    
    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;
    
    import java.time.LocalDateTime;
    
    @Slf4j
    @Component
    public class MyMetaObjectHandler implements MetaObjectHandler {
    
        @Override
        public void insertFill(MetaObject metaObject) {
            this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
            this.strictInsertFill(metaObject, "createdBy", String.class, "system");
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            // 起始版本 3.3.0(推荐)
            this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
            this.strictUpdateFill(metaObject, "updatedBy", String.class, "system");
        }
    }
    

    八、项目地址

    gitee

    PS:可以通过tag下载本文对应的代码版本

    九、结尾

    集成mapstruct已完成,有问题可以联系chenzhenlindx@qq.com

    十、参考文章

  • 乐观锁插件
  • 自动填充功能
  • mapstuct官网
  • 相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论