在启动时为开发环境预填充数据库模式和数据

在本地开发过程中,预填充数据库中的必要数据和模式是一种常见的做法,可以提高开发和测试工作流程的效率。通过模拟真实场景,这种做法有助于尽早发现前端问题,确保数据库管理员和软件工程师之间保持一致性,并促进更顺畅的协作。预填充具有诸如自信部署、环境一致性和尽早发现问题等优点,最终可以改善整个开发过程。

在本指南中,您将学习如何:

  • 使用 Docker 启动 Postgres 容器
  • 使用 SQL 脚本预填充 Postgres
  • 通过将 SQL 文件复制到 Docker 镜像中来预填充 Postgres
  • 使用 JavaScript 代码预填充 Postgres

使用 Docker 部署 Postgres

官方的 Docker Postgres 镜像 提供了一种在您的开发机器上运行 Postgres 数据库的便捷方法。Postgres Docker 镜像是一个预配置的环境,其中包含 PostgreSQL 数据库系统。它是一个自包含的单元,可以在 Docker 容器中运行。通过使用此镜像,您可以快速轻松地设置 Postgres 实例,而无需手动配置。

前提条件

按照本操作指南进行操作需要以下前提条件:

启动 Postgres

使用以下步骤快速演示 Postgres:

  1. 打开终端并运行以下命令以启动 Postgres 容器。

    此示例将启动一个 Postgres 容器,将端口 `5432` 映射到主机,以便本地运行的应用程序可以使用密码 `mysecretpassword` 连接到它。

    $ docker run -d --name postgres -p 5432:5432 -e POSTGRES_PASSWORD=mysecretpassword postgres
    
  2. 通过选择容器并在 Docker 仪表板上检查日志来验证 Postgres 是否已启动并正在运行。

    PostgreSQL Database directory appears to contain a database; Skipping initialization
    
    2024-09-08 09:09:47.136 UTC [1] LOG:  starting PostgreSQL 16.4 (Debian 16.4-1.pgdg120+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
    2024-09-08 09:09:47.137 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
    2024-09-08 09:09:47.137 UTC [1] LOG:  listening on IPv6 address "::", port 5432
    2024-09-08 09:09:47.139 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
    2024-09-08 09:09:47.142 UTC [29] LOG:  database system was shut down at 2024-09-08 09:07:09 UTC
    2024-09-08 09:09:47.148 UTC [1] LOG:  database system is ready to accept connections
  3. 从本地系统连接到 Postgres。

    `psql` 是 PostgreSQL 交互式 shell,用于连接到 Postgres 数据库并允许您开始执行 SQL 命令。假设您已经在本地系统上安装了 `psql` 实用程序,现在可以连接到 Postgres 数据库了。在本地终端运行以下命令:

    $ docker exec -it postgres psql -h localhost -U postgres
    

    您现在可以在 `psql` 提示符下执行所需的任何 SQL 查询或命令。

    使用 `\q` 或 `\quit` 退出 Postgres 交互式 shell。

使用 SQL 脚本预填充 Postgres 数据库

现在您已经熟悉了 Postgres,是时候了解如何使用示例数据预填充它了。在此演示中,您首先将创建一个包含 SQL 命令的脚本。该脚本定义数据库和表结构,并插入示例数据。然后,您将连接到数据库以验证数据。

假设您已经有一个正在运行的 Postgres 数据库实例,请按照以下步骤预填充数据库。

  1. 创建一个名为 `seed.sql` 的空文件,并添加以下内容。

    CREATE DATABASE sampledb;
    
    \c sampledb
    
    CREATE TABLE users (
      id SERIAL PRIMARY KEY,
      name VARCHAR(50),
      email VARCHAR(100) UNIQUE
    );
    
    INSERT INTO users (name, email) VALUES
      ('Alpha', 'alpha@example.com'),
      ('Beta', 'beta@example.com'),
      ('Gamma', 'gamma@example.com');  

    SQL 脚本创建一个名为 `sampledb` 的新数据库,连接到它,并创建一个 `users` 表。该表包含一个自动递增的 `id` 作为主键,一个最大长度为 50 个字符的 `name` 字段,以及一个最大长度为 100 个字符的唯一 `email` 字段。

    创建表后, `INSERT` 命令将三个用户及其各自的姓名和电子邮件插入到 `users` 表中。此设置形成了一个基本的数据库结构,用于存储具有唯一电子邮件地址的用户的信息。

  2. 预填充数据库。

    现在可以使用 `<` 运算符将 `seed.sql` 的内容直接输入数据库。该命令用于对名为 `sampledb` 的 Postgres 数据库执行名为 `seed.sql` 的 SQL 脚本。

    $ cat seed.sql | docker exec -i postgres psql -h localhost -U postgres -f-
    

    执行查询后,您将看到以下结果:

    CREATE DATABASE
    You are now connected to database "sampledb" as user "postgres".
    CREATE TABLE
    INSERT 0 3
  3. 运行以下 `psql` 命令以验证名为 users 的表是否已填充到数据库 `sampledb` 中。

    $ docker exec -it postgres psql -h localhost -U postgres sampledb
    

    您现在可以在 `psql` shell 中运行 `\l` 以列出 Postgres 服务器上的所有数据库。

    sampledb=# \l
                                                 List of databases
    Name    |  Owner   | Encoding |  Collate   |   Ctype    | ICU Locale | Locale Provider |   Access privileges
    -----------+----------+----------+------------+------------+------------+-----------------+-----------------------
    postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            |
    sampledb  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            |
    template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | =c/postgres          +
              |          |          |            |            |            |                 | postgres=CTc/postgres
    template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 |            | libc            | =c/postgres          +
              |          |          |            |            |            |                 | postgres=CTc/postgres
    (4 rows)
    

    要从 users 表中检索所有数据,请输入以下查询:

    sampledb=# SELECT * FROM users;
    id | name  |       email
    ----+-------+-------------------
     1 | Alpha | alpha@example.com
     2 | Beta  | beta@example.com
     3 | Gamma | gamma@example.com
    (3 rows)
    

    使用 `\q` 或 `\quit` 退出 Postgres 交互式 shell。

通过绑定挂载 SQL 脚本预填充数据库

在 Docker 中,挂载是指使主机系统中的文件或目录可在容器内访问。这使您可以共享主机和容器之间的数据或配置文件,从而实现更大的灵活性和持久性。

现在您已经学习了如何启动 Postgres 并使用 SQL 脚本预填充数据库,是时候学习如何将 SQL 文件直接挂载到 Postgres 容器的初始化目录 ( `/docker-entrypoint-initdb.d` ) 中了。 `/docker-entrypoint-initdb.d` 是 PostgreSQL Docker 容器中的一个特殊目录,用于在容器首次启动时初始化数据库。

在执行以下步骤之前,请确保停止任何正在运行的 Postgres 容器(以及卷),以防止端口冲突。

$ docker container stop postgres
  1. 使用以下条目修改 `seed.sql`:

    CREATE TABLE IF NOT EXISTS users (
     id SERIAL PRIMARY KEY,
     name VARCHAR(50),
     email VARCHAR(100) UNIQUE
    );
    
    INSERT INTO users (name, email) VALUES
     ('Alpha', 'alpha@example.com'),
     ('Beta', 'beta@example.com'),
     ('Gamma', 'gamma@example.com')
    ON CONFLICT (email) DO NOTHING;
  2. 创建一个名为 `Dockerfile` 的文本文件,并复制以下内容。

    # syntax=docker/dockerfile:1
    FROM postgres:latest
    COPY seed.sql /docker-entrypoint-initdb.d/

    此 Dockerfile 将 `seed.sql` 脚本直接复制到 PostgreSQL 容器的初始化目录。

  3. 使用 Docker Compose。

    使用 Docker Compose 可以更轻松地管理和部署包含已预填充数据库的 PostgreSQL 容器。此 compose.yml 文件定义了一个名为 `db` 的 Postgres 服务,使用最新的 Postgres 镜像,该镜像设置了一个名为 `sampledb` 的数据库,以及一个用户 `postgres` 和一个密码 `mysecretpassword`。

    services:
      db:
        build:
          context: .
          dockerfile: Dockerfile
        container_name: my_postgres_db
        environment:
          POSTGRES_USER: postgres
          POSTGRES_PASSWORD: mysecretpassword
          POSTGRES_DB: sampledb
        ports:
          - "5432:5432"
        volumes:
          - data_sql:/var/lib/postgresql/data   # Persistent data storage
    
    volumes:
      data_sql:

    它将主机上的端口 `5432` 映射到容器的 `5432`,允许您从容器外部访问 Postgres 数据库。它还定义了 `data_sql` 用于持久化数据库数据,确保在容器停止时不会丢失数据。

    需要注意的是,只有当您想从非容器化程序连接到数据库时,才需要将端口映射到主机。如果您将连接到数据库的服务容器化,则应通过自定义桥接网络连接到数据库。

  4. 启动 Compose 服务。

    假设您已将seed.sql文件放在与Dockerfile相同的目录中,请执行以下命令:

    $ docker compose up -d --build
    
  5. 现在,验证users表是否已填充数据。

    $ docker exec -it my_postgres_db psql -h localhost -U postgres sampledb
    
    sampledb=# SELECT * FROM users;
      id | name  |       email
    ----+-------+-------------------
       1 | Alpha | alpha@example.com
       2 | Beta  | beta@example.com
       3 | Gamma | gamma@example.com
     (3 rows)
    
    sampledb=#

使用 JavaScript 代码预填充数据库

现在您已经学习了如何使用各种方法(例如SQL脚本、挂载卷等)来填充数据库,接下来尝试使用JavaScript代码来实现。

  1. 创建一个包含以下内容的 .env 文件:

    POSTGRES_USER=postgres
    POSTGRES_DB_HOST=localhost
    POSTGRES_DB=sampledb
    POSTGRES_PASSWORD=mysecretpassword
    POSTGRES_PORT=5432
  2. 创建一个名为 seed.js 的新 JavaScript 文件,内容如下:

    这段 JavaScript 代码导入dotenv包,该包用于从.env文件加载环境变量。.config()方法读取.env文件并将环境变量设置为process.env对象的属性。这使您可以安全地将敏感信息(如数据库凭据)存储在代码之外。

    然后,它从 pg 库创建一个新的 Pool 实例,该实例提供连接池以实现高效的数据库交互。定义了seedData函数来执行数据库填充操作。脚本结尾处调用它来启动填充过程。try...catch...finally 块用于错误处理。

    require('dotenv').config();  // Load environment variables from .env file
    const { Pool } = require('pg');
    
    // Create a new pool using environment variables
    const pool = new Pool({
      user: process.env.POSTGRES_USER,
      host: process.env.POSTGRES_DB_HOST,
      database: process.env.POSTGRES_DB,
      port: process.env.POSTGRES_PORT,
      password: process.env.POSTGRES_PASSWORD,
    });
    
    const seedData = async () => {
      try {
         // Drop the table if it already exists (optional)
         await pool.query(`DROP TABLE IF EXISTS todos;`);
    
         // Create the table with the correct structure
         await pool.query(`
           CREATE TABLE todos (
             id SERIAL PRIMARY KEY,
             task VARCHAR(255) NOT NULL,
             completed BOOLEAN DEFAULT false
               );
         `   );
    
         // Insert seed data
         await pool.query(`
           INSERT INTO todos (task, completed) VALUES
           ('Watch netflix', false),
           ('Finish podcast', false),
           ('Pick up kid', false);
           `);
           console.log('Database seeded successfully!');
         } catch (err) {
           console.error('Error seeding the database', err);
         } finally {
           pool.end();
        }
      };
    
      // Call the seedData function to run the script
      seedData();
  3. 启动填充过程:

    $ node seed.js
    

    您应该看到以下命令:

    Database seeded successfully!
  4. 验证数据库是否已正确填充。

    $ docker exec -it postgres psql -h localhost -U postgres sampledb
    
    sampledb=# SELECT * FROM todos;
    id |      task      | completed
    ----+----------------+-----------
    1 | Watch netflix  | f
    2 | Finish podcast | f
    3 | Pick up kid    | f
    (3 rows)  
    

回顾

在启动时预先填充数据库模式和数据对于创建一致且真实的测试环境至关重要,这有助于尽早发现开发中的问题并协调前端和后端工作。本指南为您提供了使用各种方法(包括 SQL 脚本、Docker 集成和 JavaScript 代码)实现预填充的知识和实践步骤。