导航
以学生信息管理(增删改查)为例。主要目的是,通过开发这样一个小业务功能,熟悉 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 是怎么知道具体实现步骤的?靠方法名推测么?