【Spring Boot + MyBatis|第3篇】统一返回结果 Result 类设计
前言上一篇我们学习了 Controller 中常见的三种参数接收方式RequestParam、PathVariable、RequestBody。这一篇继续学习一个项目中非常常见的设计统一返回结果类 Result。在 Spring Boot 项目中如果每个接口返回的数据格式都不一样前端处理起来就会很麻烦。比如有的接口返回字符串有的接口返回对象有的接口返回列表有的接口失败时又返回另一种格式。所以在实际开发中我们一般会设计一个统一的返回结构让所有接口都按照同一种格式返回数据。一、为什么需要统一返回结果假设我们不使用统一返回结果接口可能会这样写。GetMapping(/{id}) public Emp getById(PathVariable Integer id) { return empService.getById(id); }这个接口直接返回了一个 Emp 对象。再看另一个接口PostMapping public String save(RequestBody Emp emp) { empService.save(emp); return 新增成功; }这个接口返回的是字符串。如果删除失败又可能这样写DeleteMapping(/{id}) public String delete(PathVariable Integer id) { return 删除失败; }这样写虽然能运行但是问题很明显接口返回格式不统一。前端拿到数据后很难用同一套逻辑判断接口是否成功。所以我们更希望所有接口都返回类似下面这种格式{ code: 1, msg: success, data: {} }这样前端只需要判断 code就能知道接口是否执行成功。二、Result 类的基本设计一个基础版的 Result 类通常包含三个字段字段含义code状态码用来表示成功或失败msg提示信息data返回给前端的数据比如public class Result { private Integer code; private String msg; private Object data; }在学习阶段我们可以先约定code 1 表示成功 code 0 表示失败三、编写 Result 工具类1. Result 类代码public class Result { private Integer code; private String msg; private Object data; public Result() { } public Result(Integer code, String msg, Object data) { this.code code; this.msg msg; this.data data; } public static Result success() { return new Result(1, success, null); } public static Result success(Object data) { return new Result(1, success, data); } public static Result error(String msg) { return new Result(0, msg, null); } public Integer getCode() { return code; } public String getMsg() { return msg; } public Object getData() { return data; } public void setCode(Integer code) { this.code code; } public void setMsg(String msg) { this.msg msg; } public void setData(Object data) { this.data data; } }2. 文字说明这个 Result 类中最核心的是三个静态方法Result.success() Result.success(data) Result.error(msg)如果接口执行成功但是不需要返回数据就使用return Result.success();如果接口执行成功并且需要返回数据就使用return Result.success(data);如果接口执行失败就使用return Result.error(失败原因);这样 Controller 层写起来会非常统一。四、Controller 中如何使用 Result1. 查询接口GetMapping(/{id}) public Result getById(PathVariable Integer id) { Emp emp empService.getById(id); return Result.success(emp); }2. 新增接口PostMapping public Result save(RequestBody Emp emp) { empService.save(emp); return Result.success(); }3. 删除接口DeleteMapping(/{id}) public Result delete(PathVariable Integer id) { empService.delete(id); return Result.success(); }4. 条件分页查询接口GetMapping public Result page(Integer page, Integer pageSize, String name) { PageBean pageBean empService.page(page, pageSize, name); return Result.success(pageBean); }5. 文字说明可以看到Controller 的返回值都统一变成了Result无论是查询、新增、删除还是分页查询返回结构都一样。只不过有的接口有 data有的接口没有 data。例如新增成功后返回{ code: 1, msg: success, data: null }查询成功后返回{ code: 1, msg: success, data: { id: 1, name: 张三 } }这样前端处理起来就会更方便。五、Service 层要不要返回 Result这一点很容易写混。一般来说不建议 Service 层直接返回 Result。比如不推荐这样写public Result getById(Integer id) { Emp emp empMapper.getById(id); return Result.success(emp); }更推荐这样写public Emp getById(Integer id) { return empMapper.getById(id); }然后在 Controller 层统一包装GetMapping(/{id}) public Result getById(PathVariable Integer id) { Emp emp empService.getById(id); return Result.success(emp); }原因很简单Result 是返回给前端的响应格式更适合放在 Controller 层处理。Service 层更应该关注业务逻辑而不是关心前端响应格式。这样分层会更清楚。六、如果使用 Lombok 可以简化代码如果项目中引入了 Lombok可以把 Result 类简化成这样Data NoArgsConstructor AllArgsConstructor public class Result { private Integer code; private String msg; private Object data; public static Result success() { return new Result(1, success, null); } public static Result success(Object data) { return new Result(1, success, data); } public static Result error(String msg) { return new Result(0, msg, null); } }这里的Data会自动生成 get、set、toString 等方法。NoArgsConstructor AllArgsConstructor会自动生成无参构造和全参构造。不过如果刚开始学习 Java不使用 Lombok 也完全可以手动写 getter、setter 反而更容易理解。七、Result 类可以进一步优化成泛型上面的 data 使用的是private Object data;这样写比较简单但是类型不够明确。后面项目复杂之后可以改成泛型public class ResultT { private Integer code; private String msg; private T data; public Result() { } public Result(Integer code, String msg, T data) { this.code code; this.msg msg; this.data data; } public static T ResultT success(T data) { return new Result(1, success, data); } public static T ResultT success() { return new Result(1, success, null); } public static T ResultT error(String msg) { return new Result(0, msg, null); } }这样写以后返回类型可以更加清楚。比如public ResultEmp getById(PathVariable Integer id) { Emp emp empService.getById(id); return Result.success(emp); }表示这个接口返回的 data 类型是 Emp。不过在初学阶段使用 Object data 的版本已经够用了。八、常见问题总结1. Result 类是不是必须写不是语法要求必须写但是实际项目中非常推荐写。因为统一返回格式可以让接口更规范也方便前端统一处理成功和失败。2. code 一定要用 1 和 0 吗不一定。有的项目会用200 表示成功 500 表示服务器错误 401 表示未登录 403 表示无权限学习阶段用 1 和 0 比较简单后期可以根据项目规范调整。3. Result 和 HTTP 状态码有什么关系HTTP 状态码是协议层面的状态比如 200、404、500。Result 中的 code 是业务层面的状态用来告诉前端业务是否成功。在普通后台管理系统中经常会同时使用HTTP 状态码200 Result.code1 或 0也就是说请求能正常到达后端HTTP 可以是 200但是业务可能成功也可能失败。4. Mapper 层需要关心 Result 吗不需要。Mapper 层只负责操作数据库返回实体类、集合、数字等结果即可。比如Emp getById(Integer id); ListEmp list(); int deleteById(Integer id);不要在 Mapper 层返回 Result。九、实际开发中的建议在项目中使用 Result 类时可以记住下面几点Controller 层统一返回 ResultService 层返回业务数据不直接返回 ResultMapper 层只操作数据库不关心响应格式成功时使用 Result.success()失败时使用 Result.error(错误信息)返回列表、分页对象、详情对象时放到 data 中这样代码结构会更加清楚。十、总结这一篇主要学习了 Spring Boot 项目中统一返回结果类 Result 的设计。Result 类的作用就是让所有接口返回同一种格式常见字段包括 code、msg、data。它最大的好处是让前后端交互更统一前端可以通过 code 判断接口是否成功通过 msg 获取提示信息通过 data 获取真正的数据。在分层上Controller 层负责把业务数据包装成 Result 返回Service 层负责业务逻辑Mapper 层负责数据库操作。这样三层职责会更清楚也更适合后期维护。下一篇可以继续学习 MyBatis 中非常重要的动态 SQL也就是 if、where、foreach 的使用。