Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)

Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)

项目介绍

​ 大家好,我们现在java开发大部分项目都是使用Springcloud 和Springboot做的微服务架构,我使用SpringBoot也有很长的一段时间了,今天和大家分享一下我们在使用Springboot开发一个项目的时候的通用配置,包括接口参数的校验,数据库连接,连接池的配置,MybatisPlus的配置,使用,属性的自动插入,Swagger的集成配置,常用的配置类,全局异常的配置和处理,属性的复制方法,抽取公共属性,减少代码开发,提高我们的开发效率等等。也欢迎大家提出宝贵的意见,互相学习。好了,废话不多说,我们现在开始。

SpringBoot项目搭建

创建SpringBoot项目一般有两种方式,一种是在Spring官网,另一种就是自己在IDE(比如IDEA)上边自己构使用maven构建,然后自己引入依赖。
先看一下我们的项目整体的结构:
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第1张图片

在此我建议初学者使用第二种方式搭建SpringBoot项目,这样的话有利于初学者更加的熟悉SpringBoot的优势和使用SpringBoot开发的流程。有经验的人可以直接在Spring官网生成SpringBoot项目。接下来分别介绍一下这两种方式:

Spring官网

地址:https://spring.io/
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第2张图片
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第3张图片

Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第4张图片
依赖这里可以先不选,后边有详细的pom文件
点击生成就可以了。

maven构建

IDEA中 file -> new -> project
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第5张图片
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第6张图片
点击finish就好了。

POM依赖

我们的SpringBoot项目已经进行了初始化,但是现在里边什么都没有,启动没有问题。我们现在开始导入依赖。先说一下需要什么依赖:

  1. 数据库使用mysql,需要mysql的驱动依赖。
  2. 我们是一个web项目,还要SpringBoot的web依赖。
  3. 数据源使用hikari,需要hikari的依赖。
  4. 参数校验使用hibernate的validator进行参数一系列的校验,需要javax.validation的依赖。
  5. 我们对数据库的操作使用MybatisPlus,需要MybatisPlus的依赖
  6. 代码的自动生成,需要MybatisPlus的自动生成代码的插件,需要mybatis-plus-generator和freemarker的依赖
  7. 接口文档使用swagger,需要引入swagger2的依赖
  8. 我们需要对bean的属性复制,需要引入dozer的依赖(也可以使用其他的,看自己喜欢)
  9. 我们还要使用lombok注解,需要引入lombok依赖
  10. 我们需要对bean与Json的转化,我们使用谷歌的Gson,引入gson依赖。
  11. 我们需要使用切面编程,还要引入 aspectj的切面依赖。

依赖我们都介绍完了,我们来看一下我们的整体的pom文件:


<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.3.9.RELEASEversion>
        <relativePath/> 
    parent>
    <groupId>com.bootgroupId>
    <artifactId>testartifactId>
    <version>1.0-SNAPSHOTversion>

    <properties>
        <maven.compiler.source>8maven.compiler.source>
        <maven.compiler.target>8maven.compiler.target>
    properties>

    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.apache.logging.log4jgroupId>
                    <artifactId>log4j-apiartifactId>
                exclusion>
            exclusions>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-configuration-processorartifactId>
            <optional>trueoptional>
        dependency>

        <dependency>
            <groupId>javax.persistencegroupId>
            <artifactId>javax.persistence-apiartifactId>
            <version>2.2version>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-freemarkerartifactId>
        dependency>

        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-aopartifactId>
        dependency>
        

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>

        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.4.1version>
        dependency>
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-generatorartifactId>
            <version>3.4.0version>
        dependency>
        <dependency>
            <groupId>org.freemarkergroupId>
            <artifactId>freemarkerartifactId>
            <version>2.3.30version>
        dependency>
        <dependency>
            <groupId>org.apache.velocitygroupId>
            <artifactId>velocity-engine-coreartifactId>
            <version>2.0version>
        dependency>
        <dependency>
            <groupId>com.zaxxergroupId>
            <artifactId>HikariCPartifactId>
        dependency>
        
        
        <dependency>
            <groupId>net.sf.dozergroupId>
            <artifactId>dozerartifactId>
            <version>5.4.0version>
            <exclusions>
                
                <exclusion>
                    <groupId>org.slf4jgroupId>
                    <artifactId>slf4j-log4j12artifactId>
                exclusion>
            exclusions>
        dependency>
        
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger2artifactId>
            <version>2.9.2version>
        dependency>

        <dependency>
            <groupId>io.springfoxgroupId>
            <artifactId>springfox-swagger-uiartifactId>
            <version>2.9.2version>
        dependency>
        


        
        <dependency>
            <groupId>com.esotericsoftwaregroupId>
            <artifactId>reflectasmartifactId>
            <version>1.11.1version>
        dependency>
        


        
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
        dependency>


        <dependency>
            <groupId>cn.hutoolgroupId>
            <artifactId>hutool-allartifactId>
            <version>4.1.14version>
        dependency>

        <dependency>
            <groupId>com.google.code.gsongroupId>
            <artifactId>gsonartifactId>
            <version>2.8.6version>
        dependency>

        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.9.7version>
        dependency>

        
        <dependency>
            <groupId>javax.validationgroupId>
            <artifactId>validation-apiartifactId>
            <version>1.1.0.Finalversion>
        dependency>
        <dependency>
            <groupId>org.hibernategroupId>
            <artifactId>hibernate-validatorartifactId>
            <version>5.4.1.Finalversion>
        dependency>
    dependencies>

project>

YML配置文件

pom文件配置完了,我们还需要有一个SpringBoot项目的整体配置文件,常用的有yml,yaml,properties三种文件格式,yml,yaml配置基本一模一样,只不过文件后缀不一样,properties就是属性配置文件。三者格式虽然不同,但是内容是一致的,这个很简单,就不一一介绍了。

SpringBoot的配置文件一般命名为application.yml。在我们开发的时候,一般都会把配置文件按照环境进行区分,还有一个总的配置文件用来指定使用哪一个环境的配置文件,使用spring.profiles.active属性进行区分。而另一个配置文件使用application-环境名.yml的名称,比如开发环境就命名为application-dev.yml。

当然现在为了开发的便利和高效,也是为了方便项目的部署,配置文件和项目的解耦,也出现了越来越多的配置管理组件,比如阿里的nacos, 携程的apollo组件,还有config组件等等。在这里为了简化,并没有进行配置中心的集成,后续我可以出一篇文章,喜欢的大家可以留言。

主配置文件:application.yml

# 端口号
server:
  port: 8088

# 指定环境
spring:
  profiles:
    active: dev

开发环境配置文件:application-dev.yml

## dataSource
hikari:
  jdbcurl: jdbc:mysql://localhost:3306/boot?useUnicode=true&characterEncoding=utf8&useSSL=false&charset=utf-8&autoReconnect=true&zeroDateTimeBehavior=convertToNull
  username: root
  password: root
  driverclassname: com.mysql.cj.jdbc.Driver
  maximumPoolSize: 20
  idleTimeout: 60000
  connectTimeout: 5000
  maxLifetime: 1800000
  connection-init-sql: SET NAMES utf8mb4
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8


# 日志级别配置
logging:
  level:
    ROOT: INFO


# Mybatis-Plus
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 完整sql打印
    map-underscore-to-camel-case: true
  mapper-locations: classpath*:mapper/*.xml # 指定mapper的xml路径
  type-aliases-package: com.boot.test.entity
  global-config:
    db-column-underline: true
    db-config:
      logic-delete-value: 1 # 逻辑删除标致的值
      logic-not-delete-value: 0

对于上边的配置不清楚的同学不要着急,后边我会细致的进行说明的。

接下来我们先写一个简单的接口,再慢慢的去优化我们的代码。

如何写好一个接口

在我们的开发中都是在编写接口,所以要写好一个接口是很重要的。一般我们是操作数据库来完成我们的业务逻辑,接口本质上还是对数据库的CRUD.那么如何写好一个接口呢?跟着我一步步的来看。

首先我们先建一个用户表对它进行操作:

sql脚本:

/*
 Navicat Premium Data Transfer

 Source Server         : local
 Source Server Type    : MySQL
 Source Server Version : 50717
 Source Host           : localhost:3306
 Source Schema         : boot

 Target Server Type    : MySQL
 Target Server Version : 50717
 File Encoding         : 65001

 Date: 29/07/2022 16:05:50
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  `sex` tinyint(2) NULL DEFAULT NULL,
  `deleted` tinyint(4) NOT NULL DEFAULT 0 COMMENT '删除标志:0:未删除,1:已删除',
  `create_time` datetime NULL DEFAULT NULL,
  `create_by` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `update_time` datetime NULL DEFAULT NULL,
  `update_by` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

自动生成代码

为了我们的快速开发,controller,service,mapper我们使用mybatisplus的自动生成功能进行自动生成,大大的加快了我们的开发速度。我们可以写一个自动生成的工具类:

CodeGenerator.java

package com.boot.test.generator;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import java.util.ArrayList;

/**
 * mybatisplus 根据数据库表生成实体类,controller,service,dao等类
 */
public class CodeGenerator {

    public static void main(String[] args) {
        // 生成单个表使用这个
        String[] tables = {"user"};
        // 生成多个表使用这个,每个表逗号分隔
        // String[] tables = {"user", "dept"};

        String property = System.getProperty("user.dir");
        String projectName = property.substring(property.lastIndexOf("\\")+1);

        String propertPath = new CodeGenerator().getClass().getResource("/").getPath().replace("/target/classes/", "");
        System.out.println(propertPath);
//        需要构架一个代码生成器对象
        AutoGenerator mpg = new AutoGenerator();

        //2.全局配置
        GlobalConfig gc = new GlobalConfig();
        gc.setOutputDir(propertPath+"/src/main/java")
        .setAuthor("WangChunQiu")
        .setOpen(false) //是否打开资源管理器
        .setFileOverride(true) //是否覆盖
        .setServiceName("%sService")
        .setIdType(IdType.AUTO)
        .setDateType(DateType.ONLY_DATE);

        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/boot?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&autoReconnect=true&useSSL=false")
        .setDriverName("com.mysql.cj.jdbc.Driver")
        .setUsername("root")
        .setPassword("root")
        .setDbType(DbType.MYSQL);


        PackageConfig pc = new PackageConfig();
        pc.setParent("com.boot" + "." + projectName)
        .setEntity("entity")
        .setMapper("mapper")
        .setService("service")
        .setController("controller");

        StrategyConfig sc = new StrategyConfig();
        sc.setInclude(tables) //此处设置要生成的数据库表
        .setNaming(NamingStrategy.underline_to_camel)
        .setColumnNaming(NamingStrategy.underline_to_camel)
        .setEntityLombokModel(true);
        TableFill creatTime = new TableFill("creat_time", FieldFill.INSERT);
        TableFill updateTime = new TableFill("update_time", FieldFill.INSERT_UPDATE);
        ArrayList<TableFill> obs = new ArrayList<>();
        obs.add(creatTime);
        obs.add(updateTime);
        sc.setTableFillList(obs)
        .setRestControllerStyle(true)
        .setControllerMappingHyphenStyle(true);

        mpg.setGlobalConfig(gc)
        .setPackageInfo(pc)
        .setDataSource(dsc)
        .setStrategy(sc);
        //执行
        mpg.execute();
    }
}

我们来说一下这个怎么用,需要先把数据库的配置改成你自己的,然后把包路径改成你自己的,我这个是自动获取了项目的名称,和包路径进行了拼接,如果你的包路径不是这个需要修改一下。最后就是要给那个表生成,在tables属性配置,表名写在这个数组里,多个使用逗号分隔就好了,执行main方法就会自动生成了。

代码优化

上边生成的包结构需要细微的调整一下。

service层

先说一下service层,我们在开发的时候在service包下一般还有一个impl放包的实现类对吧?但是如果是这样的结构,service是一个接口,serviceimpl实现service,那么service是一个接口,不可以写方法体,那么我们在实际的开发中就需要把所有的代码逻辑写在serviceimpl中,代码都是一坨坨,让别人读自己的代码就会很费劲,让人看了就很不舒服。我们要做到的是service层只做一件自己应该做的事,每一个方法就做一件事,也就是设计模式的单一原则。这样的话拓展性高,可以让其他接口和服务方便调用,实现可插拔性,也减少了代码的冗余和耦合度,不需要重复多次写功能相似甚至是一样的方法,否则这样程序员的负担是相当大的。

好,那我们接下来进行改造:

  1. 将生成的service接口删掉。
    在这里插入图片描述

  2. ServiceImpl中的实现删掉,并把它移到service包中,删掉impl包。
    在这里插入图片描述
    4这样就可以了Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第7张图片 新建biz包,然后新建userBiz类,使用@Service标注类。
    在这里插入图片描述

biz是我们真正的业务逻辑处理的类,而service主要是作为与mapper和数据库交互的媒介。这样就改好了。

mapper层

  1. 我们需要把xml文件移到resource目录下,新建一个文件夹mapper,然后把生成的mapper.xml文件移动到这个文件夹下。

  2. Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第8张图片

  3. 配置文件修改:在刚刚的配置文件中需要指定mapper.xml的所在目录,其实刚刚的文件已经指定好了。

  4. Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第9张图片

  5. 在主启动类上加上@MapperScan注解,并制定mapper的包路径,如:

Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第10张图片

配置类

我们的数据库和一部分代码生成好了, 项目结构也改造好了,还有一些配置类需要加上,比如数据库配置,swagger的配置,还有MyBatisPlus的配置。这一步我们先配置这几个,其他的后续需要用的话再补充。

配置类都放在config包下:

数据源配置:HikariCPConfig.java

package com.boot.test.config;

import com.zaxxer.hikari.HikariDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;

import javax.sql.DataSource;

@Configuration
@ConfigurationProperties(prefix = "hikari")
public class HikariCPConfig {
    @Value("${hikari.driverclassname}")
    private String driverClassName;

    @Value("${hikari.jdbcurl}")
    private String jdbcUrl;

    @Value("${hikari.username}")
    private String userName;

    @Value("${hikari.password}")
    private String password;

    @Value("${hikari.maximumPoolSize}")
    private int maximumPoolSize;

    @Value("${hikari.idleTimeout}")
    private long idleTimeout;

    @Value("${hikari.connectTimeout}")
    private long connectTimeout;

    @Value("${hikari.maxLifetime}")
    private long maxLifetime;

    @Primary
    @Bean(name = "dataSource")
    public DataSource dataSource() {
        final HikariDataSource hds = new HikariDataSource();
        hds.setJdbcUrl(jdbcUrl);
        hds.setDriverClassName(driverClassName);
        hds.setUsername(userName);
        hds.setPassword(password);
        hds.setMaximumPoolSize(maximumPoolSize);
        hds.setIdleTimeout(idleTimeout);
        hds.setConnectionTimeout(connectTimeout);
        hds.setMaxLifetime(maxLifetime);
        return hds;
    }


}

swagger配置:SwaggerConfig.java

package com.boot.test.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;


/**
 * swagger config.
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig implements WebMvcConfigurer {

    /**
     * add resouces.
     *
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    /**
     * inject docket.
     *
     * @return
     */
    @Bean
    public Docket createRestApi() {

        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.boot.test.controller"))
                .paths(PathSelectors.any())
                .build()
                // 解决 $ref values must be RFC3986-compliant percent-encoded URIs
                .enableUrlTemplating(false)
                .forCodeGeneration(true);
    }

    /**
     * api info.
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("mpt-social-campaign-planning-service")
                .description("MPT SOCIAL CAMPAIGN PLANNING API")
                .version("1.0.0")
                .build();
    }

}

mybatisplus配置:MyBatisPlusConfig.java

package com.boot.test.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Date;

@Configuration
public class MyBatisPlusConfig {

    /**
     * mybatisplus 分页插件
     * @return
     */
    @Bean
    public MybatisPlusInterceptor sqlInjector() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setOverflow(false);
        paginationInnerInterceptor.setMaxLimit(500L);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }
}

编写接口-restful风格

接口编写要遵循restful风格,不了解的小伙伴可以进行百度一下。

准备工作都做好了,现在我们可以编写接口了。因为现在的数据库中还没有数据,我们就先写一个添加用户的接口。

dto:

package com.boot.test.dto;

import lombok.Data;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotNull;

@Data
@Accessors(chain = true)
public class UserDTO {
    @NotNull(message = "姓名不能为空")
    private String name;

    private Integer age;

    private Integer sex;
}

controller层:

package com.boot.test.controller;


import com.boot.test.biz.UserServiceBiz;
import com.boot.test.dto.UserDTO;
import com.boot.test.entity.User;
import com.boot.test.vo.ApiResponse;
import com.boot.test.vo.BasePageVO;
import com.boot.test.vo.PageVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 

* 前端控制器 *

* * @author WangChunQiu * @since 2022-07-29 */
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserServiceBiz userServiceBiz; @PostMapping("/add-test") public Boolean testAdd(@RequestBody UserDTO userDTO) { return userServiceBiz.testAdd(userDTO); } }

biz层:UserServiceBiz.java

package com.boot.test.biz;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.boot.test.convert.PageConvert;
import com.boot.test.dto.UserDTO;
import com.boot.test.entity.User;
import com.boot.test.service.UserService;
import com.boot.test.vo.ApiResponse;
import com.boot.test.vo.BasePageVO;
import com.boot.test.vo.PageVO;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

/**
 * 

* 服务实现类 *

* * @author WangChunQiu * @since 2022-07-29 */
@Service @Slf4j public class UserServiceBiz { @Autowired private UserService userService; User user = new User(); user.setAge(userDTO.getAge()); user.setName(userDTO.getName()); user.setSex(userDTO.getSex()); return userService.add(user); }

service层:

package com.boot.test.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.boot.test.dto.UserDTO;
import com.boot.test.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;
import com.boot.test.mapper.UserMapper;
import org.apache.commons.lang3.StringUtils;
import org.dozer.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 

* 服务类 *

* * @author WangChunQiu * @since 2022-07-29 */
@Service public class UserService extends ServiceImpl<UserMapper, User> { public Boolean add(UserDTO userDTO) { return save(user); } }

接口到现在就算是结束了,刚刚我们已经配置了swagger,现在我们访问http://localhost:8088/swagger-ui.html就可以看到我们刚刚的接口了。填写数据测试一下。

查看数据库添加成功,接口功能算是实现了,但是看我们的代码和返回结果还是有很多的问题的。

  1. user的属性设置繁琐,需要一个个的设置。
  2. 添加时间创建时间都要自己手动设置。
  3. 返回值只有一个true,结果说因为某些原因添加失败呢,只返回一个false?别人是不知道到底是什么意思的,需要有错误信息高速调用者是怎么回事。
  4. 接口的参数没有校验。

那我们接下来一个个的解决。

属性复制

我们可以吧有相同属性的类里边的属性进行复制,不需要像刚刚我们一个个的属性进行get,set,属性值少还好,如果很多呢是不是很费劲呢?所以我们要使用Dozer的属性复制。
首先配置类:DozerBeanMapperConfig.java

package com.boot.test.config;

import org.dozer.DozerBeanMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DozerBeanMapperConfig {

    @Bean
    public DozerBeanMapper mapper() {
        return new DozerBeanMapper();
    }
}

在使用的时候我们使用@Autowired把Mapper注入就好了。我们改造一下我们刚刚的代码:
biz层:UserServiceBiz.java

package com.boot.test.biz;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.boot.test.convert.PageConvert;
import com.boot.test.dto.UserDTO;
import com.boot.test.entity.User;
import com.boot.test.service.UserService;
import com.boot.test.vo.ApiResponse;
import com.boot.test.vo.BasePageVO;
import com.boot.test.vo.PageVO;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.dozer.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

/**
 * 

* 服务实现类 *

* * @author WangChunQiu * @since 2022-07-29 */
@Service @Slf4j public class UserServiceBiz { @Autowired private UserService userService; @Autowired private Gson gson; @Autowired private Mapper mapper; public Boolean testAdd(UserDTO userDTO) { User user = mapper.map(userDTO, User.class); log.info(gson.toJson(user)); return userService.add(user); } }

控制台打印结果:
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第11张图片
说明属性复制生效了,但是属性复制应该类型和名字都是一致的,一定要注意哦

创建时间自动插入

我们刚刚是实现了基本的插入,但是我们没有设置创建时间和创建人呢,数据库里也没有设置默认的,这种和我们业务不是很大关系的字段其实是可以使用自动插入的。
在实现自动插入之前,还有个问题,就是主键id,创建时间,创建人,更新时间,更新人这些字段应该每一张表都是有的,我们其实是可以把这些字段抽取出来的。我们可以建一个基类来记录这些属性,具体的实体类集成它就好了。
那我们来实现一下:
基类:BaseEntity.java

package com.boot.test.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class BaseEntity implements Serializable {

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    private String createBy;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    private String updateBy;

    @TableLogic
    private Integer deleted;
}

user类删掉id,创建时间,创建人,更新时间,更新人这些属性:

package com.boot.test.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import java.util.Date;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import java.io.Serializable;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 

* *

* * @author WangChunQiu * @since 2022-07-29 */
@Data @EqualsAndHashCode(callSuper = false) public class User extends BaseEntity { private String name; private Integer age; private Integer sex; }

这样就可以了。
接下来实现自动插入:

配置类修改:MyBatisPlusConfig.java

package com.boot.test.config;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Date;

@Configuration
public class MyBatisPlusConfig {

    /**
     * mybatisplus 分页插件
     * @return
     */
    @Bean
    public MybatisPlusInterceptor sqlInjector() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setOverflow(false);
        paginationInnerInterceptor.setMaxLimit(500L);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }

    /**
     * 自动更新时间
     *
     * @return
     */
    @Bean
    public MetaObjectHandler metaObjectHandler() {
       return new MetaObjectHandler() {
           @Override
           public void insertFill(MetaObject metaObject) {
           		// 插入的时候设置添加和修改时间的值
               this.strictInsertFill(metaObject, "createTime", Date.class, new Date());
               this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
           }

           @Override
           public void updateFill(MetaObject metaObject) {
           		// 更新的时候设置修改时间的值
               this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
           }
       };
    }
}

当然,我的这个没有集成登录,你们根据自己的业务设置更新人和添加人。还有就是添加时间和更新时间的属性上需要添加注解@TableField,因为我们刚刚设置了基类:所以就应该是这样的
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第12张图片
接下来我们测试一下,看我们的时间是不是可以实现自动插入
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第13张图片
swagger是没有传时间参数的,请求接口控制台打印:
在这里插入图片描述
在看数据库时间也是插入了,这个问题也解决了。

接口的返回值优化

在这里插入图片描述
我们可以看到返回值只有一个true,没有信息,也没有状态码,感觉太突兀的,所以我们需要再封装一层,让接口的返回值更加友好。我们先写一个公共的返回值对象:
ApiResponse.java

package com.boot.test.vo;

import com.boot.test.enums.BizExceptionEnum;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

import java.io.Serializable;

@Getter
@Setter
public class ApiResponse<T> implements Serializable {
    private boolean result;
    private int code;
    private String msg;
    private T data;
    @Builder.Default
    private Long timestamp = System.currentTimeMillis();

    private static ApiResponse build(Object data, boolean isSuccess, Integer code, String msg){
        ApiResponse response = new ApiResponse();
        response.setData(data);
        response.setResult(isSuccess);
        response.setCode(code);
        response.setMsg(msg);
        return response;
    }


    public static ApiResponse buildSuccess(Object data) {
        return build(data,Boolean.TRUE, BizExceptionEnum.SUCCESS.getCode(), BizExceptionEnum.SUCCESS.getMsg());
    }

    public static ApiResponse buildSuccess() {
        return build(null,Boolean.TRUE, BizExceptionEnum.SUCCESS.getCode(), BizExceptionEnum.SUCCESS.getMsg());
    }

    public static ApiResponse buildSuccess(String msg) {
        return build(null,Boolean.TRUE, BizExceptionEnum.SUCCESS.getCode(), msg);
    }

    public static ApiResponse buildFailure() {
        return build(null,Boolean.TRUE, BizExceptionEnum.UNKNOWN_ERROR.getCode(), BizExceptionEnum.UNKNOWN_ERROR.getMsg());
    }

    public static ApiResponse buildFailure(String errorMsg) {
        return build(null,Boolean.FALSE, BizExceptionEnum.UNKNOWN_ERROR.getCode(), errorMsg);
    }

    public static ApiResponse buildFailure(BizExceptionEnum bizExceptionEnum) {
        return build(null,Boolean.FALSE, bizExceptionEnum.getCode(), bizExceptionEnum.getMsg());
    }

    public static ApiResponse buildFailure(Integer errorCode, String errorMsg) {
        return build(null,Boolean.FALSE, errorCode, errorMsg);
    }
}

再定义一个错误枚举类:BizExceptionEnum.java

package com.boot.test.enums;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public enum BizExceptionEnum {
    SUCCESS(0, "SUCCESS", "成功!"),
    UNKNOWN_ERROR(9999, "UNKNOWN_ERROR", "未知异常!"),
    DB_CONNECT_ERROR(10000, "DB_CONNECT_ERROR", "数据库连接异常!"),
    DATA_NOT_FOUND_ERROR(10001, "DATA_NOT_FOUND_ERROR", "数据不存在!"),
    DATA_EXIST_ERROR(10002, "DATA_EXIST_ERROR", "数据已存在!"),
    DB_NOT_SUPPORT_ERROR(10003, "DB_NOT_SUPPORT_ERROR", "暂不支持此数据库!"),
    PARAM_ERROR                     (4001,"PARAM_ERROR","参数不正确"),
    SOURCE_NOT_FOUND_ERROR(10004, "SOURCE_NOT_FOUND_ERROR", "请求资源不存在!");


    private int code;
    private String index;
    private String msg;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getIndex() {
        return index;
    }

    public void setIndex(String index) {
        this.index = index;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

当然这个是根据自己的业务进行的一些调整枚举值,不够的再去加就好了。
我们使用ResponseEntity作为接口最外层的返回值,所以接口改造就是这样的:

package com.boot.test.controller;


import com.boot.test.biz.UserServiceBiz;
import com.boot.test.dto.UserDTO;
import com.boot.test.entity.User;
import com.boot.test.vo.ApiResponse;
import com.boot.test.vo.BasePageVO;
import com.boot.test.vo.PageVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 

* 前端控制器 *

* * @author WangChunQiu * @since 2022-07-29 */
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserServiceBiz userServiceBiz; @PostMapping("/add-test") public ResponseEntity<ApiResponse<Boolean>> testAdd(@RequestBody UserDTO userDTO) { return new ResponseEntity(ApiResponse.buildSuccess(userServiceBiz.add(userDTO)), HttpStatus.OK); } }

调用测试:
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第14张图片
这样就可以的了。

参数校验

参数校验我们使用validation,假如我们添加的时候名字不能为空。我们只需要在实体类加上@NotNull注解就可以了

package com.boot.test.dto;

import lombok.Data;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotNull;

@Data
@Accessors(chain = true)
public class UserDTO {
    @NotNull(message = "姓名不能为空")
    private String name;
    
    private Integer age;
    
    private Integer sex;
}

在接口层的 参数上加上@Validated注解:

package com.boot.test.controller;


import com.boot.test.biz.UserServiceBiz;
import com.boot.test.dto.UserDTO;
import com.boot.test.entity.User;
import com.boot.test.vo.ApiResponse;
import com.boot.test.vo.BasePageVO;
import com.boot.test.vo.PageVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 

* 前端控制器 *

* * @author WangChunQiu * @since 2022-07-29 */
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserServiceBiz userServiceBiz; @PostMapping("/add-test") public ResponseEntity<ApiResponse<Boolean>> testAdd(@RequestBody @Validated UserDTO userDTO) { return new ResponseEntity(ApiResponse.buildSuccess(userServiceBiz.add(userDTO)), HttpStatus.OK); } }

接口测试:
传参:
在这里插入图片描述
控制台打印:在这里插入图片描述

返回结果,
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第15张图片
是报错了,但是呢有点看不懂,所以我们还要对我们的异常进行统一处理,在spring框架有一个注解@RestControllerAdvice可以对我们的异常进行全局处理,并且返回更加友好的错误提示。
全局异常类:

package com.boot.test.config;

import com.boot.test.enums.BizExceptionEnum;
import com.boot.test.exception.BizException;
import com.boot.test.vo.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class MyGlobalExceptionHandler {

    private static final String VALIDATION_FAILED = "validation_failed";


    // 422 请求参数未通过验证错误
    // 方法参数校验错误

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("MethodArgumentNotValidException", e);
		// 根据上边的异常的报错信息我们可以拿到我们设置的异常信息方法是 e.getBindingResult().getFieldError().getDefaultMessage()
        return new ResponseEntity<>(ApiResponse.buildFailure(e.getBindingResult().getFieldError().getDefaultMessage()), new HttpHeaders(), HttpStatus.UNPROCESSABLE_ENTITY);
    }
}

再测试一下:
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第16张图片
这样的返回结果是不是就很友好?
在全局的异常配置里,根据你的抛出的异常配置。
我这里有一个自定义异常的例子可以看下:

package com.boot.test.exception;


import com.boot.test.enums.BizExceptionEnum;

public class BizException extends RuntimeException{

    private Integer code;
    private String msg;
    private BizExceptionEnum errorEnum;
    /**
     * 详细错误信息
     */
    private String detailErrorMsg;

    public BizException(BizExceptionEnum errorEnum) {
        this.errorEnum = errorEnum;
    }

    public BizException(BizExceptionEnum errorEnum, String detailErrorMsg) {
        this.code = errorEnum.getCode();
        this.msg = detailErrorMsg;
        this.errorEnum = errorEnum;
        this.detailErrorMsg = detailErrorMsg;
    }

    public BizException(Integer code, String msg) {
        super(msg);
        this.code = code;
        this.msg = msg;
    }

    public BizException(String msg) {
        super(msg);
        this.code = 500;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public BizExceptionEnum getErrorEnum() {
        return errorEnum;
    }

    public void setErrorEnum(BizExceptionEnum errorEnum) {
        this.errorEnum = errorEnum;
    }

    public String getDetailErrorMsg() {
        return detailErrorMsg;
    }

    public void setDetailErrorMsg(String detailErrorMsg) {
        this.detailErrorMsg = detailErrorMsg;
    }
}

异常处理类:

package com.boot.test.config;

import com.boot.test.enums.BizExceptionEnum;
import com.boot.test.exception.BizException;
import com.boot.test.vo.ApiResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@Slf4j
@RestControllerAdvice
public class MyGlobalExceptionHandler {

    private static final String VALIDATION_FAILED = "validation_failed";

    @ExceptionHandler(value = BizException.class)
    public ResponseEntity<ApiResponse> handleBizException(BizException e) {
        log.error("BizException", e);
        BizExceptionEnum errorEnum = e.getErrorEnum();
        String detailErrorMsg = e.getDetailErrorMsg();
        Integer code = e.getCode();
        if (errorEnum != null && detailErrorMsg == null) {
            detailErrorMsg = errorEnum.getMsg();
            code = errorEnum.getCode();
        }
        return new ResponseEntity(ApiResponse.buildFailure(code, detailErrorMsg), new HttpHeaders(), HttpStatus.OK);
    }


    // 422 请求参数未通过验证错误
    // 方法参数校验错误

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity<ApiResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        log.error("MethodArgumentNotValidException", e);
        return new ResponseEntity<>(ApiResponse.buildFailure(e.getBindingResult().getFieldError().getDefaultMessage()), new HttpHeaders(), HttpStatus.UNPROCESSABLE_ENTITY);
    }


}

这样写就可以啦!

分页

最后我们再说一下mybatisplus的分页吧!
刚刚我们已经配置了分页,我们对user的分页进行查询。

controller:

package com.boot.test.controller;


import com.boot.test.biz.UserServiceBiz;
import com.boot.test.dto.UserDTO;
import com.boot.test.entity.User;
import com.boot.test.vo.ApiResponse;
import com.boot.test.vo.BasePageVO;
import com.boot.test.vo.PageVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 

* 前端控制器 *

* * @author WangChunQiu * @since 2022-07-29 */
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserServiceBiz userServiceBiz; @GetMapping("/list") // 分页 public ResponseEntity<ApiResponse<BasePageVO<User>>> list(@RequestParam(value = "name", required = false) String name, @RequestParam(value = "pageNum", required = false, defaultValue = "1") Long pageNum, @RequestParam(value = "pageSize", required = false, defaultValue = "30") Long pageSize) { return new ResponseEntity(ApiResponse.buildSuccess(userServiceBiz.list(name, pageNum, pageSize)), HttpStatus.OK); } @PostMapping("/add-test") public ResponseEntity<ApiResponse<Boolean>> testAdd(@RequestBody @Validated UserDTO userDTO) { return new ResponseEntity(ApiResponse.buildSuccess(userServiceBiz.add(userDTO)), HttpStatus.OK); } }

biz:

package com.boot.test.biz;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.boot.test.convert.PageConvert;
import com.boot.test.dto.UserDTO;
import com.boot.test.entity.User;
import com.boot.test.service.UserService;
import com.boot.test.vo.ApiResponse;
import com.boot.test.vo.BasePageVO;
import com.boot.test.vo.PageVO;
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.dozer.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;

/**
 * 

* 服务实现类 *

* * @author WangChunQiu * @since 2022-07-29 */
@Service @Slf4j public class UserServiceBiz { @Autowired private UserService userService; @Autowired private Gson gson; @Autowired private Mapper mapper; public BasePageVO list(String name, Long pageNum, Long pageSize) { Page<User> page = new Page<>(pageNum, pageSize); Page<User> userPage = userService.getPage(page, name); BasePageVO<User> basePageVO = new BasePageVO<>(); PageVO pageVO = PageConvert.convertPageVO(userPage); pageVO.setRecords(userPage.getRecords()); basePageVO.setPage(pageVO); String json = gson.toJson(pageVO); log.info(json); return basePageVO; } public Boolean testAdd(UserDTO userDTO) { User user = mapper.map(userDTO, User.class); log.info(gson.toJson(user)); return userService.add(user); } }

service:

package com.boot.test.service;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.boot.test.dto.UserDTO;
import com.boot.test.entity.User;
import com.baomidou.mybatisplus.extension.service.IService;
import com.boot.test.mapper.UserMapper;
import org.apache.commons.lang3.StringUtils;
import org.dozer.Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 

* 服务类 *

* * @author WangChunQiu * @since 2022-07-29 */
@Service public class UserService extends ServiceImpl<UserMapper, User> { public List<User> getAll() { return baseMapper.getAll(); } public Page<User> getPage(Page<User> page, String name) { boolean notEmpty = StringUtils.isNotEmpty(name); return page(page, new LambdaQueryWrapper<User>().like(notEmpty, User::getName, name)); } }

vo:

package com.boot.test.vo;

import lombok.Data;

import java.util.Collections;
import java.util.List;

/**
 * @Author: liuyaxu
 * @Date: 2022/1/5
 * @Description:分页VO
 */
@Data
public class PageVO<T> {

    protected List<T> records;
    protected long total;
    protected long pageSize;
    protected long pageNum;

    public PageVO() {
        this.records = Collections.emptyList();
        this.total = 0L;
        this.pageSize = 10L;
        this.pageNum = 1L;
    }

    public PageVO(List<T> records, long total, long size, long current) {
        this.records = records;
        this.total = total;
        this.pageSize = size;
        this.pageNum = current;
    }
}

package com.boot.test.vo;

import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class BasePageVO<T> {

    private PageVO<T> page;

}

package com.boot.test.convert;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.boot.test.vo.PageVO;
import com.google.gson.Gson;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;


/**
 * @Author: liuyaxu
 * @Date: 2022/1/5
 * @Description:
 */
public class PageConvert {


    public static PageVO convertPageVO(Page page) {
        PageVO<Object> pageVO = new PageVO<>();
        if (page == null) {
            return pageVO;
        }
        pageVO.setTotal(page.getTotal());
        pageVO.setPageSize(page.getSize());
        pageVO.setPageNum(page.getCurrent());
        pageVO.setRecords(page.getRecords());
        return pageVO;
    }

    public static <T> PageVO<T> convertPageVO(Page page, Class<T> destClass) {
        PageVO<T> pageVO = new PageVO<>();
        if (page == null) {
            return pageVO;
        }
        pageVO.setTotal(page.getTotal());
        pageVO.setPageSize(page.getSize());
        pageVO.setPageNum(page.getCurrent());
        List records = page.getRecords();
        ArrayList<T> result = new ArrayList<>();
        if (!CollectionUtils.isEmpty(records)) {
            for (Object record : records) {
                T tempObj = BeanUtils.instantiateClass(destClass);
                BeanUtils.copyProperties(record,tempObj);
                result.add(tempObj);
            }
        }
        pageVO.setRecords(result);
        return pageVO;
    }
}

这样就好了,我们对mybatisplus的返回值进行了进一步的封装,来看一下返回值:
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第17张图片

{
  "result": true,
  "code": 0,
  "msg": "成功!",
  "data": {
    "page": {
      "records": [
        {
          "id": 5,
          "createTime": "2022-07-30T01:58:38.000+00:00",
          "createBy": null,
          "updateTime": "2022-07-30T01:58:38.000+00:00",
          "updateBy": null,
          "deleted": 0,
          "name": "1",
          "age": 1,
          "sex": 1
        },
        {
          "id": 6,
          "createTime": "2022-07-30T02:07:36.000+00:00",
          "createBy": null,
          "updateTime": "2022-07-30T02:07:36.000+00:00",
          "updateBy": null,
          "deleted": 0,
          "name": "1",
          "age": 1,
          "sex": 1
        }
      ],
      "total": 2,
      "pageSize": 30,
      "pageNum": 1
    }
  },
  "timestamp": 1659148523691
}

逻辑删除

在我们的开发中是很多时候不是要真的删除数据,而是给一个删除的标志进行逻辑删除,我们刚刚建表的时候有一个deleted就是逻辑删除的标志,我们可以在mybatisplus的配置中进行配置:
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第18张图片
还要在deleted上添加@TableLogic注解

package com.boot.test.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class BaseEntity implements Serializable {

    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;

    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    private String createBy;

    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    private String updateBy;

    @TableLogic
    private Integer deleted;
}

写一个删除接口:

controller:

package com.boot.test.controller;


import com.boot.test.biz.UserServiceBiz;
import com.boot.test.dto.UserDTO;
import com.boot.test.entity.User;
import com.boot.test.vo.ApiResponse;
import com.boot.test.vo.BasePageVO;
import com.boot.test.vo.PageVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 

* 前端控制器 *

* * @author WangChunQiu * @since 2022-07-29 */
@RestController @RequestMapping("/user") public class UserController { @Autowired private UserServiceBiz userServiceBiz; @GetMapping("/list") // 分页 public ResponseEntity<ApiResponse<BasePageVO<User>>> list(@RequestParam(value = "name", required = false) String name, @RequestParam(value = "pageNum", required = false, defaultValue = "1") Long pageNum, @RequestParam(value = "pageSize", required = false, defaultValue = "30") Long pageSize) { return new ResponseEntity(ApiResponse.buildSuccess(userServiceBiz.list(name, pageNum, pageSize)), HttpStatus.OK); } @PostMapping("/add") public ResponseEntity<ApiResponse<Boolean>> add(@RequestBody @Validated UserDTO userDTO) { return new ResponseEntity(ApiResponse.buildSuccess(userServiceBiz.add(userDTO)), HttpStatus.OK); } @PostMapping("/del/{id}") public ResponseEntity<ApiResponse<Boolean>> del(@PathVariable(value = "id") Long id) { return new ResponseEntity(ApiResponse.buildSuccess(userServiceBiz.del(id)), HttpStatus.OK); } @PostMapping("/add-test") public Boolean testAdd(@RequestBody UserDTO userDTO) { return userServiceBiz.testAdd(userDTO); } } biz: package com.boot.test.biz; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.boot.test.convert.PageConvert; import com.boot.test.dto.UserDTO; import com.boot.test.entity.User; import com.boot.test.service.UserService; import com.boot.test.vo.ApiResponse; import com.boot.test.vo.BasePageVO; import com.boot.test.vo.PageVO; import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; import org.dozer.Mapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; /** *

* 服务实现类 *

* * @author WangChunQiu * @since 2022-07-29 */
@Service @Slf4j public class UserServiceBiz { @Autowired private UserService userService; @Autowired private Gson gson; @Autowired private Mapper mapper; public BasePageVO list(String name, Long pageNum, Long pageSize) { Page<User> page = new Page<>(pageNum, pageSize); Page<User> userPage = userService.getPage(page, name); BasePageVO<User> basePageVO = new BasePageVO<>(); PageVO pageVO = PageConvert.convertPageVO(userPage); pageVO.setRecords(userPage.getRecords()); basePageVO.setPage(pageVO); String json = gson.toJson(pageVO); log.info(json); return basePageVO; } public Boolean add(UserDTO userDTO) { User user = mapper.map(userDTO, User.class); return userService.add(user); } public Boolean testAdd(UserDTO userDTO) { User user = new User(); user.setAge(userDTO.getAge()); user.setName(userDTO.getName()); user.setSex(userDTO.getSex()); return userService.add(user); } public boolean del(Long id) { return userService.removeById(id); } }

调用接口查看控制台:
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第19张图片
发现是update语句,数据库也更新成功!
调用分页接口:
控制台在这里插入图片描述
返回结果也没有刚刚主键为6的数据
Spring Boot 项目通用架构搭建(包含MybatisPlus,代码生成,Swagger集成,通用配置,全局异常处理)_第20张图片
逻辑删除就搞定了!

结束语

这样我们今天的内容也就结束了,欢迎大家积极评论,有问题留言私聊都可以,希望大家给个三连谢谢!

你可能感兴趣的:(Spring,java,spring,boot)