前言

上篇我们学习用service调用mapper来操作数据库,本篇主要优化文章的新增,已经返回统一的结果包装、异常处理

优化新增文章

修改mapper 添加方法供ArticleService调用

@Mapper
@Repository
public interface ArticleMapper {

    List<Article> getArticles();

    +++
    Boolean addArticle(Article article);
    +++
}


同样的,我们也修改ArticleService/Impl

// ArticleService
public interface ArticleService {

    List<Article> getArticles();
    
    +++
    Boolean addArticle(Article article);
    +++
}


// ArticleServiceImpl

@Service
public class ArticleServiceImpl implements ArticleService {

    @Resource
    private ArticleMapper articleMapper;

    @Override
    public List<Article> getArticles() {
        return articleMapper.getArticles();
    }

    +++
    @Override
    public Boolean addArticle(Article article) {
        return articleMapper.addArticle(article);
    }
    +++
}


我们新建一个vo类,用来存储返回结果和请求body字段,我们需要前端请求的数据做一下校验,安装validation

// 修改根目录pom.xml

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

创建一个类用来接新增文章的字段,并在其中做校验

// src/main/java/com/semyin/blog/vo/request/AddArticle.java

package com.semyin.blog.vo.request;

import javax.validation.constraints.NotBlank;

public class AddArticle {

    @NotBlank(message = "标题不能为空")
    private String title;

    @NotBlank(message = "内容不能为空")
    private String detail;

    private Integer status;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDetail() {
        return detail;
    }

    public void setDetail(String detail) {
        this.detail = detail;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }
}

然后修改,ArticleController中的方法addArticle,我们先来看有几个字段,id,title,detail,status

id ,一般默认后端生成,或者数据库自增,我们在创建数据库的时候并没有设置自增,这里我们手动设置一下,让它自增,alter table article change id id int AUTO_INCREMENT
title,前端传入
detail,前端传入
status,后端默认状态为1,这个我们后期再讲

@PostMapping("/articles")
public Boolean addArticle(
        @RequestBody @Valid AddArticle addArticle
        ) {
    Article article = new Article();
    article.setTitle(addArticle.getTitle());
    article.setDetail(addArticle.getDetail());
    article.setStatus(1);
    Date now = new Date();
    article.setCreateTime(now);
    article.setUpdateTime(now);
    return articleService.addArticle(article);
}

现在,我们修改src/main/resources/mapper/ArticleMapper.xml文件,添加新增文章的sql


<?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="com.semyin.blog.mapper.ArticleMapper">

    +++
    // resultMap 用户映射我们代码里的字段和数据库里的字段
	id 唯一名称,我们下面的sql语句中resultMap带上这个id,这样就做好了映射,一般用于驼峰
    <resultMap id="BaseResultMap" type="com.semyin.blog.entity.Article" >
        <id column="id" property="id" jdbcType="INTEGER" />
        <result column="title" property="title" jdbcType="VARCHAR" />
        <result column="detail" property="detail" jdbcType="VARCHAR" />
        <result column="status" property="status" jdbcType="INTEGER" />
        <result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
        <result column="update_time" property="updateTime" jdbcType="TIMESTAMP" />
    </resultMap>
    +++
    
    <select id="getArticles" resultMap="BaseResultMap" resultType="Article">
        select * from article
    </select>

    <insert id="addArticle" parameterType="com.semyin.blog.entity.Article">
        insert into article (title, detail, createTime, updateTime, status)
        values ( #{title,jdbcType=VARCHAR},
                 #{detail,jdbcType=VARCHAR},
                 #{createTime,jdbcType=TIMESTAMP},
                 #{updateTime,jdbcType=TIMESTAMP},
                 #{status,jdbcType=INTEGER})
    </insert>
</mapper>


看一下请求结果

POST http://localhost:8020/articles
Content-Type: application/json

{
"title": "semyin",
"detail": "semyin desc"
}

POST http://localhost:8020/articles

HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 29 Mar 2022 09:33:10 GMT
Keep-Alive: timeout=60
Connection: keep-alive

true

Response code: 200; Time: 226ms; Content length: 4 bytes

可以看到返回了true,代表我们新增成功了,我们看一下数据库也会发现article表里也多了一条数据

### 优化一下返回结果
我们新增成功一条数据,返回前端一个true,这样不太友好,所以封装一个ResultVO很有必要
```java

// src/main/java/com/semyin/blog/vo/ResultVO.java

package com.semyin.blog.vo;

import java.io.Serializable;

public class ResultVO<T> implements Serializable {

    public static final ResultVO<?> POST_OK = new ResultVO<>(200, "请求成功");

    public static final ResultVO<?> POST_ERROR = new ResultVO<>(400, "失败");

    /**
     *  状态码
     */
    private Integer code;

    /**
     *  提示信息
     */
    private String msg;

    /**
     *  数据体
     */
    private Object data;

    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 Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public T getCount() {
        return count;
    }

    public void setCount(T count) {
        this.count = count;
    }

    /**
     *  数量,一般用户列表查询
     */
    private T count;

    public ResultVO() {
    }

    private ResultVO(Integer code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public ResultVO(Integer code, String msg, T data, T count) {
        this.code = code;
        this.msg = msg;
        this.data = data;
        this.count = count;
    }

    private ResultVO(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}



现在我们处理一下ArticleController.java中的addArticlegetArticles方法,对返回数据使用ResultVO包装给前端


@PostMapping("/articles")
public ResultVO<?> addArticle(
        @RequestBody AddArticle addArticle
        ) {
    Article article = new Article();
    article.setTitle(addArticle.getTitle());
    article.setDetail(addArticle.getDetail());
    article.setStatus(1);
    boolean isSuccess = articleService.addArticle(article);
    +++
    if (isSuccess) {
        return ResultVO.POST_OK;
    } else {
        return ResultVO.POST_ERROR;
    }
   +++

}


@GetMapping("/articles")
public ResultVO<Integer> getArticles() {
    List<Article> articleList = articleService.getArticles();
    ResultVO<Integer> resultVO = new ResultVO<>();
    resultVO.setCode(200);
    resultVO.setCount(0); // count 我们后续会讲怎么处理
    resultVO.setData(articleList);
    resultVO.setMsg("ok");
    return resultVO;
}

返回结果


// 新增
POST http://localhost:8020/articles

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 29 Mar 2022 09:49:38 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "code": 200,
  "msg": "请求成功",
  "data": null,
  "count": null
}

Response code: 200; Time: 677ms; Content length: 50 bytes

// 列表

GET http://localhost:8020/articles

HTTP/1.1 200 
Content-Type: application/json
Transfer-Encoding: chunked
Date: Tue, 29 Mar 2022 10:00:58 GMT
Keep-Alive: timeout=60
Connection: keep-alive

{
  "code": 200,
  "msg": "ok",
  "data": [
    {
      "id": 1,
      "title": "测试标题",
      "detail": "我是测试内容",
      "status": 1,
      "createTime": "2022-03-24T08:53:56.000+00:00",
      "updateTime": "2022-03-24T08:53:56.000+00:00"
    },
    {
      "id": 2,
      "title": "semyin",
      "detail": "semyin desc",
      "status": 1,
      "createTime": "2022-03-29T10:00:35.000+00:00",
      "updateTime": "2022-03-29T10:00:35.000+00:00"
    }
  ],
  "count": 0
}

Response code: 200; Time: 140ms; Content length: 335 bytes

这样我们查询和新增返回给前端的优化处理就已经做好了

校验参数

我们再请求中参数不对或者确实就会,校验不正确会抛出异常,我们在异常中处理这个

// pom.xml 安装lang3

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.12.0</version>
</dependency>

// 新建 src/main/java/com/semyin/blog/exception/SystemExceptionHandler.java

package com.semyin.blog.exception;

import com.semyin.blog.vo.ResultVO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

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

@RestControllerAdvice
public class SystemExceptionHandler {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public Object handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
        return ResultVO.MISSING_REQUEST_BODY;
    }

    /**
     * 处理 json 请求体调用接口对象参数校验失败抛出的异常
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Object jsonParamsException(MethodArgumentNotValidException e) {
        BindingResult bindingResult = e.getBindingResult();
        List<String> errMsgList = new ArrayList<>();
        for (FieldError fieldError : bindingResult.getFieldErrors()) {
            String msg = fieldError.getField() + ":" + fieldError.getDefaultMessage();
            errMsgList.add(msg);
        }
        String joinErrorString = StringUtils.join(errMsgList.toArray(), ",");
        ResultVO<Integer> resultVO = new ResultVO<>();
        resultVO.setMsg(joinErrorString);
        resultVO.setCode(400);
        return resultVO;
    }

    /**
     * 缺失参数校验异常处理
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public Object handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        ResultVO<Integer> resultVO = new ResultVO<>();
        resultVO.setMsg(e.getMessage());
        resultVO.setCode(400);
        return resultVO;
    }
}


// 我们新增文章不传title试一下

{
  "code": 400,
  "msg": "title:标题不能为空",
  "data": null,
  "count": null
}

// 可以看到我们VO中AddArticle类里面的@NotBlank中的messgae被返回出来

结语

本篇主要学习如何使用VO来处理输入输出的数据
1、RequestVO、validation校验
2、异常处理
2、ResultVO封装

注解:@RequestBody,@Valid