在 Docker Compose 中运行 PostgreSQL 和 Spring Boot

1、概览

本文将带你了解如何使用 Docker Compose 来运行 Spring Boot 和 PostgreSQL。

2、创建 Spring Boot 应用

Spring Initializer 创建 Spring Boot 项目,添加 PostgreSQL 驱动和 Spring Data JPA 依赖。下载生成的 ZIP 文件并解压到文件夹后,就可以运行应用了:

./mvnw spring-boot:run

应用启动失败,因为连接数据库失败:

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class

3、Dockerfile

在使用 Docker Compose 启动 PostgreSQL 之前,需要将 Spring Boot 应用转化为 Docker 镜像。第一步是将应用打包为 JAR 文件:

./mvnw clean package -DskipTests

如上,首先清理之前的构建,然后再打包应用。而且跳过测试,因为没有 PostgreSQL,测试会失败。

现在,在 target 目录中有一个应用 JAR 文件。该文件的名称中包含了项目名称和版本号,并以 -SNAPSHOT.jar 结尾:docker-spring-boot-postgres-0.0.1-SNAPSHOT.jar

创建新的 src/main/docker 目录。然后,将应用 JAR 文件复制到该目录:

cp target/docker-spring-boot-postgres-0.0.1-SNAPSHOT.jar src/main/docker

最后,在同一目录下创建如下 Dockerfile

FROM adoptopenjdk:11-jre-hotspot
ARG JAR_FILE=*.jar
COPY ${JAR_FILE} application.jar
ENTRYPOINT ["java", "-jar", "application.jar"]

该文件描述了 Docker 应如何运行 Spring Boot 应用。它使用 AdoptOpenJDK 中的 Java 11,并将应用 JAR 文件复制到 application.jar。然后运行该 JAR 文件来启动 Spring Boot 应用。

4、Docker Compose 文件

现在,编写 Docker Compose 文件 docker-compose.yml,并将其保存在 src/main/docker 中:

version: '2'

services:
  app:
    image: 'docker-spring-boot-postgres:latest'
    build:
      context: .
    container_name: app
    depends_on:
      - db
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/compose-postgres
      - SPRING_DATASOURCE_USERNAME=compose-postgres
      - SPRING_DATASOURCE_PASSWORD=compose-postgres
      - SPRING_JPA_HIBERNATE_DDL_AUTO=update
          
  db:
    image: 'postgres:13.1-alpine'
    container_name: db
    environment:
      - POSTGRES_USER=compose-postgres
      - POSTGRES_PASSWORD=compose-postgres

应用名为 app。它是两个服务中的第一个(第 4-15 行):

  • Spring Boot 的 Docker 镜像名为 docker-spring-boot-postgres:late(第 5 行)。Docker 会根据当前目录中的 Dockerfile 构建该镜像(第 6-7 行)。
  • 容器名称是 app(第 8 行)。它依赖于 db 服务(第 10 行)。这就是它在 db 容器之后启动的原因。
  • 应用使用 db PostgreSQL 容器作为数据源(第 12 行)。数据库名称、用户名和密码都是 compose-postgres(第 12-14 行)。
  • Hibernate 会自动创建或更新所需的数据库表(第 15 行)。

PostgreSQL 数据库名为 db,是第二个服务(第 17-22 行):

  • 使用 PostgreSQL 13.1(第 18 行)。
  • 容器名称为 db(第 19 行)。
  • 用户名和密码都是 compose-postgres(第 21-22 行)。

5、使用 Docker Compose 运行

使用 Docker Compose 运行 Spring Boot 应用和 PostgreSQL:

docker-compose up

首先,它会为 Spring Boot 应用构建 Docker Image。接着,启动 PostgreSQL 容器。最后,启动应用 Docker 镜像。

这一次,应用运行正常:

Starting DemoApplication v0.0.1-SNAPSHOT using Java 11.0.9 on f94e79a2c9fc with PID 1 (/application.jar started by root in /)
[...]
Finished Spring Data repository scanning in 28 ms. Found 0 JPA repository interfaces.
[...]
Started DemoApplication in 4.751 seconds (JVM running for 6.512)

如你所见,Spring Data 没有找到 JPA Repository 接口,因为我们还没创建。

按下 Ctrl-C 来停止所有容器,然后停止 Docker Compose:

docker-compose down

6、创建 Customer Entity 和 Repository

为了在应用中使用 PostgreSQL 数据库,创建一个简单的 Customer 实体:

@Entity
@Table(name = "customer")
public class Customer {

    @Id
    @GeneratedValue
    private long id;
    
    @Column(name = "first_name", nullable = false)
    private String firstName;
    
    @Column(name = "last_name", nullable = false)
    private String lastName;

    // get / set 等方法省略
}

Customer 有一个自动生成的 id 属性和两个必选属性:firstNamelastName

然后,创建其对应的 Repository 接口:

public interface CustomerRepository extends JpaRepository<Customer, Long> { }

通过继承 JpaRepository,获得用于创建和查询 Customer 实体的方法。

最后,在应用中使用这些方法:

@SpringBootApplication
public class DemoApplication {
    @Autowired 
    private CustomerRepository repository; 
  
    @EventListener(ApplicationReadyEvent.class)
    public void runAfterStartup() {
        List allCustomers = this.repository.findAll(); 
        logger.info("Number of customers: " + allCustomers.size());
 
        Customer newCustomer = new Customer(); 
        newCustomer.setFirstName("John"); 
        newCustomer.setLastName("Doe"); 
        logger.info("Saving new customer..."); 
        this.repository.save(newCustomer); 
 
        allCustomers = this.repository.findAll(); 
        logger.info("Number of customers: " + allCustomers.size());
    }
}
  • 通过字段注入 CustomerRepository 依赖。
  • 查询现有 Customer 的数量,结果为 0。
  • 然后,创建并保存一个 Customer
  • 再次查询现有 Customer,就会检索到刚刚创建的 Customer

7、再次运行 Docker Compose

要运行更新后的 Spring Boot 应用,需要先重新构建它。因此,要在项目根目录下再次执行这些命令:

./mvnw clean package -DskipTests
cp target/docker-spring-boot-postgres-0.0.1-SNAPSHOT.jar src/main/docker

如何用更新后的应用 JAR 文件重建 Docker 镜像?最好的办法是删除在 docker-compose.yml 中指定名称的现有 Docker 镜像。这样,当下一次启动 Docker Compose 文件时,Docker 就会重新构建镜像:

cd src/main/docker
docker-compose down
docker rmi docker-spring-boot-postgres:latest
docker-compose up

因此,在停止容器后,删除应用的 Docker 镜像。然后,再次启动 Docker Compose,重建应用镜像。

应用输出的日志如下:

Finished Spring Data repository scanning in 180 ms. Found 1 JPA repository interfaces.
[...]
Number of customers: 0
Saving new customer...
Number of customers: 1

如上,首先检索 Repository 中的 Customer 数量,结果为 0,然后创建新的 Customer 实体,保存。最后,再次检索,结果为 1。

符合预期。

8、总结

本文介绍了如何在 Docker Compose 中运行使用 PostgreSQL 的 Spring Boot 应用。


Ref:https://www.baeldung.com/spring-boot-postgresql-docker