上篇文章 中我們在使用的開發環境中增加了 MySQL 容器,然後介紹了使用 database/sql 標準庫結合數據庫驅動包進行數據庫操作的方法。不過它們是相對偏底層的軟件包。實際開發經常會使用一些在它的基礎上封裝的 ORM 庫。 ORM 的查詢使用起來更簡單些,語法更富表達力。這篇文章我們主要探究下面這些內容。

gorm
ORM

安裝gorm包

gorm 是一個出色的,對開發人員友好的 Golang ORM 庫,其支持的特性包括:

  • 全特性 ORM (幾乎包含所有特性)
  • 模型關聯 (一對一, 一對多,一對多(反向), 多對多, 多態關聯)
  • 鉤子 (Before/After Create/Save/Update/Delete/Find)
  • 預加載
  • 事務
  • 複合主鍵
  • SQL 構造器
  • 自動遷移
  • 日誌

使用如下命令進行安裝:

go get -u github.com/jinzhu/gorm

將gorm加入項目中

規劃數據模型目錄結構

我們在項目根目錄下新建如下目錄:

http_demo
|
└───model
│   └───dao
│   │   init.go
│   └───────table
│           │   user.go

在 Go 中包以目錄的形式來組織,所以 model 包中存放所有數據模型, dao 代表數據訪問對象,存放數據庫 CRUD 方法的封裝,其中的 init.go 存放 dao 包的初始化函數主要是用來在加載包後連接上數據庫。 table 包裏放與數據表對應的模型定義(使用 ORM 之前要先定義模型與數據庫中的表對應),在示例裏我們會定義一個 User 模型放在 user.go 文件中。

規劃完目錄後就可以在各部分寫相應的代碼了,首先來看使用 gorm 連接數據庫。

連接數據庫

我們在 dao 包的 init.go 中加入包的初始化邏輯進行數據庫連接,初始化函數會在 dao 包第一次被導入時執行,由於 gorm 文檔連接數據庫的例子太簡單,參考價值不大,我們根據項目需要做些簡單封裝, init.go 中的代碼如下所示:

package dao

import (
   _ "github.com/go-sql-driver/mysql"
   "github.com/jinzhu/gorm"
   "time"
)

var _DB *gorm.DB

func DB() *gorm.DB {
   return _DB
}

func init() {
   _DB = initDB()
}

func initDB() *gorm.DB {
   // In our docker dev environment use
   // db, err := gorm.Open("mysql", "go_web:go_web@tcp(database:3306)/go_web?charset=utf8&parseTime=True&loc=Local")
   db, err := gorm.Open("mysql", "go_web:go_web@tcp(localhost:33063)/go_web?charset=utf8&parseTime=True&loc=Local")
   if err != nil {
      panic(err)
   }
   db.DB().SetMaxOpenConns(100)
   db.DB().SetMaxIdleConns(10)
   db.DB().SetConnMaxLifetime(time.Second * 300)
   if err = db.DB().Ping(); err != nil {
      panic(err)
   }
   return db
}

代碼很簡單,大家實操的時候根據自己的 MySQL 配置更改代碼裏面的配置就行了。唯一說明一點的是,如果使用了我們提提供的 Docker 環境,在連接數據庫時 host 要改爲 database:3306 ,因爲我在容器環境裏將 MySQL 容器的服務名定義成了 database ,在運行了 Goapp 容器需要用服務名訪問容器網絡中的其他容器。關於容器環境的詳細配置請大家查看 Go Web編程--應用數據庫 中的描述。

定義模型

使用模型訪問數據庫的表之前我們需要先定義對應的模型。我們示例中現在只有一個 users 表,接下來我們在 table 包中添加 users 表的模型定義並放置在 user.go 文件中。

package table

import "time"

type User struct {
   Id        int64     `gorm:"column:id;primary_key"`
   UserName  string    `gorm:"column:username"`
   Secret    string    `gorm:"column:secret;type:varchar(1000)"`
   CreatedAt time.Time `gorm:"column:created_at"`
   UpdatedAt time.Time `gorm:"column:updated_at"`
}

// TableName sets the insert table name for this struct type
func (m *User) TableName() string {
   return "users"
}

模型 CRUD

關於模型的 CRUD,建議將單個模型的 CRUD 放在 dao 包的單個文件中,這樣方便以後代碼的管理。這裏多說一點,建議不要直接用 controller 或者叫 handler 包直接訪問 dao 包,而是在中間加一層 logic 包,把邏輯放在這一層。這樣對代碼的管理、複用性都有幫助。

因爲數據庫的 CRUD 有很多種操作,本文的目的是幫助大家快速開始使用 gorm 所以我就只放簡單的 CRUD 做演示了。大家按照這裏步驟引入 gorm 後用到其他的數據庫操作了直接去官方文檔裏查一查就好。

dao 包中新建 user.go 用來存放 User 模型的操作方法。

package dao

import "example.com/http_demo/model/dao/table"

func CreateUser(user *table.User) (err error) {
    err = DB().Create(user).Error

    return
}

func GetUserById(userId int64) (user *table.User, err error) {
    user = new(table.User)
    err = DB().Where("id = ?", userId).First(user).Error

    return
}

func GetAllUser() (users []*table.User, err error) {
    err = DB().Find(&users).Error
    return
}

func UpdateUserNameById(userName string, userId int64) (err error) {
    user := new(table.User)
    err = DB().Where("id = ?", userId).First(user).Error
    if err != nil {
        return
    }

    user.UserName = userName
    err = DB().Save(user).Error

    return
}

func DeleteUserById(userId int64) (err error) {
    user := new(table.User)
    err = DB().Where("id = ?", userId).First(user).Error
    if err != nil {
        return
    }
    err = DB().Delete(user).Error

    return
}

驗證ORM 方法

經過上面幾步的設置後我們就可以在項目裏使用 gorm 訪問數據庫了,由於我們項目的 main goroutine 中運行了 http 服務,所以我們使用測試用例對上面 dao 包中定義的幾個方法進行一下測試。

篇幅原因我就只貼一個 GetAllUsers 方法的測試用例了:

func TestGetAllUser(t *testing.T) {
    tests := []struct {
        name      string
        wantErr   bool
    }{
        {
            name: "test",
            wantErr: false,
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            gotUsers, err := GetAllUser()
            if (err != nil) != tt.wantErr {
                t.Errorf("GetAllUser() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            for _, user := range gotUsers {
                log.Info("user: %v", user)
            }

        })
    }
}

運行測試後,可以看到結果:

INFO user: &{1   2020-02-15 14:14:46 +0800 CST 2020-02-15 06:44:17 +0800 CST}
--- PASS: TestGetAllUsers (0.00s)
    --- PASS: TestGetAllUsers/test (0.00s)
PASS

Process finished with exit code 0

關注文末的公衆號回覆 gohttp05 可以得到完整的測試用例代碼,建議這些 CRUD 都要寫好測試用例進行自測,使用 GoLand 可以很容易的生成測試函數和運行測試。

重新規劃項目目錄

引入 ORM 後,我們項目中的代碼就比較多了,都放在根目錄下的 main 包中有點雜亂,所以我們根據各部分的功能和職責對項目目錄進行了簡單的劃分,劃分後的目錄結構如下:

http_demo
|
└───handler//route handler
|
└───logic//business logic
|
└───middleware
│
└───model
│   └───dao
│   │   init.go
│   └───table
│       │   user.go
└───router// router
|
|   main.go

感覺今天的內容還是挺多的,尤其對於剛入門 Go 的同學們一定要把今天的代碼下載下來實操一遍才能掌握。 gorm 提供的功能還是很多的,每個功能在官方文檔裏都有講解,我們這裏就不做過多介紹了。這篇文章的目的主要是讓大家能快速入門,同時把 ORM 相關的代碼管理和初始化流程做的規範些,這些組織方式完全可以應用到生產級別的項目中的。

關注文末的公衆號回覆 gohttp05 獲取文章中完整的源代碼,喜歡我的文章請點贊和收藏。

前文回顧

深入學習用Go編寫HTTP服務器

Web服務器路由

用Docker快速搭建Go開發環境

十分鐘學會用Go編寫Web中間件

Go Web編程--應用數據庫

相關文章