前面学院君给大家介绍了 Go 语言中的内存存储和文件存储,文件存储的好处是可以持久化数据,但是并不是 Web 应用数据存储的终极方案,因为这样存储起来的数据检索和管理起来非常麻烦,为此又诞生了数据库管理系统来处理数据的增删改查。数据库又可以划分为关系型数据库(RDBMS)和非关系型数据库(NoSQL),前者比如 MySQL、Oracle,后者比如 Redis、MongoDB,这里我们以当前最流行的开源关系型数据库 MySQL 为例进行介绍。

1、初始化数据库

开始之前,我们先要连接到 MySQL 服务器初始化数据库和数据表。

注:如果你还没有在本地安装 MySQL 数据库,需要先进行安装,使用 Docker 启动或者去 MySQL 官网下载安装包安装均可,Mac 系统中还可以使用 Homebrew 进行安装,然后选择一个自己喜欢的 GUI 客户端,学院君本地使用的是 TablePlus。

test_db
posts
CREATE TABLE `posts` (
  `id` bigint unsigned AUTO_INCREMENT,
  `title` varchar(100) DEFAULT NULL,
  `content` text,
  `author` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

2、建立数据库连接

posts
database/sql
database/sqlgo-sql-driver/mysql
db.goinitmain
import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

var Db *sql.DB

func init()  {
    var err error
    Db, err = sql.Open("mysql", "root:root@/test_db?charset=utf8mb4&parseTime=true")
    if err != nil {
        panic(err)
    }
}
sql.DBsql.Openmysql
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
address
sql.DB
Opensql.DB
sql.DBsql.DBDbsql.DB
Opensql.Registergo-sql-driver/mysqlmysqlinitdriver.go
func init() {
    sql.Register("mysql", &MySQLDriver{})
}
sqldriver.Driver
mysql
_ "github.com/go-sql-driver/mysql"
mysqlinit
sql.Driver_database/sql

注:如果你对这一块接口与实现的细节不清楚,可以回顾 Go 入门教程中的面向对象编程部分。

3、增删改查示例代码

数据库初始化完成并设置好连接配置之后,就可以在 Go 应用中与数据库进行交互了。我们将编写一段对文章表进行增删改查的示例代码来演示 Go 语言中的数据库操作。

db.go

定义 Post 结构体

首先我们需要定义一个表示文章表数据结构的结构体:

type Post struct {
    Id int
    Title string
    Content string
    Author string
}

创建新文章

CreateDb
func (post *Post) Create() (err error) {
    sql := "insert into posts (title, content, author) values (?, ?, ?)"
    stmt, err := Db.Prepare(sql)
    if err != nil {
        panic(err)
    }
    defer stmt.Close()

    res, err := stmt.Exec(post.Title, post.Content, post.Author)
    if err != nil {
        panic(err)
    }

    postId, _ := res.LastInsertId()
    post.Id = int(postId)
    return
}

注:这里我们使用了预处理语句,以避免 SQL 注入攻击,如果你有 PHP 或者其他语言数据库编程基础的话,应该很容易看懂这些代码。

stmt.QueryRow(post.Title, post.Content, post.Author)Db.Exec
res, err := Db.Exec(sql, post.Title, post.Content, post.Author)

获取单篇文章

Db.QueryRowPost
func GetPost(id int) (post Post, err error) {
    post = Post{}
    err = Db.QueryRow("select id, title, content, author from posts where id = ?", id).
        Scan(&post.Id, &post.Title, &post.Content, &post.Author)
    return
}

获取文章列表

sql.DBQuery
func Posts(limit int) (posts []Post, err error) {
    rows, err := Db.Query("select id, title, content, author from posts limit ?", limit)
    if err != nil {
        panic(err)
    }
    defer rows.Close()
    for rows.Next() {
        post := Post{}
        err = rows.Scan(&post.Id, &post.Title, &post.Content, &post.Author)
        if err != nil {
            panic(err)
        }
        posts = append(posts, post)
    }
    return
}
sql.RowsNextsql.Rowsql.Rowsio.EOF
sql.RowPostPostposts

其实对于单条记录,也可以使用类似的方式实现,毕竟单条记录查询是 SELECT 查询的特例:

func GetPost(id int) (post Post, err error) {
    rows, err := Db.Query("select id, title, content, author from posts where id = ? limit 1", id)
    if err != nil {
        panic(err)
    }
    defer rows.Close()
    for rows.Next() {
        post = Post{}
        err = rows.Scan(&post.Id, &post.Title, &post.Content, &post.Author)
        if err != nil {
            panic(err)
        }
    }
    return
}
Db.Query
stmt, err := Db.Prepare("select id, title, content, author from posts limit ?")
if err != nil {
    panic(err)
}
defer stmt.Close()
rows, err := stmt.Query(limit)
if err != nil {
    panic(err)
}
... // 后续其他操作代码

更新文章

对于已存在的文章记录,可以通过执行 SQL 更新语句进行修改:

func (post *Post) Update() (err error)  {
    stmt, err := Db.Prepare("update posts set title = ?, content = ?, author = ? where id = ?")
    if err != nil {
        return
    }
    stmt.Exec(post.Title, post.Content, post.Author, post.Id)
    return
}
stmt.QueryRowDb.ExecDb.Exec
func (post *Post) Update() (err error)  {
    _, err = Db.Exec("update posts set title = ?, content = ?, author = ? where id = ?",
        post.Title, post.Content, post.Author, post.Id)
    return
}
Db.Execsql.Result

-w632

Result_

删除文章

删除操作和更新操作类似,只是将 UPDATE 语句调整为 DELETE 语句而已:

func (post *Post) Delete() (err error) {
    stmt, err := Db.Prepare("delete from posts where id = ?")
    if err != nil {
        return
    }
    stmt.Exec(post.Id)
    return
}
stmt.QueryRowDb.Exec
func (post *Post) Delete() (err error) {
    _, err = Db.Exec("delete from posts where id = ?", post.Id)
    return
}

4、整体测试

db.gomain
func main()  {
    post := Post{Title: "Go 语言数据库操作", Content: "基于第三方 go-sql-driver/mysql 包实现 MySQL 数据库增删改查", Author: "学院君"}

    // 创建记录
    post.Create()
    fmt.Println(post)

    // 获取单条记录
    dbPost, _ := GetPost(post.Id)
    fmt.Println(dbPost)

    // 更新记录
    dbPost.Title = "Golang 数据库操作"
    dbPost.Update()

    // 获取文章列表
    posts, _ := Posts(1)
    fmt.Println(posts)

    // 删除记录
    dbPost.Delete()
}
go-sql-driver/mysql
db.go

好了,关于数据库增删改查基本操作就简单介绍到这里,下篇教程,我们来看看如何在 MySQL 数据库中实现不同表之间的关联查询和更新。

(全文完)