前言
上一篇我们学习了 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 中如何使用 Result?
1. 查询接口
@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 Result<T> { 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> Result<T> success(T data) { return new Result<>(1, "success", data); } public static <T> Result<T> success() { return new Result<>(1, "success", null); } public static <T> Result<T> error(String msg) { return new Result<>(0, msg, null); } }这样写以后,返回类型可以更加清楚。
比如:
public Result<Emp> 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.code:1 或 0也就是说,请求能正常到达后端,HTTP 可以是 200,但是业务可能成功,也可能失败。
4. Mapper 层需要关心 Result 吗?
不需要。
Mapper 层只负责操作数据库,返回实体类、集合、数字等结果即可。
比如:
Emp getById(Integer id); List<Emp> list(); int deleteById(Integer id);不要在 Mapper 层返回 Result。
九、实际开发中的建议
在项目中使用 Result 类时,可以记住下面几点:
- Controller 层统一返回 Result
- Service 层返回业务数据,不直接返回 Result
- Mapper 层只操作数据库,不关心响应格式
- 成功时使用 Result.success()
- 失败时使用 Result.error("错误信息")
- 返回列表、分页对象、详情对象时放到 data 中
这样代码结构会更加清楚。
十、总结
这一篇主要学习了 Spring Boot 项目中统一返回结果类 Result 的设计。
Result 类的作用就是让所有接口返回同一种格式,常见字段包括 code、msg、data。
它最大的好处是让前后端交互更统一,前端可以通过 code 判断接口是否成功,通过 msg 获取提示信息,通过 data 获取真正的数据。
在分层上,Controller 层负责把业务数据包装成 Result 返回,Service 层负责业务逻辑,Mapper 层负责数据库操作。这样三层职责会更清楚,也更适合后期维护。
下一篇可以继续学习 MyBatis 中非常重要的动态 SQL,也就是 <if>、<where>、<foreach> 的使用。