使用 Spring Boot 和 MongoDB 构建 REST 服务

以学生信息管理(增删改查)为例。主要目的是,通过开发这样一个小业务功能,熟悉 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)