导航
以学生信息管理(增删改查)为例。主要目的是,通过开发这样一个小业务功能,熟悉 Spring Boot 的基本用法。
github 链接:https://github.com/plough/student-information-system
技术点概要:
- 用 Spring Boot 和 Spring Data 开发 REST API;
- 采用领域驱动开发(domain-driven development)方式,将分出 model, repository, service, controller 层;
- 相关工具:使用 IntelliJ IDEA 做开发,MongoDB 做持久化,Postman 做接口测试,Gradle 做构建。
(MongoDB、Gradle 等工具的安装和配置略过不讲)
0 业务需求定义:学生信息管理服务
提供一组 REST API,实现对学生信息的增删改查。
具体要求如下:
- 发送 POST 请求,向系统中添加学生;
- 发送 GET 请求,根据学生编号或 email 查询学生信息;
- 发送 GET 请求,查询所有学生信息,并按 GPA 排序;
- MongoDB 中的学生集合,包含以下数据:名字、学生编号、email、课程列表、GPA;
- 存储、接收及返回,都是 JSON 格式。
1 使用 Spring Initialzr 初始化项目
我们使用 Spring Initalizr 来创建一个新的 Spring Boot 项目。
- 选择 Gradle Project,Java,Spring Boot 2.1.4;
- 填写合适的 group 和 artifact 名称;
- 添加依赖:Spring Data MongoDB 和 Spring Web Starter;
截图如下:
点击 Generate the project 按钮,会下载一个 zip 包,我们解压后导入 IDEA。
Gradle 把依赖处理好后,我们把包名改得规范一点。
2 Model 和 Repository
2.1 学生 Model
我们首先定义一个学生模型。
package me.baimoz.demo.student.information.system.model;
// imports
@Document(collection = "students")
public class Student {
@Id
private String id;
private String name;
private long studentNumber;
private String email;
private List<String> courseList;
private float gpa;
// 构造函数以及 Getters 和 Setters
}
@Document 标识了一个需要持久化到 MongoDB 的领域对象。详见文档(IDEA 中直接跳转到源码查看)。
2.2 学生 Repository
得益于 Spring Data 项目,我们只需要创建一个继承自 MongoRepository 的接口,不用实现它。
package me.baimoz.demo.student.information.system.repository;
// imports
public interface StudentRepository extends MongoRepository<Student, String> {
Student findByStudentNumber(long studentNumber);
Student findByEmail(String email);
List<Student> findAllByOrderByGpaDesc();
}
StudentRepository 提供了我们需要的三种查询方式。
3 学生 Service
现在来创建一个给 controller 调用的 service 对象。
package me.baimoz.demo.student.information.system.service;
// imports
public interface StudentService {
List<Student> findAll();
Student findByStudentNumber(long studentNumber);
Student findByEmail(String email);
List<Student> findAllByOrderByGpaDesc();
Student saveOrUpdateStudent(Student student);
void deleteStudentById(String id);
}
通常,controller 可以直接跟 repository 类交互。但是为了避免在 controller 中处理太多业务逻辑,我们需要把业务逻辑封装到 service 类中,因此多了一个中间层。
package me.baimoz.demo.student.information.system.service.impl;
// imports
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentRepository studentRepository;
@Override
public List<Student> findAll() {
return studentRepository.findAll();
}
@Override
public Student findByStudentNumber(long studentNumber) {
return studentRepository.findByStudentNumber(studentNumber);
}
@Override
public Student findByEmail(String email) {
return studentRepository.findByEmail(email);
}
@Override
public List<Student> findAllByOrderByGpaDesc() {
return studentRepository.findAllByOrderByGpaDesc();
}
@Override
public Student saveOrUpdateStudent(Student student) {
return studentRepository.save(student);
}
@Override
public void deleteStudentById(String id) {
studentRepository.deleteById(id);
}
}
4 REST Controller
最后,我们来创建一个 REST 控制器。
package me.baimoz.demo.student.information.system.controller;
// imports
@RestController
@RequestMapping("/students")
public class StudentRestController {
@Autowired
private StudentService studentService;
@GetMapping(value = "/")
public List<Student> getAllStudents(@RequestParam(value = "orderByGpa", defaultValue = "true") Boolean orderByGpa) {
if (orderByGpa) {
return studentService.findAllByOrderByGpaDesc();
}
return studentService.findAll();
}
@GetMapping(value = "/byStudentNumber/{studentNumber}")
public Student getStudentByStudentNumber(@PathVariable("studentNumber") Long studentNumber) {
return studentService.findByStudentNumber(studentNumber);
}
@GetMapping(value = "/byEmail/{email}")
public Student getStudentByEmail(@PathVariable("email") String email) {
return studentService.findByEmail(email);
}
@PostMapping(value = "/save")
public ResponseEntity<?> saveOrUpdateStudent(@RequestBody Student student) {
studentService.saveOrUpdateStudent(student);
return new ResponseEntity<>("Student added successfully", HttpStatus.OK);
}
@DeleteMapping(value = "/delete/{studentNumber}")
public ResponseEntity<?> deleteStudentByStudentNumber(@PathVariable long studentNumber) {
studentService.deleteStudentById(studentService.findByStudentNumber(studentNumber).getId());
return new ResponseEntity<>("Student deleted successfully", HttpStatus.OK);
}
}
5 数据库配置
在 application.properties 文件中,添加数据库相关的配置。例如:
#server server.port=8081 #mongodb spring.data.mongodb.host=localhost spring.data.mongodb.port=27017 spring.data.mongodb.database=test
6 运行测试
启动 MongoDB 服务器,运行 StudentInformationSystemApplication。
然后就可以用 Postman 来测试我们写的接口了。(代码量这么少就能运行,我感觉很神奇)
具体测试过程略过不表,可参考文末的英文文档。
单元测试略。
参考文章:Building a REST Service with Spring Boot and MongoDB (Part 1)




StudentRepository 真的很神奇,尤其是 findAllByOrderByGpaDesc 方法。Spring 是怎么知道具体实现步骤的?靠方法名推测么?