BlockLune's Blog

首页 标签 关于 |

如何在 Golang 中连接数据库

发表于 2024-11-11
更新于 2024-11-21
更新于 7 日内 许可证 CC BY-NC-SA 4.0 programming-languagegolangdatabase

本文以 MySQL 和 PostgreSQL 为例,介绍了如何在 Golang 中连接数据库。

准备工作

您需要准备一个 MySQL 和/或一个 PostgreSQL。

如果您安装了 Docker,可以使用以下命令快速创建即用即删的 MySQL 或 PostgreSQL 容器以供测试。我写了一篇文章简单介绍了 Docker,如果您不清楚什么是 Docker,可以看一看。

下面的命令创建一个名为 mysql-server 的 MySQL 容器,用户名为 root,密码为 password,使用 3306 端口,并附带一个名为 example_db 的数据库:

docker run --rm --name mysql-server -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=example_db -p 3306:3306 -d mysql

下面的命令创建一个名为 postgres-server 的 PostgreSQL 容器,用户名为 postgres,密码为 password,使用 5432 端口,并附带一个名为 example_db 的数据库:

docker run --rm --name postgres-server -e POSTGRES_PASSWORD=password -e POSTGRES_DB=example_db -p 5432:5432 -d postgres

准备环境变量

为了在 Go 中连接数据库,我们需要一些连接信息,这些信息包括:

  • 主机名 HOST
  • 端口 PORT
  • 数据库名 NAME
  • 用户名 USER
  • 用户密码 PASSWORD

出于安全和可配置等原因,我们不应将这些信息直接写入源代码中。一个推荐的做法是使用环境变量进行管理。我创建了如下环境变量:

  • DB_HOST
  • DB_PORT
  • DB_NAME
  • DB_USER
  • DB_PASSWORD

如果您使用的是上面所说的以 Docker 方式创建的数据库,这些信息应当如下:

MySQL 版本:

DB_HOST=localhost
DB_PORT=3306
DB_NAME=example_db
DB_USER=root
DB_PASSWORD=password

PostgreSQL 版本:

DB_HOST=localhost
DB_PORT=5432
DB_NAME=example_db
DB_USER=postgres
DB_PASSWORD=password

创建示例项目并连接数据库

创建一个目录 go-db-example 并在其中运行下面的命令来创建一个 Go 项目:

go mod init example/db

可选地,您可以安装一个 godotenv 来使程序自动地从项目中的 .env 文件中读取环境变量:

go get github.com/joho/godotenv

为了连接数据库,您需要一个 驱动程序(Driver)

运行下面的命令将这些依赖项添加到项目中:

go get github.com/go-sql-driver/mysql
go get github.com/lib/pq

然后这样编辑 main.go 来连接数据库:

Caution

注意:下面的代码注释了 Postgres 版本的内容,也注释了使用了 godotenv 的代码

package main

import (
    "database/sql"
    "fmt"
    "os"

    // _ "github.com/joho/godotenv/autoload"
    _ "github.com/go-sql-driver/mysql"
    // _ "github.com/lib/pq"
)

var db *sql.DB

func initDb() {
    // get config from env variables
    dbHost := os.Getenv("DB_HOST")
    dbPort := os.Getenv("DB_PORT")
    dbName := os.Getenv("DB_NAME")
    dbUser := os.Getenv("DB_USER")
    dbPassword := os.Getenv("DB_PASSWORD")

    // construct the connection string
    // mysql
    connStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUser, dbPassword, dbHost, dbPort, dbName)
    // postgres
    // connStr := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", dbHost, dbPort, dbUser, dbPassword, dbName)

    // connect
    var err error
    db, err = sql.Open("mysql", connStr) // mysql
    // db, err = sql.Open("postgres", connStr) // postgres
    if err != nil {
       panic(err)
    }

    // check the connection
    err = db.Ping()
    if err != nil {
       panic(err)
    }
    fmt.Println("Successfully connected to database!")
}

func main() {
    initDb()

    // do something ...

    db.Close()
}

测试运行一下:

go run .

如果 Successfully connected to database! 成功输出了,那就说明数据库成功连接了。

初始化表

下面让我们来创建一张示例表 users,表中的数据项定义如下:

type User struct {
    ID       int64
    Email    string
    Password string
}

对应的 MySQL 建表语句如下:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL
);

对应的 PostgreSQL 建表语句如下:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL
);

Tip

PostgreSQL 中的 SERIAL 对应于 MySQL 中的 INT AUTO_INCREMENT 类型

在 Golang 中,使用 Exec() 来执行这样一条建表语句(如果您使用 PostgreSQL,请自行替换):

func createUsersTable() {
    query := `
    CREATE TABLE users (
        id INT AUTO_INCREMENT PRIMARY KEY,
        email VARCHAR(255) NOT NULL,
        password VARCHAR(255) NOT NULL
    )
    `

    _, err := db.Exec(query)
    if err != nil {
        panic(err)
    }
}

常用操作 CRUD

下面演示四种常用操作,即

  • Create
  • Read
  • Update
  • Delete

Create

让我们来用 INSERT 插入一条记录,相应的语句无论在 MySQL 中还是在 PostgreSQL 中都是一样的:

INSERT INTO users (email, password)
VALUES ('[email protected]', 'password123');

但是 Go 代码略微有些区别,体现在占位符上:

首先是 MySQL:

func addUser(email, password string) error {
    query := `
    INSERT INTO users (email, password)
    VALUES (?, ?)
    `

    _, err := db.Exec(query, email, password)
    if err != nil {
        return err
    }

    return nil
}

接着是 PostgreSQL:

func addUser(email, password string) error {
    query := `
    INSERT INTO users (email, password)
    VALUES ($1, $2)
    `

    _, err := db.Exec(query, email, password)
    if err != nil {
        return err
    }

    return nil
}

可以看到 MySQL 中使用 ? 作为占位符,而 PostgreSQL 中用的是 $1, $2 等。

Important

从现在开始,下面的代码均 默认使用 MySQL 语句,您需要替换占位符以适配 PostgreSQL。

事实上,Exec() 的执行结果返回了两个值。我们上面一直只使用了第二个 error 类型的值,判断语句执行是否出错。而第一个值的类型为 Result,借助它,我们可以获取诸如 “最后插入记录的 ID” 等信息:

result, err := db.Exec(query, email, password)
if err != nil {
    return err
}

userId, err := result.LastInsertId()
if err != nil {
    return err
}

Caution

需要注意 PostgreSQL 不支持 这个 LastInsertId()

如果我们需要执行许多插入操作,我们可以先 “准备” 一下查询语句,以提高性能:

stmt, err := db.Prepare(query)
if err != nil {
    return err
}
defer stmt.Close()

// assumming that there are a lot of records to insert
_, err = stmt.Exec(email, password)
if err != nil {
    return err
}

Read

可以使用 Query()QueryRow() 来进行查询,区别在于前者返回任意多行,后者返回至多一行:

func getUsers() ([]User, error) {
    query := `
    SELECT id, email, password
    FROM users
    `

    rows, err := db.Query(query)
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var users []User
    for rows.Next() {
        var user User
        err := rows.Scan(&user.ID, &user.Email, &user.Password)
        if err != nil {
            return nil, err
        }
        users = append(users, user)
    }

    if err = rows.Err(); err != nil {
        return nil, err
    }

    return users, nil
}
func getUserByEmail(email string) (*User, error) {
    query := `
    SELECT id, email, password
    FROM users
    WHERE email = ?
    `

    row := db.QueryRow(query, email)

    var user User
    err := row.Scan(&user.ID, &user.Email, &user.Password)
    if err != nil {
        return nil, err
    }

    return &user, nil
}

Update

func updateUserPassword(email, newPassword string) error {
    query := `
    UPDATE users
    SET password = ?
    WHERE email = ?
    `

    _, err := db.Exec(query, newPassword, email)
    if err != nil {
        return err
    }

    return nil
}

Delete

func deleteUserByEmail(email string) error {
    query := `
    DELETE FROM users
    WHERE email = ?
    `

    _, err := db.Exec(query, email)
    if err != nil {
        return err
    }

    return nil
}

下一步(GORM)

在实际工程中,连接数据库更多地采用 ORM 方式。ORM(Object-Relational Mapping,对象关系映射)是一种在面向对象编程语言中实现数据持久化的一种技术。它的基本思想是将关系数据库中的数据表映射到编程语言中的对象,从而允许开发者使用面向对象的编程技术来操作数据库。

GORM 是面向 Golang 的一个全功能 ORM 库。下面演示了使用连接 PostgreSQL 并进行 CRUD 操作的流程:

package main

import (
    "fmt"
    "os"

    _ "github.com/joho/godotenv/autoload"
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

type User struct {
    ID       uint
    Email    string
    Password string
}

func main() {
    dbHost := os.Getenv("DB_HOST")
    dbPort := os.Getenv("DB_PORT")
    dbName := os.Getenv("DB_NAME")
    dbUser := os.Getenv("DB_USER")
    dbPassword := os.Getenv("DB_PASSWORD")

    // dsn means Data Source Name
    dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", dbHost, dbPort, dbUser, dbPassword, dbName)
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }

    db.AutoMigrate(&User{})

    // C
    db.Create(&User{Email: "[email protected]", Password: "password123"})

    // R
    var user User
    db.First(&user, 1)
    fmt.Printf("User with ID 1: %s %s\n", user.Email, user.Password)
    db.First(&user, "email = ?", "[email protected]")
    fmt.Printf("User with email [email protected]: %d, %s\n", user.ID, user.Password)

    // U
    db.Model(&user).Update("Password", "password456")
    db.First(&user, 1)
    fmt.Printf("User with ID 1: %s %s\n", user.Email, user.Password)

    // D
    db.Delete(&user, 1)
}

总结

本文详细介绍了如何在 Go 语言中连接和操作数据库,主要内容包括:

  1. 环境准备

    • 使用 Docker 快速搭建 MySQL 和 PostgreSQL 测试环境
    • 配置必要的数据库连接环境变量
  2. 数据库连接

    • 安装必要的数据库驱动
    • 建立数据库连接的基本步骤
    • MySQL 和 PostgreSQL 的连接字符串差异
  3. 基础数据库操作(CRUD)

    • 创建表结构
    • 插入数据(Create)
    • 查询数据(Read)
    • 更新数据(Update)
    • 删除数据(Delete)
  4. 实用技巧

    • 使用环境变量管理配置
    • 预处理语句提高性能
    • MySQL 和 PostgreSQL 的语法差异(如占位符的不同使用方式)

示例仓库:BlockLune/go-db-example

参考资料