likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 108

一、本节内容

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

二、修改ID为雪花算法

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

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

  1. 重新运行MybatisPlusGenerator,发现SystemLog.java有更新

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

  1. 执行save方法时,mybatis plus会自动使用内置的雪花算法,为id赋值

三、引入mapstruct

1. 添加依赖

<dependency>
    <groupId>org.mapstruct</groupId>
    <artifactId>mapstruct</artifactId>
    <version>1.5.5.Final</version>
    <scope>provided</scope>
</dependency>

2. 添加构建工具

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <annotationProcessorPaths>
                    <!--一定要在mapstruct前面-->
                    <path>
                        <groupId>org.projectlombok</groupId>
                        <artifactId>lombok</artifactId>
                        <version>1.18.30</version>
                    </path>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>1.5.5.Final</version>
                    </path>
                    <!-- other annotation processors -->
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

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

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.5.3.2</version>
    <scope>test</scope>
</dependency>

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

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

四、调整controller.java.ftl模板

  1. 添加swagger注解
  2. 指定save和updateById接收入参为json(添加@RequestBody注解),增加校验@Validated
  3. 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;
<#if restControllerStyle>
import org.springframework.web.bind.annotation.RestController;
<#else>
import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
import ${superControllerClassPackage};
</#if>

import javax.annotation.Resource;

/**
 * <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>
@Api(tags = "${table.comment!}")
    <#if superControllerClass??>
public class ${table.controllerName} extends ${superControllerClass} {
    <#else>
public class ${table.controllerName} {
    </#if>

    @Resource
    private ${table.serviceName} ${table.serviceName?uncap_first};

    @PostMapping("/save")
    @ApiOperation(value = "新增${table.comment!}")
    public RestResult<String> 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<Boolean> 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<Boolean> 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<${entity}> 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<${entity}>> page(Integer pageNum, Integer pageSize) {
        Page<${entity}> page = ${table.serviceName?uncap_first}.page(new Page<>(pageNum, pageSize));
        return RestResult.getSuccessResult(page);
    }
}
</#if>

重新生成的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;

/**
 * <p>
 * 系统日志表 前端控制器
 * </p>
 *
 * @author chenzl
 * @since 2023-10-11
 */
@RestController
@RequestMapping("/systemLog")
@Api(tags = "系统日志表")
public class SystemLogController {

    @Resource
    private SystemLogService systemLogService;

    @PostMapping("/save")
    @ApiOperation(value = "新增系统日志表")
    public RestResult<String> save(@RequestBody @Validated SystemLog systemLog) {
        systemLogService.save(systemLog);
        return RestResult.getSuccessResult(String.valueOf(systemLog.getId()));
    }

    @PostMapping("/removeById")
    @ApiOperation(value = "根据id删除系统日志表")
    public RestResult<Boolean> removeById(String id) {
        boolean success = systemLogService.removeById(Long.parseLong(id));
        return RestResult.getSuccessResult(success);
    }

    @PostMapping("/updateById")
    @ApiOperation(value = "根据id更新系统日志表")
    public RestResult<Boolean> updateById(@RequestBody @Validated SystemLog systemLog) {
        boolean success = systemLogService.updateById(systemLog);
        return RestResult.getSuccessResult(success);
    }

    @PostMapping("/getById")
    @ApiOperation(value = "根据id查询系统日志表")
    public RestResult<SystemLog> getById(String id) {
        SystemLog systemLog = systemLogService.getById(Long.parseLong(id));
        return RestResult.getSuccessResult(systemLog);
    }

    @PostMapping("/page")
    @ApiOperation(value = "分页查询系统日志表")
    public RestResult<Page<SystemLog>> page(Integer pageNum, Integer pageSize) {
        Page<SystemLog> page = systemLogService.page(new Page<>(pageNum, pageSize));
        return RestResult.getSuccessResult(page);
    }
}

五、使用mapstruct

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

  1. 从com.example.demo.model.SystemLog复制得到
  2. 注意要修改@ApiModel的value值,否则swagger不能正常生成前端对象
  3. 删除属性id、deleteFlag、createTime、updateTime、updatedBy、version六个属性
  4. 删除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

  1. 从com.example.demo.model.SystemLog复制得到
  2. 删除属性deleteFlag、createTime、createdBy、updateTime四个属性
  3. 删除com.baomidou.mybatisplus.annotation相关注解内容
  4. id增加注解@NotBlank(message = "id不能为空"),控制更新时id必传
  5. 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

  1. 从com.example.demo.model.SystemLog复制得到
  2. 删除属性deleteFlag一个属性
  3. 删除com.baomidou.mybatisplus.annotation相关注解内容
  4. 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

  1. 从com.example.demo.model.SystemLog复制得到
  2. 删除属性deleteFlag一个属性
  3. 删除com.baomidou.mybatisplus.annotation相关注解内容
  4. id类型改为String,解决前端失真问题
  5. 这里和详情返回字段相同,实际业务场景分页仅需要部分字段,可以根据需求进行删除字段
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<SystemLogPageResp> convert2PageResp(Page<SystemLog> page);
}

6. 改造出入参SystemLogController

  1. save方法入参修改为SystemLogSaveCommand,使用SystemLogConverter转换为SystemLog执行后续操作,可以拷贝所有属性到SystemLog
  2. updateById方法入参修改为SystemLogUpdateCommand,使用SystemLogConverter转换为SystemLog执行后续操作,可以拷贝所有属性到SystemLog,同时将id从String转换为Long参与后续逻辑
  3. getById方法出参使用SystemLogConverter转换为SystemLogDetailResp后返回,可以把id转换为String,同时不返回deleteFlag属性
  4. 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;

/**
 * <p>
 * 系统日志表 前端控制器
 * </p>
 *
 * @author chenzl
 * @since 2023-10-11
 */
@RestController
@RequestMapping("/systemLog")
@Api(tags = "系统日志表")
public class SystemLogController {

    @Resource
    private SystemLogService systemLogService;

    @PostMapping("/save")
    @ApiOperation(value = "新增系统日志表")
    public RestResult<String> 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<Boolean> removeById(String id) {
        boolean success = systemLogService.removeById(Long.parseLong(id));
        return RestResult.getSuccessResult(success);
    }

    @PostMapping("/updateById")
    @ApiOperation(value = "根据id更新系统日志表")
    public RestResult<Boolean> 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<SystemLogDetailResp> 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<SystemLogPageResp>> page(Integer pageNum, Integer pageSize) {
        Page<SystemLog> page = systemLogService.page(new Page<>(pageNum, pageSize));
        Page<SystemLogPageResp> resp = SystemLogConverter.INSTANCE.convert2PageResp(page);
        return RestResult.getSuccessResult(resp);
    }
}

六、添加乐观锁拦截器

  1. 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;
    }
}

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

  1. 自动获取当前时间填充创建和更新时间(无值时有效)
  2. 创建人和更新人写死为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");
    }
}

八、项目地址

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

九、结尾

十、参考文章

  1. 乐观锁插件
  2. 自动填充功能
  3. mapstuct官网
转载自:https://juejin.cn/post/7288264434279202850
评论
请登录