Spring + MP + Swagger 完成简单的CURD操作

  • Spring:Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器框架。用来装JavaBean(java对象),中间层框架(万能胶)。
    提供了展现层 SpringMVC和持久层 Spring JdbcTemplate以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。
  • SpringMVC: SpringMvc是Spring的一个模块,基于MVC的一个框架,无需中间整合层来整合 .
    什么是MVC? : 首先请求发送request请求到C(control 接收用户请求响应用户) 然后控制器到M模型(pojo、action、service、dao)层处理 处理结果完了返回控制器 控制器要经过视图渲染 最后返回终端(response)
  • MyBatis: MyBatis 是一款优秀的(ORM)持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
  • MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
    • 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
    • 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
    • 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可*配置,完美解决主键问题
    • 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
    • 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
  • 当前框架整合使用的Spring版本是5.1.5.RELEASE ,MyBatisPlus版本为:3.1.0

文章目录

1. 创建对应的数据库以及数据表

--创建数据库表
create database ssm_db; 
-- 使用数据库
use ssm_db;
--创建用户表
create table tbl_user(
    id int primary key auto_increment ,
	rel_name varchar(25) comment '真实名',
	log_name varchar(25) comment '登陆名',
	log_pwd varchar(50) comment '密码',
	log_status int(2) default 0 comment '登陆状态,0启用,1禁用',
	sex varchar(4) comment '性别',
	birthday date comment '生日',
	create_time timestamp default current_timestamp
);
--默认管理员数据
insert into tbl_user(rel_name,log_name,log_pwd,sex,birthday) values('管理员','admin','123','男','1991-11-12');
--查询数据
select id,rel_name,log_name,log_pwd,log_status,sex,birthday,create_time from tbl_user;

2. 创建web工程

使用IDEA新建一个Maven Web项目,并构建相应的项目架构,config(全局配置),util(帮助工具类),exception(自定义异常类) ,interceptor(自定义拦截器),剩下的(controller,entity,mapper,service)这些目录不用自己定义,MP代码构建工具可以帮我们完成。
Spring + MP + Swagger 完成简单的CURD操作

3. 导入所需要的jar包

初始构建jar包,后续集成其它框架或工具会陆续加入。

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.1.7.RELEASE</spring.version>
    <jackson.verion>2.9.0</jackson.verion>
    <swagger2.version>2.7.0</swagger2.version>
  </properties>

  <dependencies>
    <!--spring架构基础包-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!--spring 数据库支持-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.46</version>
    </dependency>
    <!--数据源-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.14</version>
    </dependency>
    <!--
      myBatisPlus : myBatis 增强包,封装了Mapper,Service,Page 等常用操作,提供了简洁的lambda条件表达式
      myBatisPlus依赖于mybatis于mybatis-spring包,这里它会依赖添加,不用显示添加这两个jar包
      `使用注意不要使用3.1.1版本。否则java8日期序列化会报如下错误:Error attempting to get column 'birthday' from result set.  `
    -->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus</artifactId>
      <version>3.1.0</version>
    </dependency>
    <!-- mybatis 对象java8日期处理的辅助类 -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-typehandlers-jsr310</artifactId>
      <version>1.0.2</version>
    </dependency>
    <!--mybatis-plus代码生成器工具包-->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-generator</artifactId>
      <version>3.1.1</version>
    </dependency>
    <!-- mybatis-plus 代码生成需要的静态模板工具包-->
    <dependency>
      <groupId>org.apache.velocity</groupId>
      <artifactId>velocity-engine-core</artifactId>
      <version>2.1</version>
    </dependency>
    <!--spring整合junit4测试包-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--springMVC-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!--json序列化-->
    <dependency>
      <groupId>com.fasterxml.jackson.core</groupId>
      <artifactId>jackson-databind</artifactId>
      <version>${jackson.verion}</version>
    </dependency>
    <!--json序列化java8日期辅助包-->
    <dependency>
      <groupId>com.fasterxml.jackson.datatype</groupId>
      <artifactId>jackson-datatype-jsr310</artifactId>
      <version>${jackson.verion}</version>
    </dependency>
    <!--
        快速生成Getting,Setting,ToString  
        `使用需要注意:maven tomcat7插件启动项目,不支持lombok1.16.18以上的版本,否则会报 Unable to process Jar entry [module-info.class] from Jar`
    -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.16</version>
    </dependency>
    <!--日志包-->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.26</version>
    </dependency>
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.2.3</version>
    </dependency>
    <!--字符串常用工具包-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.8.1</version>
    </dependency>
    <!-- Swagger2 构建Restful API -->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>${swagger2.version}</version>
    </dependency>
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>${swagger2.version}</version>
    </dependency>

    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>4.0.1</version>
      <scope>provided</scope>
    </dependency>

  </dependencies>
  <build>
    <finalName>ssm</finalName>
    <plugins>
      <!--tomcat7 maven插件-->
      <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
          <port>80</port>
          <path>/</path>
          <uriEncoding>UTF-8</uriEncoding>
          <!--
          <url>http://localhost/manager</url>
          <server>tomcat7</server>
          <username>tomcat</username>
          <password>tomcat</password>
          -->
        </configuration>
      </plugin>
    </plugins>
  </build>

4. 通过MP代码生成器生成代码

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

4.1 加入数据库属性配置文件

把常用的数据源连接数据库操作的常用字段信息提取为一个properties文件方便后续修改和重复使用。

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/ssm_db?useSSL=false&useUnicode=true&characterEncoding=utf8
# mybatis中数据源连接使用username,报 Access denied for user 'CDHong'@'localhost' (using password: YES)
user=root
password=root

initialSize=5
maxActive=10
minIdle=4
maxWait=3000

代码生成配置启动类,配置官网参考地址

package mybaits.plus.code.generator;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import org.junit.Test;

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


/**
 * @description
 * @auther: CDHong
 * @date: 2019/6/24-10:18
 **/
public class MyBatisPlusCodeGenerator {

    //读取属性配置文件
    private ResourceBundle rb = ResourceBundle.getBundle("druid");

    @Test
    public void codeGenerator() {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("CDHong");
        gc.setOpen(false);
        gc.setSwagger2(true); //实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl(rb.getString("url"));
        // dsc.setSchemaName("public");
        dsc.setDriverName(rb.getString("driverClassName"));
        dsc.setUsername(rb.getString("username"));
        dsc.setPassword(rb.getString("password"));
        mpg.setDataSource(dsc);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("org.itcast.demo");  //父级公用包名,就是自动生成的文件放在项目路径下的那个包中
        mpg.setPackageInfo(pc);

        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };
        String templatePath = "/templates/mapper.xml.vm";
        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mappers/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });

        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);

        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();
        templateConfig.setXml(null); //是否在mapper接口处生成xml文件
        mpg.setTemplate(templateConfig);


        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);  //Entity文件名称命名规范
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);  //Entity字段名称命名规范
        strategy.setEntityLombokModel(true);  //是否使用lombok完成Entity实体标注Getting Setting ToString 方法
        strategy.setRestControllerStyle(true);  //Controller注解使用是否RestController标注,否则是否开启使用Controller标注
        strategy.setControllerMappingHyphenStyle(true);  //Controller注解名称,不使用驼峰,使用连字符
        strategy.setTablePrefix("tbl_");  //表前缀,添加该表示,则生成的实体,不会有表前缀,比如sys_dept 生成就是Dept
        //strategy.setFieldPrefix("sys_");  //字段前缀
        mpg.setStrategy(strategy);
        mpg.execute();
    }
}

5. application-core.xml配置

Spring 与 MyBatis 整合,导入属性配置文件,配置数据眼,配置SqlSessionFactory , 配置MapperScanner , 配置事务管理,扫描Service注解

  • applicationContext-core.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--扫描Services-->
    <context:component-scan base-package="org.itcast.demo.service.impl" />

    <!--导入配置文件-->
    <context:property-placeholder location="classpath:druid.properties" />

    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
        <property name="driverClassName" value="${driver}" />
        <property name="url" value="${url}" />
        <property name="username" value="${user}" />
        <property name="password" value="${password}" />

        <property name="initialSize" value="${initialSize}" />
        <property name="maxWait" value="${maxWait}" />
        <property name="maxActive" value="${maxActive}" />
        <property name="minIdle" value="${minIdle}" />
    </bean>

    <!--配置MybatisSqlSessionFactoryBean-->
    <bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
        <!--数据源注入-->
        <property name="dataSource" ref="dataSource" />
        <!--别名配置-->
        <property name="typeAliasesPackage" value="org.itcast.demo.entity" />
        <!--SQL映射文件地址-->
        <property name="mapperLocations" value="classpath:mappers/*Mapper.xml" />
        <!--分页插件配置-->
        <property name="plugins">
            <bean class="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor" />
        </property>
    </bean>

    <!--配置MapperScan-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--Mapper接口扫描-->
        <property name="basePackage" value="org.itcast.demo.mapper" />
        <!--SqlSession注入,在mybatis plus中可以省略-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
    </bean>

    <!--事务处理-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!--开启事务注解-->
    <tx:annotation-driven transaction-manager="transactionManager" />
</beans>

6 通用返回接口编写

随着互联网的高速发展,前端页面的展示、交互体验越来越灵活、炫丽,响应体验也要求越来越高,后端服务的高并发、高可用、高性能、高扩展等特性的要求也愈加苛刻,从而导致前后端研发各自专注于自己擅长的领域深耕细作。
然而带来的另一个问题:前后端的对接界面双方却关注甚少,没有任何接口约定规范情况下各自撸起袖子就是干,导致我们在产品项目开发过程中,前后端的接口联调对接工作量占比在30%-50%左右,甚至会更高。往往前后端接口联调对接及系统间的联调对接都是整个产品项目研发的软肋。
因此我们需要一个统一的返回接口样式,这样,前后端交互就较为融洽。

6.1 定义通过的返回编码及提示信息

后台所有的返回数据都有一个状态码,这样定义好,后续生成文档,前端人员看到对应的Code值就知道对应是什么错误,使用对应的样式来显示对应的错误。这样统一定义也方便管理,统一规范。

 package org.itcast.demo.util;

/**
 * @description
 * @auther: CDHong
 * @date: 2019/5/2-13:56
 **/
public enum ResponseCode {

    SUCCESS(0,"SUCCESS"),
    ERROR(1,"ERROR"),
    PARAME_ERROR(2,"PARAME_ERROR"),
    NEED_LOGIN(10,"NEED_LOGIN"),
    SYS_ERROR(11,"系统错误~");

    private final int code;
    private final String desc;

    ResponseCode(int code,String desc){
        this.code = code;
        this.desc = desc;
    }

    public int getCode() {
        return code;
    }

    public String getDesc() {
        return desc;
    }}

6.2 定义通用的返回实体,并提供对应的常用方法

前后端分离,肯定需要返回统一格式的JSON数据,我们这里就是定义一个返回给前端的数据实体,包括状态码,显示信息,数据,以及分页需要展示的数据信息,都有定义,基本上能够满足所有需求。

package org.itcast.demo.util;


import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

import java.io.Serializable;

/**
 * @description
 * @auther: CDHong
 * @date: 2019/5/2-13:52
 **/
@Data
@JsonInclude(JsonInclude.Include.NON_NULL) //JSON序列化的是否,如果值为NULL则不返回到前端
public class ResponseData implements Serializable{

    private Integer status;
    private Long total;
    private String msg;
    private Object data;

    private ResponseData(int status) {
        this.status = status;
    }
    private ResponseData(int status, String msg) {
        this.status = status;
        this.msg = msg;
    }
    private ResponseData(int status, Object data) {
        this.status = status;
        this.data = data;
    }
    private ResponseData(int status, String msg, Object data) {
        this.status = status;
        this.msg = msg;
        this.data = data;
    }
    private ResponseData(int status, Long total, Object data) {
        this.status = status;
        this.total = total;
        this.data = data;
    }

    public static ResponseData ok(){
        return new ResponseData(ResponseCode.SUCCESS.getCode());
    }

    public static ResponseData ok(String msg){
        return new ResponseData(ResponseCode.SUCCESS.getCode(),msg);
    }

    public static ResponseData ok(Object data){
        return new ResponseData(ResponseCode.SUCCESS.getCode(),data);
    }

    public static ResponseData okPage(Long total, Object data){
        return new ResponseData(ResponseCode.SUCCESS.getCode(),total,data);
    }

    public static ResponseData fail(String msg){
        return new ResponseData(ResponseCode.ERROR.getCode(),msg);
    }

    public static ResponseData error(ResponseCode responseCode){
        return new ResponseData(responseCode.getCode(),responseCode.getDesc());
    }
    public static ResponseData exception(String msg){
        return new ResponseData(ResponseCode.ERROR.getCode(),msg);
    }

    public static ResponseData ok(String msg, Object data) {
        return new ResponseData(ResponseCode.SUCCESS.getCode(),msg,data);
    }

    @JsonIgnore
    public boolean isSuccess() {
        return this.status == ResponseCode.SUCCESS.getCode();
    }
}

7. application-mvc.xml配置

集成SpringMVC 模块,配置JSON类型转化器,开启静态资源处理,扫描Controller注解

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
    <!--扫描Controller注解-->
    <context:component-scan base-package="org.itcast.demo.controller" />

    <!--配置一个JSON数据类型转化器-->
    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="messageConverters">
            <list>
                <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" >
                    <property name="supportedMediaTypes">
                        <list>
                            <!--设置返回格式,防止浏览器下载和数据乱码-->
                            <value>application/json;charset=UTF-8</value>
                            <value>text/html;charset=UTF-8</value>
                        </list>
                    </property>
                    <property name="objectMapper">
                        <bean class="org.itcast.demo.config.JavaTimeJsonMapper" />
                    </property>
                </bean>
            </list>
        </property>
    </bean>

    <!--开启注解驱动,JSON处理,上传下载..-->
    <mvc:annotation-driven />
    <!--将静态资源交由默认的servlet处理-->
    <mvc:default-servlet-handler />
    <!--向容器自动注入配置-->
    <context:annotation-config />
</beans>

8. web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
    <!--IOC容器 -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext-core.xml</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <!--前端控制器-->
    <servlet>
        <servlet-name>springMVC</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:applicationContext-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    <servlet-mapping>
        <servlet-name>springMVC</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <!--字符编码处理-->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>utf-8</param-value>
        </init-param>
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

</web-app>

9. 集成Swagger构建restful API

Swagger 是什么?
*1、是一款让你更好的书写API文档的规范且完整框架。
*2、提供描述、生产、消费和可视化RESTful Web Service。
*3、是由庞大工具集合支撑的形式化规范。这个集合涵盖了从终端用户接口、底层代码库到商业API管理的方方面面。
Swagger 主要提供了三个功能:
*Swagger Editor: Swagger提供的一个编辑器,用来通过Swagger提供的特定的YAML语法来编写API文档
*Swagger Codegen: 代码生成器
*Swagger UI: YAML语法定义我们的RESTful API,然后它会自动生成一篇排版优美的API文档,并且提供实时预览。

9.1 Swagger2注解说明

在前面MP代码生成器中我们已经配置了自动生成Swagger注解,相信也有同学看到了,但是对他不是很了解,接下来我们就来看看这些常用的注解

@Api:用在请求的类上,表示对类的说明
    tags="说明该类的作用,可以在UI界面上看到的注解"
    value="该参数没什么意义,在UI界面上也看到,所以不需要配置"

@ApiOperation:用在请求的方法上,说明方法的用途、作用
    value="说明方法的用途、作用"
    notes="方法的备注说明"

@ApiModel:用于响应类上,表示一个返回响应数据的信息,MP代码生成器Entity中有配置
            (这种一般用在post创建的时候,使用@RequestBody这样的场景,
            请求参数无法使用@ApiImplicitParam注解进行描述的时候)
    @ApiModelProperty:用在属性上,描述响应类的属性
    
@ApiImplicitParams:用在请求的方法上,表示一组参数说明
    @ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
        name:参数名
        value:参数的汉字说明、解释
        required:参数是否必须传
        paramType:参数放在哪个地方
            · header --> 请求参数的获取:@RequestHeader
            · query  --> 请求参数的获取:@RequestParam
            · path(用于restful接口)--> 请求参数的获取:@PathVariable
            · body(不常用)
            · form(不常用)    
        dataType:参数类型,默认String,其它值dataType="Integer"       
        defaultValue:参数的默认值

@ApiResponses:用在请求的方法上,表示一组响应
    @ApiResponse:用在@ApiResponses中,一般用于表达一个错误的响应信息
        code:数字,例如400
        message:信息,例如"请求参数没填好"
        response:抛出异常的类

9.2 SpringMVC集成springfox-swagger2构建restful API

  1. 9.2.1 在pom.xml文件中添加swagger相关依赖

第一个是API获取的包,第二是官方给出的一个ui界面。这个界面可以自定义,默认是官方的,对于安全问题,以及ui路由设置需要着重思考

<!-- swagger2 : swagger本身不支持spring mvc的,SpringFox把swagger包装了一下,让他可以支持Spring mvc-->
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>${swagger2.version}</version>
    </dependency>
    <dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>${swagger2.version}</version>
    </dependency>
  1. 配置Swagger2的Config,生成相应的API

需要特别注意的是swagger scan base package,这是扫描注解的配置,即你的API接口位置。
Swagger2配置类 通过@Configuration注解,让spring来加载该配置 再通过@EnableSwagger2注解来启动Swagger2, 一定要jdk1.8,不然跑不起swagger

package org.itcast.demo.config;

import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
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;

/**
 * @description  swagger的configuration
 * @auther: CDHong
 * @date: 2019/6/25-10:14
 **/
@Configuration
@EnableSwagger2
@EnableWebMvc //非springboot框架需要引入且需要在配置文件中配置该类的Bean对象
@ComponentScan(basePackages = {"org.itcast.demo.controller"}) //扫描包
public class Swagger2Config {
    /**
     * 创建API应用
     * appinfo()增加API相关信息
     * 通过select()函数返回一个ApiSelectorBuilder实例,用来控制那些接口暴露给Swagger来展现
     * 本例采用置顶扫描的包路径来定义指定要建立API的目录
     * @return
     */
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
            .groupName("")
            .select()// 选择哪些路径和API会生成document
            .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))// 对所有api进行监控
            .paths(PathSelectors.any())// 对所有路径进行监控
            .build()
            .apiInfo(apiInfo());
    }
    /**
     * 创建改API的基本信息(这些基本信息会展示在文档页面中)
     * 访问地址: http://项目实际地址/swagger-ui.html
     * @return
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
            .title("简单的SSM框架搭建练习案例")
            .description("使用MyBatisPlus与VUE来扩展一下前后端")
            .termsOfServiceUrl("http://localhost:80/swagger-ui.html")// 将“url”换成自己的ip:port
            .version("1.0")
            .build();
    }
}
  1. 修改applicaitonContext-mvc.xml文件;添加关于swagger的配置,内容如下:
	<mvc:default-servlet-handler />
	<!--扫描Config-->
    <context:component-scan base-package="org.itcast.demo.config" />
	<!-- 将swaggerconfig配置类注入 -->
    <bean class="org.itcast.demo.config.Swagger2Config"/>
    <!--
    <mvc:resources location="classpath:/META-INF/resources/" mapping="swagger-ui.html"/>
    <mvc:resources location="classpath:/META-INF/resources/webjars/" mapping="/webjars/**"/>
	-->

集成完毕,接下来在代码中使用。

10. Controller 编写

package org.itcast.demo.controller;


import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.itcast.demo.entity.User;
import org.itcast.demo.exception.MyException;
import org.itcast.demo.service.IUserService;
import org.itcast.demo.util.ConstUtils;
import org.itcast.demo.util.ResponseCode;
import org.itcast.demo.util.ResponseData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpSession;
import java.util.List;
import java.util.Objects;

/**
 * @author CDHong
 * @since 2019-06-24
 */
@Slf4j
@RestController
@RequestMapping("/user")
@Api(value = "用户管理接口Api",tags = "用户管理接口Api")
public class UserController {

    @Autowired
    private IUserService userService;

    @ApiOperation(value = "添加用户",notes = "用户注册,状态默认0启用1禁用,生日格式:yyyy-MM-dd")
    @PostMapping("/save")
    public ResponseData save(User user){
        if(Objects.isNull(user)){
            return ResponseData.error(ResponseCode.PARAME_ERROR);
        }
        boolean flg = userService.save(user);
        if(flg){
            return ResponseData.ok("添加用户成功~");
        }
        return ResponseData.fail("添加用户失败~");
    }

    @ApiOperation("根据ID删除用户")
    @ApiImplicitParam(name = "id",value = "用户ID",dataType = "Integer" ,required = true,paramType = "path")
    @DeleteMapping("/del/{id}")
    public ResponseData delById(@PathVariable("id") Integer id){
        if(Objects.isNull(id)){
            return ResponseData.error(ResponseCode.PARAME_ERROR);
        }
        boolean flg = userService.removeById(id);
        if(flg){
            return ResponseData.ok("删除用户成功~");
        }
        return ResponseData.fail("删除用户失败~");
    }

    @ApiOperation("根据ID编辑用户")
    @PostMapping("/edit")
    public ResponseData edit(User user){
        if(Objects.isNull(user)){
            return ResponseData.error(ResponseCode.PARAME_ERROR);
        }
        boolean flg = userService.updateById(user);
        if(flg){
            return ResponseData.ok("修改用户成功~");
        }
        return ResponseData.fail("修改用户失败~");
    }
    @ApiOperation("显示用户列表")
    @GetMapping("/list")
    public ResponseData list(){
        List<User> list = userService.list();
        return ResponseData.ok(list);
    }

    @ApiOperation("根据ID获取用户信息")
    @ApiImplicitParam(name = "id",value = "用户ID",dataType = "Integer" ,required = true,paramType = "path")
    @GetMapping("/find/{id}")
    public ResponseData findById(@PathVariable("id") Integer id){
        User user = userService.getById(id);
        return ResponseData.ok(user);
    }

    @ApiOperation("分页显示用户信息")
    @ApiImplicitParams({
         @ApiImplicitParam(name = "pageIndex",value = "页码",dataType = "Integer",required = true,paramType = "path"),
         @ApiImplicitParam(name = "pageSize",value = "每页显示条数",dataType = "Integer",required = true,paramType = "path")
    })
    @GetMapping("/page/{pageIndex}/{pageSize}")
    public ResponseData page(@PathVariable("pageIndex") Integer pageIndex,@PathVariable("pageSize") Integer pageSize){
        Page<User> page = new Page<>(pageIndex,pageSize);
        IPage<User> userPage = userService.page(page);
        return ResponseData.okPage(userPage.getTotal(), userPage.getRecords());
    }
}

11. logback日志

需要在pom文件中配置两个坐标:slf4j-api,logback-classic

<dependency>
  	  <groupId>org.slf4j</groupId>
	  <artifactId>slf4j-api</artifactId>
	  <version>1.7.26</version>
  </dependency>
  <dependency>
	    <groupId>ch.qos.logback</groupId>
	    <artifactId>logback-classic</artifactId>
	    <version>1.2.3</version>
 </dependency>

配置文件名称必须为logback.xml,且需要放在资源目录的根路径下。

<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
    <!-- magenta:洋红 -->
    <!-- boldMagenta:粗红-->
    <!-- cyan:青色 -->
    <!-- white:白色 -->
    <!-- magenta:洋红 -->
    <!--控制台日志配置-->
    <property name="CONSOLE_LOG_PATTERN" value="%yellow(%date{yyyy-MM-dd HH:mm:ss}) |%highlight(%-5level)|%green(%logger) |%cyan(%msg%n)"/>
    <property name="FILE_PATH" value="/root/sys/logs/member"/>

    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>${CONSOLE_LOG_PATTERN}</pattern>
        </encoder>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debug</level>
        </filter>
    </appender>
    <!--  log 日志 要强制打印的日志信息,否则按root的级别打印 -->
    <logger name="org.springframework.web.servlet.DispatcherServlet" level="debug" additivity="false">
        <appender-ref ref="debug" />
    </logger>

    <root level="debug">
        <appender-ref ref="console"/>
    </root>
</configuration>

12. java8日期注入全局处理

在实体中使用java8的日期,导致SpringMVC数据无法注入,需要一序列的配置,在这里,我使用一个全局的配置,统一处理,该文件需要注解扫描,所以需要在配置文件中添加扫描路径。

package org.itcast.demo.config;

import org.itcast.demo.util.ConstUtils;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.InitBinder;

import java.beans.PropertyEditorSupport;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

/**
 * @description
 * @auther: CDHong
 * @date: 2019/6/23-19:50
 **/
@ControllerAdvice
public class GlobalDataInitBinder {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.registerCustomEditor(LocalDate.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) {
                setValue(LocalDate.parse(text, DateTimeFormatter.ofPattern(ConstUtils.DEFAULT_DATE_FORMAT)));
            }
        });
        binder.registerCustomEditor(LocalDateTime.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalDateTime.parse(text, DateTimeFormatter.ofPattern(ConstUtils.DEFAULT_DATE_TIME_FORMAT)));
            }
        });
        binder.registerCustomEditor(LocalTime.class, new PropertyEditorSupport() {
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(LocalTime.parse(text, DateTimeFormatter.ofPattern(ConstUtils.DEFAULT_TIME_FORMAT)));
            }
        });
    }
}

13. 日期类JSON序列化和反序列化全局处理

Spring集成Jackson来完成JSON序列化操作,但是日期的返回信息不是我们需要的格式,这个时候,我们同样使用一个全局的配置文件来完成序列化操作。同样需要注解扫描。

package org.itcast.demo.config;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
import org.itcast.demo.util.ConstUtils;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;

/**
 * @description javaTime JSON序列化和反序列化
 * @auther: CDHong
 * @date: 2019/6/23-19:56
 **/
public class JavaTimeJsonMapper extends ObjectMapper {
    /** Date日期格式化 **/
    private SimpleDateFormat formatter = new SimpleDateFormat(ConstUtils.DEFAULT_DATE_TIME_FORMAT);
    public JavaTimeJsonMapper(){
        this.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        this.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);

        //LocalDateTime系列序列化和反序列化模块,继承自jsr310,我们在这里修改了日期格式
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(ConstUtils.DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addSerializer(LocalDate.class,new LocalDateSerializer(DateTimeFormatter.ofPattern(ConstUtils.DEFAULT_DATE_FORMAT)));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(ConstUtils.DEFAULT_TIME_FORMAT)));
        javaTimeModule.addDeserializer(LocalDateTime.class,new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(ConstUtils.DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addDeserializer(LocalDate.class,new LocalDateDeserializer(DateTimeFormatter.ofPattern(ConstUtils.DEFAULT_DATE_FORMAT)));
        javaTimeModule.addDeserializer(LocalTime.class,new LocalTimeDeserializer(DateTimeFormatter.ofPattern(ConstUtils.DEFAULT_TIME_FORMAT)));


        //Date序列化和反序列化
        javaTimeModule.addSerializer(Date.class, new JsonSerializer<Date>() {
            @Override
            public void serialize(Date date, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
                String formattedDate = formatter.format(date);
                jsonGenerator.writeString(formattedDate);
            }
        });
        javaTimeModule.addDeserializer(Date.class, new JsonDeserializer<Date>() {
            @Override
            public Date deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
                String date = jsonParser.getText();
                try {
                    return formatter.parse(date);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                return null;
            }
        });

        this.registerModule(javaTimeModule);
    }
}

14. 自定义异常以及异常处理

在项目开发中我们总是无法避免各种错误,如果都是使用try…catch来预防,总归不是完美的办法,这个对我们查找错误信息修复bug也不利,这时候我们需要配置一个异常全局处理,给前端提供一个统一的数据格式信息。

14.1 自定义异常类

这里需要注意一点,自定义异常继承RuntimeException即可,不需要继承Excpeiton,不然抛出自定义异常的时候,不是很方便。

package org.itcast.demo.exception;

import org.itcast.demo.util.ResponseCode;

/**
 * @description
 * @auther: CDHong
 * @date: 2019/6/25-18:01
 **/
public class MyException extends RuntimeException {

    public MyException(String message) {
        super(message);
    }
    public MyException(ResponseCode code) {
        super(code.getDesc());
    }
}

14.2 全局异常处理

package org.itcast.demo.config;

import org.itcast.demo.exception.MyException;
import org.itcast.demo.util.ResponseCode;
import org.itcast.demo.util.ResponseData;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

/**
 * @description 全局异常处理
 * @auther: CDHong
 * @date: 2019/6/24-11:30
 **/
@RestControllerAdvice
public class GlobalExceptionResolver{

    @ExceptionHandler(MyException.class)
    public ResponseData myException(MyException e){
        return ResponseData.exception(e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public ResponseData exception(Exception e) {
        return ResponseData.exception(e.getMessage());
    }
}

14.3 在controller中定义两个异常来测试一下

    @ApiOperation("自定义异常")
    @GetMapping("/exception")
    public ResponseData exception(){
        throw new MyException("异常测试");
    }

    @ApiOperation("系统异常")
    @GetMapping("/sys-error")
    public ResponseData sysException(){
        int i = 1/0;
        return ResponseData.ok();
    }

15. 拦截器配置

SpringMVC拦截器配置比较简单,只需要实现HandlerInterceptor,重写preHandle,添加相应的拦截器处理,在配置文件中注册即可。

package org.itcast.demo.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.itcast.demo.exception.MyException;
import org.itcast.demo.util.ConstUtils;
import org.itcast.demo.util.ResponseCode;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Objects;

/**
 * @description
 * @auther: CDHong
 * @date: 2019/6/27-13:57
 **/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
        Object obj = request.getSession().getAttribute(ConstUtils.CURRENT_LOGIN_USER);
        String requestURI = request.getRequestURI();
        if(Objects.nonNull(obj)){
            return true;
        }
        log.debug("请求路径:【{}】被拦截了",requestURI);
        throw new MyException(ResponseCode.NEED_LOGIN);
        //response.sendRedirect("/login");
        //return false;
    }
}

配置文件中注册: 可以指定要拦截的表达式和排除的表达式

	<!--拦截器配置-->
    <mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/user/**"/> <!--拦截所有以/user/开头的请求-->
            <mvc:exclude-mapping path="/user/login" /> <!--登录请求和其他非/user开头的请求不拦截-->
            <bean class="org.itcast.demo.interceptor.LoginInterceptor" />
        </mvc:interceptor>
    </mvc:interceptors>

16. MP lambda表达式使用

@ApiOperation("用户登录")
    @PostMapping("/login")
    public ResponseData login(HttpSession session,String logName, String logPwd){
        //LambdaQueryWrapper<User> lambda = new QueryWrapper<User>().lambda();
        //LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
        //LambdaQueryWrapper<User> queryWrapper = Wrappers.<User>lambdaQuery().eq(User::getLogName, logName).eq(User::getLogPwd, logPwd);
        //User user = userService.getOne(queryWrapper);
        ResponseData responseData = userService.login(logName, logPwd);
        if(responseData.isSuccess()){
            session.setAttribute(ConstUtils.CURRENT_LOGIN_USER,responseData.getData());
        }
        return responseData;
    }

17. MP 使用Wrapper 自定义SQL

在使用了mybatis-plus之后, 自定义SQL的同时也想使用Wrapper的便利应该怎么办? 在mybatis-plus版本3.0.7得到了完美解决 版本需要大于或等于3.0.7, 以下两种方案取其一即可

  1. 方案一 注解方式 Mapper.java
@Select("select * from mysql_data ${ew.customSqlSegment}")
List<MysqlData> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);
  1. 方案二 XML形式 Mapper.xml
<select id="getAll" resultType="MysqlData">
	SELECT * FROM mysql_data ${ew.customSqlSegment}
</select>
  1. 具体使用如下:

Mapper接口中定义如下:

package org.itcast.demo.mapper;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.itcast.demo.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import java.util.List;

/**
 * <p>
 *  Mapper 接口
 * </p>
 *
 * @author CDHong
 * @since 2019-06-24
 */
public interface UserMapper extends BaseMapper<User> {

    @Select("SELECT id,rel_name,log_pwd,log_name,log_status,sex,birthday,create_time from tbl_user ${ew.customSqlSegment}")
    List<User> findAll(@Param(Constants.WRAPPER) Wrapper<User> wrapper);

    IPage<User> pageInfo(Page<User> page,@Param(Constants.WRAPPER) Wrapper<User> wrapper);

}

XMLSQL映射文件配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.itcast.demo.mapper.UserMapper">

    <select id="pageInfo" resultType="org.itcast.demo.entity.User">
        SELECT id,rel_name,log_pwd,log_name,log_status,sex,birthday,create_time from tbl_user ${ew.customSqlSegment}
    </select>
</mapper>

18. 结束

到此讲解完毕,最后附上一个Swaager测试的ResultFul API

Spring + MP + Swagger 完成简单的CURD操作

任意点开一个请求,输入对应的数据,执行Try it out!按钮就可以看到对应的结果信息

Spring + MP + Swagger 完成简单的CURD操作

返回信息如下:

Spring + MP + Swagger 完成简单的CURD操作
最后附上案例源码地址,希望大家喜欢!!

上一篇:终极CURD-4-java8新特性


下一篇:SSM-Mybatis(一)单表CURD