Spring Boot 与 Docker
观察 GraphQL 的实际运行

本指南将引导您完成创建一个连接到 MySQL 数据库的 Spring 应用程序的过程(与大多数其他指南和许多示例应用程序使用的内存嵌入式数据库不同)。它使用 Spring Data JPA 来访问数据库,但这只是众多可能选择中的一种(例如,您也可以使用普通的 Spring JDBC)。

你将构建什么

您将创建一个 MySQL 数据库,构建一个 Spring 应用程序,并将其连接到新创建的数据库。

MySQL 使用 GPL 许可证,因此您随其分发的任何程序二进制文件也必须使用 GPL。请参阅 GNU 通用公共许可证

所需内容

  • 大约 15 分钟

  • 您喜欢的文本编辑器或 IDE

  • Java 17 或更高版本

如何完成本指南

与大多数 Spring 入门指南 一样,您可以从头开始并完成每个步骤,或者通过查看 此仓库 中的代码直接跳到解决方案。

要在本地环境中查看最终结果,您可以执行以下操作之一:

配置 MySQL 数据库

在构建应用程序之前,您首先需要配置一个 MySQL 数据库。本指南假设您使用 Spring Boot Docker Compose 支持。此方法的前提是您的开发机器有一个 Docker 环境,例如 Docker Desktop。添加一个 spring-boot-docker-compose 依赖项,它具有以下功能:

  • 在您的工作目录中搜索 compose.yml 和其他常见的 compose 文件名

  • 使用发现的 compose.yml 调用 docker compose up

  • 为每个受支持的容器创建服务连接 bean

  • 在应用程序关闭时调用 docker compose stop

要使用 Docker Compose 支持,您只需遵循本指南即可。根据您引入的依赖项,Spring Boot 会找到正确的 compose.yml 文件,并在运行应用程序时启动您的 Docker 容器。

使用 Spring Initializr 开始

您可以使用这个预初始化项目,并点击生成以下载一个ZIP文件。该项目已配置为适应本教程中的示例。

要手动初始化项目:

  1. 访问 https://start.spring.io。该服务会拉取应用程序所需的所有依赖项,并为您完成大部分设置工作。

  2. 选择 Gradle 或 Maven 以及您要使用的语言。本指南假设您选择了 Java。

  3. 点击 Dependencies,然后选择 Spring WebSpring Data JPAMySQL DriverDocker Compose SupportTestcontainers

  4. 点击 Generate

  5. 下载生成的 ZIP 文件,这是一个根据您的选择配置的 Web 应用程序的归档文件。

如果您的 IDE 集成了 Spring Initializr,您可以直接在 IDE 中完成此过程。

创建 @Entity 模型

您需要创建实体模型,如下面的清单所示(位于 src/main/java/com/example/accessingdatamysql/User.java 中):

package com.example.accessingdatamysql;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity // This tells Hibernate to make a table out of this class
public class User {
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private Integer id;

  private String name;

  private String email;

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getEmail() {
    return email;
  }

  public void setEmail(String email) {
    this.email = email;
  }
}
Plain text

Hibernate 会自动将实体转换为表。

创建仓库

您需要创建一个用于存储用户记录的存储库,如下面的代码清单(位于 src/main/java/com/example/accessingdatamysql/UserRepository.java)所示:

package com.example.accessingdatamysql;

import org.springframework.data.repository.CrudRepository;

import com.example.accessingdatamysql.User;

// This will be AUTO IMPLEMENTED by Spring into a Bean called userRepository
// CRUD refers Create, Read, Update, Delete

public interface UserRepository extends CrudRepository<User, Integer> {

}
Plain text

Spring 会自动在一个 bean 中实现这个仓库接口,该 bean 具有相同的名称(只是大小写有所变化——它被称为 userRepository)。

创建控制器

您需要创建一个控制器来处理对应用程序的HTTP请求,如下面的代码清单所示(位于src/main/java/com/example/accessingdatamysql/MainController.java中):

package com.example.accessingdatamysql;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller // This means that this class is a Controller
@RequestMapping(path="/demo") // This means URL's start with /demo (after Application path)
public class MainController {
  @Autowired // This means to get the bean called userRepository
         // Which is auto-generated by Spring, we will use it to handle the data
  private UserRepository userRepository;

  @PostMapping(path="/add") // Map ONLY POST Requests
  public @ResponseBody String addNewUser (@RequestParam String name
      , @RequestParam String email) {
    // @ResponseBody means the returned String is the response, not a view name
    // @RequestParam means it is a parameter from the GET or POST request

    User n = new User();
    n.setName(name);
    n.setEmail(email);
    userRepository.save(n);
    return "Saved";
  }

  @GetMapping(path="/all")
  public @ResponseBody Iterable<User> getAllUsers() {
    // This returns a JSON or XML with the users
    return userRepository.findAll();
  }
}
Plain text

前面的例子明确指定了两个端点的 POSTGET 方法。默认情况下,@RequestMapping 会映射所有的 HTTP 操作。

创建一个应用程序类

Spring Initializr 为应用程序创建了一个简单的类。以下清单展示了 Initializr 为本示例创建的类(位于 src/main/java/com/example/accessingdatamysql/AccessingDataMysqlApplication.java 文件中):

package com.example.accessingdatamysql;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class AccessingDataMysqlApplication {

  public static void main(String[] args) {
    SpringApplication.run(AccessingDataMysqlApplication.class, args);
  }

}
Plain text

对于这个示例,您无需修改 AccessingDataMysqlApplication 类。

Spring Initializr 将 @SpringBootApplication 注解添加到我们的主类中。@SpringBootApplication 是一个便捷的注解,它添加了以下所有内容:

  • @Configuration: 将该类标记为应用上下文的bean定义来源。

  • @EnableAutoConfiguration: Spring Boot 根据您添加的依赖项尝试自动配置您的 Spring 应用程序。

  • @ComponentScan: 告诉 Spring 查找其他组件、配置和服务。如果没有定义特定的包,递归扫描将从声明该注解的类所在的包开始。

运行应用程序

此时,您可以运行应用程序以查看代码的实际效果。您可以通过 IDE 或命令行运行主方法。请注意,如果您从解决方案仓库克隆了项目,您的 IDE 可能会在错误的位置查找 compose.yaml 文件。您可以配置 IDE 以查找正确的位置,或者使用命令行运行应用程序。./gradlew bootRun./mvnw spring-boot:run 命令会启动应用程序并自动找到 compose.yaml 文件。

测试应用程序

现在应用程序已经运行,您可以使用 curl 或类似的工具进行测试。您有两个可以测试的 HTTP 端点:

GET localhost:8080/demo/all:获取所有数据。 POST localhost:8080/demo/add:向数据中添加一个用户。

以下 curl 命令用于添加用户:

$ curl http://localhost:8080/demo/add -d name=First -d email=someemail@someemailprovider.com
Plain text

回复应如下:

Saved
Plain text

以下命令显示所有用户:

$ curl http://localhost:8080/demo/all
Plain text

回复应如下所示:

[{"id":1,"name":"First","email":"someemail@someemailprovider.com"}]
Plain text

准备构建应用程序

为了打包和运行应用程序,我们需要提供一个外部的 MySQL 数据库,而不是使用 Spring Boot Docker Compose 支持。为此,我们可以重用提供的 compose.yaml 文件,并进行一些修改:首先,将 compose.yaml 中的 ports 条目修改为 3306:3306。其次,添加一个 container_nameguide-mysql

完成这些步骤后,compose.yaml 文件应为:

services:
  mysql:
    container_name: 'guide-mysql'
    image: 'mysql:latest'
    environment:
      * 'MYSQL_DATABASE=mydatabase'
      * 'MYSQL_PASSWORD=secret'
      * 'MYSQL_ROOT_PASSWORD=verysecret'
      * 'MYSQL_USER=myuser'
    ports:
      * '3306:3306'
Plain text

现在您可以运行 docker compose up 来启动这个 MySQL 容器。

第三,我们需要告诉我们的应用程序如何连接到数据库。这一步之前是由 Spring Boot Docker Compose 支持自动处理的。为此,请修改 application.properties 文件,使其现在如下所示:

spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/mydatabase
spring.datasource.username=myuser
spring.datasource.password=secret
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.show-sql: true
Plain text

构建应用程序

本节介绍运行本指南的不同方法:

  1. 构建并执行 JAR 文件

  2. 使用 Cloud Native Buildpacks 构建并执行 Docker 容器

  3. 构建并执行原生镜像

  4. 使用 Cloud Native Buildpacks 构建并执行原生镜像容器

无论您选择如何运行应用程序,输出结果都应该是一致的。

要运行应用程序,您可以将应用程序打包为可执行的 jar 文件。./gradlew clean build 命令将应用程序编译为可执行的 jar 文件。然后,您可以使用 java -jar build/libs/accessing-data-mysql-0.0.1-SNAPSHOT.jar 命令来运行该 jar 文件。

或者,如果您有可用的 Docker 环境,您可以直接使用 Maven 或 Gradle 插件通过 buildpacks 创建一个 Docker 镜像。借助 Cloud Native Buildpacks,您可以创建可在任何地方运行的 Docker 兼容镜像。Spring Boot 直接支持 Maven 和 Gradle 的 buildpack 功能。这意味着您可以输入一个命令,快速将一个合理的镜像推送到本地运行的 Docker 守护进程中。要使用 Cloud Native Buildpacks 创建 Docker 镜像,请运行 ./gradlew bootBuildImage 命令。在启用 Docker 环境的情况下,您可以使用 docker run --network container:guide-mysql docker.io/library/accessing-data-mysql:0.0.1-SNAPSHOT 命令来运行应用程序。

--network 标志告诉 Docker 将我们的指南容器附加到外部容器正在使用的现有网络中。您可以在 Docker 文档 中找到更多信息。

原生镜像支持

Spring Boot 还支持 编译为原生镜像,前提是您的机器上安装了 GraalVM 发行版。要使用 Native Build Tools 创建 Gradle 原生镜像,首先确保您的 Gradle 构建包含一个 plugins 块,其中包含 org.graalvm.buildtools.native

plugins {
    id 'org.graalvm.buildtools.native' version '0.9.28'
...
Plain text

然后,您可以运行 ./gradlew nativeCompile 命令来生成原生镜像。当构建完成后,您可以通过执行 build/native/nativeCompile/accessing-data-mysql 命令来运行代码,启动时间几乎瞬间完成。

您还可以使用 Buildpacks 创建原生镜像。通过运行 ./gradlew bootBuildImage 命令,您可以生成原生镜像。构建完成后,您可以使用 docker run --network container:guide-mysql docker.io/library/accessing-data-mysql:0.0.1-SNAPSHOT 命令启动您的应用程序。

在 Docker 中测试应用程序

如果您使用上面的 Docker 指令运行了应用程序,那么从终端或命令行执行的简单 curl 命令将不再起作用。这是因为我们是在一个Docker 网络中运行容器,而该网络无法从终端或命令行访问。要运行 curl 命令,我们可以启动第三个容器来执行 curl 命令,并将其连接到同一网络中。

首先,获取一个在新容器上运行的交互式 shell,该容器与 MySQL 数据库和应用程序在同一网络中:

docker run --rm --network container:guide-mysql -it alpine
Plain text

接下来,在容器内的 shell 中安装 curl:

apk add curl
Plain text

最后,您可以按照测试应用中的描述运行 curl 命令。

进行一些安全更改

当您处于生产环境中时,可能会面临SQL注入攻击的风险。黑客可能会注入DROP TABLE或其他破坏性的SQL命令。因此,作为一种安全实践,在将应用程序暴露给用户之前,您应该对数据库进行一些更改。

以下命令撤销与Spring应用程序关联的用户的所有权限:

mysql> revoke all on db_example.* from 'myuser'@'%';
Plain text

现在 Spring 应用程序无法在数据库中执行任何操作。

应用程序必须具备某些权限,因此请使用以下命令来授予应用程序所需的最低权限:

mysql> grant select, insert, delete, update on db_example.* to 'myuser'@'%';
Plain text

移除所有权限并授予部分权限,可以让您的 Spring 应用程序仅拥有修改数据库数据而非结构(schema)的必要权限。

当您需要修改数据库时:

  1. 重新授予权限。

  2. spring.jpa.hibernate.ddl-auto 改为 update

  3. 重新运行您的应用程序。

然后重复此处显示的两个命令,使您的应用程序再次安全地用于生产环境。更好的做法是使用专门的迁移工具,例如 Flyway 或 Liquibase。

总结

恭喜!您刚刚开发了一个绑定到 MySQL 数据库的 Spring 应用程序,并且已经准备好投入生产!

另请参阅

以下指南可能也会有所帮助: