使用 Spring Boot 和 MySql 写一个简单应用
剧本介绍
在这篇 walk-through 里面我们会:
- 使用 MySql 建一个数据库
- 使用 Sprint Boot 连接数据库并写好一个简单的接口
- 使用 Postman 调用接口
- 使用 Swagger 生成接口文档
1 和 2 参考了 Spring Boot 的这篇 Accessing data with MySQL,缝合了 Error Handling for REST with Spring;3 和 4 是作者作为缝合怪补充的内容
原版使用的场景是保存用户的姓名和邮箱,这里改编成了在数据库里保存学生的姓名和成绩
MySQL
官网下载安装(米老猫使用的是 Windows 环境,所以就贴 Windows 链接了)
安装过程中会输入 root 账号的密码,这个密码需要记住
安装完毕之后添加系统环境变量 👇(详细步骤可参考 Java 篇安装 JDK)
连接 MySQL 服务器的命令:
mysql -h host -u user -p
本机运行可以省略 host,这里我们以 root 身份登录,输入安装时填写的密码:
mysql -u root -p
Enter password: ******
想要退出的时候输入 quit 回车即可
mysql> quit
下面我们以 root 身份新建一个数据库和一个用户,使这个用户拥有该数据库的权限
这里假设我们的数据库是 “学校”,我们想要在数据库里存学生的 id,姓名,和成绩
创建的用户叫 julia,登录密码是 123456
mysql> create database school;
mysql> create user 'julia'@'%' identified by '123456';
mysql> grant all on school.* to 'julia'@'%';
- 命令需要以分号结尾
- ”@” 后面跟的是 hostname,省略的时候相当于 “%”,表示该用户可以从任意主机连接服务器
- 第一步创建数据库,第二部创建用户,第三步给予权限
Spring Boot
使用 Spring Initializr 下载初始化项目文件的压缩包
新建 src/main/resouces/application.properties
文件,相应修改数据库名称、用户名,和用户密码
这一步用来指明要连接哪个数据库,对应的登录信息是什么
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://${MYSQL_HOST:localhost}:3306/school
spring.datasource.username=julia
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring.jpa.show-sql: true
新建 src/main/java/com/example/accessingdatamysql/Student.java
文件
@Entity
标注了这个类会用来建表
这一步定义了表的结构,有 id、name、grade 这几列,其中 id 会自动生成
package com.example.accessingdatamysql;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
@Entity
public class Student {
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Integer id;
private String name;
private Integer grade;
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setGrade(Integer grade) {
this.grade = grade;
}
public Integer getGrade() {
return this.grade;
}
}
新建 StudentRepository.java
这一步让我们可以用实例去处理一些增删改查的逻辑
package com.example.accessingdatamysql;
import org.springframework.data.repository.CrudRepository;
public interface StudentRepository extends CrudRepository<Student, Integer> {
}
新建 MainController.java
这里定义了两个 POST 接口,输入和输出都是 JSON
package com.example.accessingdatamysql;
import com.example.accessingdatamysql.exception.StudentNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.Optional;
@Controller
@RequestMapping(path="/students")
public class MainController {
@Autowired
private StudentRepository studentRepository;
@PostMapping(path="/add")
public @ResponseBody String addStudent(@RequestBody Student student) {
studentRepository.save(student);
return String.format("Student %s added", student.getName());
}
@PostMapping(path="/grade")
public ResponseEntity<Object> getGrade(@RequestBody Input input) {
Optional<Student> studentOptional = studentRepository.findById(input.id());
if (studentOptional.isEmpty()) throw new StudentNotFoundException();
return new ResponseEntity<>(studentOptional.get(), HttpStatus.OK);
}
}
第一个接口的 request 类是之前定义过的 Student,第二个接口的 request 类定义如下
package com.example.accessingdatamysql;
public record Input(int id) {}
资深 Java 工程师这样说:一般一个接口就定义一个 request 类、一个 response 类,代码即协议,在上面还能写注释
第二个接口里,当输入的 id 在数据库里找不到时,抛出了一个异常类的实例,下面我们来详细看看这个异常类是怎么定义的
资深 Java 工程师这样说:一般业务异常可以都放在 response 里,给出 errorCode 和 errorMsg,然后 http status 还是 200;返回给前端的时候,一般有一步是把异常统一转化为统一的 response,有多种实现方式,介绍几种,关键词 aop,@controlleradvice,@exceptionhandler
在 src/main/java/com/example/accessingdatamysql/exception
下新建 SchoolExceptionController.java
文件
@ControllerAdvice
代表这里是全局异常处理的入口
@ExceptionHandler
后面定义了每一种类型异常的处理方法
package com.example.accessingdatamysql.exception;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class SchoolExceptionController {
@ExceptionHandler(value = StudentNotFoundException.class)
public ResponseEntity<Object> exception(StudentNotFoundException exception) {
return new ResponseEntity<>(new Error(12345, "This student does not exist. "), HttpStatus.OK);
}
}
package com.example.accessingdatamysql.exception;
public record Error(Integer errorCode, String errorMsg) {}
package com.example.accessingdatamysql.exception;
public class StudentNotFoundException extends RuntimeException {
}
结合起来看,当我们抛出一个异常:
throw new StudentNotFoundException();
SchoolExceptionController
会接到这个异常
因为这个异常是 StudentFoundException
类的一个实例,会调用 exception
方法进行处理
最终会返回一个状态码为 200,body 为 { errorCode: 12345, errorMsg: "This student does not exist. " }
的 response
接下来,我们可以用 ./gradlew bootRun
或 ./mvnw spring-boot:run
让程序跑起来
curl -X POST http://localhost:8080/students/add -H 'Content-Type: application/json' -d '{ "name": "Julia", "grade": 100 }'
添加成功后应该可以看到 Student Rpuck added
类似地,可以用 cURL 命令获取学生的成绩
curl -X POST http://localhost:8080/students/grade -H 'Content-Type: application/json' -d '{ "id": 1 }'
得到 {"name":"Julia","grade":100}
最后,通过 MySQL 命令:
查看所有数据库
mysql> show databases;
选中其中一个数据库,以我们之前建的 school
为例
mysql> use school;
查看该数据库里的所有表
mysql> show tables;
查看表里所有记录
mysql> select * from student;
Postman
一个很好用的接口测试工具,虽然使用 cURL 命令也可以达到同样的目的,不过 Postman 在用户体验上做得还是非常不错的,提供了一个简单易用的 UI 界面
Swagger
TBC