Gorm 操作 MySQL
安裝
go get -u gorm.io/gorm
連接 MySQL
創建 model.go
文件,代碼如下:
package model
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"time"
)
var db *gorm.DB
const dsn = "root:123456@tcp(127.0.0.1:3306)/db_gorm_example?charset=utf8mb4&parseTime=True&loc=Local"
func Setup() {
var err error
db, err = gorm.Open(
mysql.New(mysql.Config{
DSN: dsn, // 連接地址
DefaultStringSize: 191, // string 類型字段的默認長度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的數據庫不支持
DontSupportRenameIndex: true, // 重命名索引時採用刪除並新建的方式,MySQL 5.7 之前的數據庫和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的數據庫和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根據當前 MySQL 版本自動配置
}),
&gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 日誌輸出級別
NamingStrategy: schema.NamingStrategy{
TablePrefix: "sys_", // 表名前綴,`User` 的表名應該是 `sys_users`
SingularTable: true, // 使用單數表名,啓用該選項,此時,`User` 的表名應該是 `sys_user`
},
DisableForeignKeyConstraintWhenMigrating: false, //在 AutoMigrate 或 CreateTable 時,GORM 會自動創建外鍵約束
})
if err != nil {
panic(err)
}
setConnectionPool()
}
// 設置連接池
func setConnectionPool() {
if db != nil {
sqlDB, err := db.DB()
if err != nil {
panic(err)
}
sqlDB.SetMaxIdleConns(10) // 空閒連接池中連接的最大數量
sqlDB.SetMaxOpenConns(10) // 打開數據庫連接的最大數量
sqlDB.SetConnMaxLifetime(60 * time.Minute) // 連接可複用的最大時間
err = sqlDB.Ping()
if err != nil {
panic(err)
}
}
}
sqlDB
對象是包含多個 idle(空閒)
和 open(打開)
數據庫連接的連接池。idle
通過 SetMaxIdleConns
設置空閒連接池中連接的最大數量;open
通過 SetMaxOpenConns
設置打開數據庫連接的最大數量。當使用連接來執行數據庫任務時,該連接會被標記爲 open
。任務完成後,連接將變爲 idle
。此外,還可以通過 SetConnMaxLifetime
設置連接池中連接可複用的最大時間,到期後會斷開連接,下次使用需要重新連接。
爲什麼要使用連接池?
“
連接複用,提升性能:數據庫連接本質就是一個 socket 連接。頻繁建立、斷開連接會降低系統的性能。所以可以把數據庫連接緩存起來複用,在規定可複用時間內(ConnMaxLifetime)可以直接重用這些連接。這就是連接池。
減少等待時間:在連接池中,創建連接後,將其放置在池中,並再次使用它,因此不必建立新的連接。如果使用了所有連接,則會建立一個新連接(不可以超過 MaxOpenConns )並將其添加到池中。減少了用戶必須等待建立與數據庫的連接的時間。
”
定義模型
首先定義一個班級模型,class.go
:
package model
import "gorm.io/gorm"
// Class 班級
type Class struct {
gorm.Model
// 班級名稱,類型爲varchar(20),不能爲空,唯一約束
ClassName string `json:"class_name" gorm:"size:20;not null;unique;comment:班級名稱"`
}
// 插入一行記錄
func (c *Class) Create() error {
return db.Create(c).Error
}
學生模型,student.go
:
package model
import "gorm.io/gorm"
// Student 學生
type Student struct {
gorm.Model
// 學生姓名,類型爲varchar(20),不能爲空
Name string `json:"name" gorm:"type:varchar(20);not null;comment:學生姓名"`
ClassID uint `json:"-" gorm:"not null;comment:學生所屬班級ID"`
Class Class `json:"class"`
}
// 插入一行記錄
func (s *Student) Create() error {
return db.Create(s).Error
}
// 批量插入
func BatchCreateStudent(students []Student) error {
return db.Create(&students).Error
}
// 查詢所有學生信息
func QueryAllStudentInfo() ([]Student, error) {
var students []Student
err := db.Model(&Student{}).
Preload("Class"). // 預加載班級信息
Find(&students).Error
if err != nil {
return nil, err
}
return students, nil
}
// 查詢指定學生信息
func QueryStudentInfoByID(studentID uint) (*Student, error) {
var student Student
err := db.Model(&Student{}).Where("id = ?", studentID).
Preload("Class"). // 預加載班級信息
Take(&student).Error
if err != nil {
return nil, err
}
return &student, nil
}
// 軟刪除
func DelStudent(studentID uint) error {
return db.Delete(&Student{}).Where("id = ?", studentID).Error
}
// 永久刪除
func UnscopedDelStudent(studentID uint) error {
return db.Unscoped().Delete(&Student{}).Where("id = ?", studentID).Error
}
班級和學生是一對多的關係,如果想要 gorm
自動爲我們創建外鍵約束,可以使用 AutoMigrate
自動遷移功能。在model.go
中補充:
func Setup() {
...
autoMigrate(&Class{}, &Student{})
}
// 自動遷移
func autoMigrate(tables ...interface{}) {
// 創建表時添加後綴
db.Set("gorm:table_options",
"ENGINE=InnoDB DEFAULT CHARSET=utf8mb4").
AutoMigrate(tables...)
// AutoMigrate 會創建表、缺失的外鍵、約束、列和索引。
// 如果大小、精度、是否爲空可以更改,則 AutoMigrate 會改變列的類型。
}
運行測試
最後在 main.go
中測試上面的代碼:
package main
import (
"gorm-example/model"
)
func main() {
// 初始化連接
model.Setup()
// 創建一行學生記錄(gorm會自動幫我們創建對應的班級記錄)
student := model.Student{
Name: "小米",
Class: model.Class{
ClassName: "一年級",
},
}
student.Create()
select {}
}
輸出日誌:id
、created_at
、updated_at
、deleted_at
四個字段,這其實是在定義模型的時候,我們在結構體中嵌入的 gorm.Model
起的作用,可以看看 gorm.Model
結構體的源碼:DeletedAt
的存在,我們的數據庫表纔可以實現軟刪除。
測試使用 gorm 來查詢數據,更改我們的 main.go
:
package main
import (
"fmt"
"gorm-example/model"
)
func main() {
// 初始化連接
model.Setup()
// 查詢所有學生數據
students, err := model.QueryAllStudentInfo()
if err != nil {
panic(err)
}
for i, student := range students {
fmt.Printf(
"---------第%d條記錄---------\n"+
"學生ID:%d\n"+
"學生姓名:%s\n"+
"學生班級:%s\n"+
"---------------------------\n",
i+1,
student.ID,
student.Name,
student.Class.ClassName)
}
select {}
}
輸出結果:
Gorm 中使用事務
默認 gorm 是會在事務裏執行寫入操作的(創建、更新、刪除)。當然,如果不需要,我們也可以禁用它,官方說是會獲得大約 30%+ 性能提升。禁用默認事務只要在 model.go
中的 gorm.Config
加入SkipDefaultTransaction: true
即可。
func Setup() {
var err error
db, err = gorm.Open(
mysql.New(mysql.Config{
DSN: dsn, // 連接地址
DefaultStringSize: 191, // string 類型字段的默認長度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的數據庫不支持
DontSupportRenameIndex: true, // 重命名索引時採用刪除並新建的方式,MySQL 5.7 之前的數據庫和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的數據庫和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根據當前 MySQL 版本自動配置
}),
&gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 日誌輸出級別
NamingStrategy: schema.NamingStrategy{
TablePrefix: "sys_", // 表名前綴,`User` 的表名應該是 `sys_users`
SingularTable: true, // 使用單數表名,啓用該選項,此時,`User` 的表名應該是 `sys_user`
},
DisableForeignKeyConstraintWhenMigrating: false, //在 AutoMigrate 或 CreateTable 時,GORM 會自動創建外鍵約束
SkipDefaultTransaction: true, //禁用默認事務
})
if err != nil {
panic(err)
}
setConnectionPool()
autoMigrate(&Class{}, &Student{})
}
來一個需要使用事務的需求吧,定義一個模型 account.go
:
package model
import (
"errors"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// Account 賬戶
type Account struct {
gorm.Model
Amount uint `json:"amount" gorm:"not null;default:0;comment:金額"`
}
// 轉賬屬於一系列操作,只能全部成功,必須在事務中執行
// 這裏只做示例,忽略餘額不夠的情況
func Transfer(remitter, payee uint, money int) error {
// 使用事務
return db.Transaction(func(tx *gorm.DB) error {
// 加排他鎖
var accounts []Account
err := tx.
Clauses(clause.Locking{Strength: "UPDATE"}).
Model(&Account{}).
Where("id in (?)", []uint{remitter, payee}).
Find(&accounts).Error
if err != nil {
return err
}
if len(accounts) != 2 {
return errors.New("賬號異常")
}
remitterOK := false
payeeOK := false
for _, account := range accounts {
switch account.ID {
case remitter:
// 匯款人
err = tx.Model(&Account{}).
Where("id = ?", remitter).
Update("amount", gorm.Expr("amount - ?", money)).Error
if err != nil {
return err
}
remitterOK = true
case payee:
// 收款人
err = tx.Model(&Account{}).
Where("id = ?", payee).
Update("amount", gorm.Expr("amount + ?", money)).Error
if err != nil {
return err
}
payeeOK = true
}
}
if remitterOK && payeeOK {
// 當返回 nil 的時候纔會提交事務
return nil
} else {
return errors.New("轉賬失敗")
}
})
}
同樣,讓自動遷移爲我們自動建表,在 model.go
的 Setup()
函數中的 autoMigrate
補充 &Account{}
:
autoMigrate(&Class{}, &Student{}, &Account{})
手動插入一些數據如下:main.go
中進行轉賬操作,更改如下:
package main
import (
"gorm-example/model"
)
func main() {
// 初始化連接
model.Setup()
// (1)轉賬給(2) 100元
err := model.Transfer(1, 2, 100)
if err != nil {
panic(err)
}
select {}
}
查看變更:
最後
關於 gorm 操作 MySQL 的代碼可以從
https://github.com/togettoyou/go-one-server
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/3V5lgqJJdMaxeN5zfmVJGQ