gorm的使用
gorm的安裝
打開go運行的文件夾下的終端,創建main.go文件 執行下面的指令初始化mod
go mod init main.go
安裝gorm
官方給的示例是
go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
但是我們這里要使用mysql數據庫 所以就對第二個依賴進行一部分修改
got get -u gorm.io/driver/mysql
gorm的使用 增刪改查
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Product struct {
gorm.Model
Code string
Price uint
}
func main() {
dsn := "root:1296729980@tcp(127.0.0.1:3306)/go_db"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println(err)
}
// //創建表
db.AutoMigrate(&Product{})
p := Product{
Price: 200,
Code: "2",
}
//插入數據
db.Create(&p)
//查看數據
var product Product
// db.First(&product, 2) // 按照整形鏈查找
db.First(&product, 1)
fmt.Printf("product: %v\n", product)
//改
//這里有坑,修改單個字段使用的是update 修改多個使用到的是updates
db.Model(&product).Update("Price", 300)
//更新多個字段
db.Model(&product).Updates(Product{Price:300,Code:"3"})
db.Model(&product).Updates(map[string]interface{}{"Price": 300, "Code": "3"})
//刪
db.Delete(&product, 1)
}
gorm 模型 model
gorm的模型更多的是一種約定,而不是配置
gorm.Model是grom的一種結構體代表的是
type Model struct{
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
當我們生命gorm結構體時,如果不想直接寫gorm.Model的話,將上面的這些字段加入到我們聲明的結構體當中效果也是一樣的。
字段權限控制
當我們對字段CRUD時,默認字段是具有全部的權限的
gorm允許我們使用使用標簽來控制字段級的權限
type User struct {
Name string `gorm:"<-:create"` // allow read and create
Name string `gorm:"<-:update"` // allow read and update
Name string `gorm:"<-"` // allow read and write (create and update)
Name string `gorm:"<-:false"` // allow read, disable write permission
Name string `gorm:"->"` // readonly (disable write permission unless it configured)
Name string `gorm:"->;<-:create"` // allow read and create
Name string `gorm:"->:false;<-:create"` // createonly (disabled read from db)
Name string `gorm:"-"` // ignore this field when write and read with struct
Name string `gorm:"-:all"` // ignore this field when write, read and migrate with struct
Name string `gorm:"-:migration"` // ignore this field when migrate with struct
}
當使用gorm Migrator創建表時,不會創建被忽略的字段名
創建、修改、刪除時間
gorm約定默認使用
CreatedAt
UpdatedAt
DeletedAt
這三個字段名來分別記載創建時間,修改時間,刪除時間
如果不想使用這三個字段可以使用標簽進行更改
type User struct {
CreatedAt time.Time // 在創建時,如果該字段值為零值,則使用當前時間填充
UpdatedAt int // 在創建時該字段值為零值或者在更新時,使用當前時間戳秒數填充
Updated int64 `gorm:"autoUpdateTime:nano"` // 使用時間戳填納秒數充更新時間
Updated int64 `gorm:"autoUpdateTime:milli"` // 使用時間戳毫秒數填充更新時間
Created int64 `gorm:"autoCreateTime"` // 使用時間戳秒數填充創建時間
}
gorm 連接數據庫
gorm支持連接的數據庫有以下幾種MySQL, PostgreSQL, SQlite, SQL Server
我們主要介紹mysql
如果想要了解更多可以訪問官方文檔地址
官方鏈接mysql的約定格式為
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
// 參考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 獲取詳情
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}
這只是個實例 我們在使用時dsn會這樣寫
dsn := "root:1296729980@tcp(127.0.0.1:3306)/go_db"
root為名稱,root后跟的是數據庫密碼,go_db是我們在root下創建的數據庫
想要正確的處理 time.Time ,您需要帶上 parseTime 參數, (更多參數) 要支持完整的 UTF-8 編碼,您需要將 charset=utf8 更改為 charset=utf8mb4 查看 此文章 獲取詳情
如果想要使用更加精細的配置,官方也給了配置選項,以下表明的是默認配置選項
db, err := gorm.Open(mysql.New(mysql.Config{
DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
DefaultStringSize: 256, // string 類型字段的默認長度
DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的數據庫不支持
DontSupportRenameIndex: true, // 重命名索引時采用刪除并新建的方式,MySQL 5.7 之前的數據庫和 MariaDB 不支持重命名索引
DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的數據庫和 MariaDB 不支持重命名列
SkipInitializeWithVersion: false, // 根據當前 MySQL 版本自動配置
}), &gorm.Config{})
示例:
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "root:1296729980@tcp(localhost:3306)/go_db"
d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println(err)
}
fmt.Println(d)
}
&{0x140001da630 <nil> 0 0x140002aa000 1}
我們會得到一個地址,代表鏈接成功
創建記錄
創建一個記錄
package main
import (
"fmt"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var db *gorm.DB
type User struct {
gorm.Model
Name string
Age uint
Birthday time.Time
}
func init() {
dsn := "root:1296729980@tcp(localhost:3306)/go_db"
d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println(err)
}
db = d
}
func InsertValue() {
user := User{Name: "小明", Age: 11, Birthday: time.Now()}
result := db.Create(&user)
fmt.Println("userID", user.ID) // 顯示添加ID
fmt.Println("Error", result.Error) //錯誤信息
fmt.Println("Rows", result.RowsAffected) //改變行數
}
func CreateTable() {
db.AutoMigrate(&User{})
}
func main() {
CreateTable()
InsertValue()
}
??注意,在命名結構體時,我們所要用到的所有字段必須首字母大寫,這是一種約定,否則gorm將無法將字段名添加至數據庫中
按照字段傳值
func InsertValue() {
user := User{Name: "小紅", Age: 12, Birthday: time.Now()}
db.Select("Name","Age").Create(&user)
db.Omit("Name", "Age").Create(&user)
}
db.Select(column1,column).Create()指只添加我們查詢到的字段名,其他的數據忽略
db.Omit(column1,column2).Create()指忽略我們傳的字段名,其他字段名全部添加
| 4 | 2022-06-15 06:20:56.537 | 2022-06-15 06:20:56.537 | NULL | 小紅 | 12 | NULL |
| 5 | 2022-06-15 06:22:15.330 | 2022-06-15 06:22:15.330 | NULL | NULL | NULL | 2022-06-15 06:22:15.329 |
批量插入
批量插入就是傳一個slice給Create(),grom會單獨創建出一條sql語句,鉤子在這時依然可以進行使用
user := []User{{Name: "小剛", Age: 11}, {Name: "小強", Age: 10}}
鉤子
Hook 是在創建、查詢、更新、刪除等操作之前、之后調用的函數。
hook執行順序
// 開始事務
BeforeSave
BeforeCreate
// 關聯前的 save
// 插入記錄至 db
// 關聯后的 save
AfterCreate
AfterSave
// 提交或回滾事務
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
u.UUID = uuid.New()
if !u.IsValid() {
err = errors.New("can't save invalid data")
}
return
}
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
if u.ID == 1 {
tx.Model(u).Update("role", "admin")
}
return
}
所有對數據庫的操作和伴隨的hook是一個事務,如果有一項報錯,那么之前所有的操作都將被回滾
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
if u.ID == 1 {
tx.Model(u).Update("role", "admin")
}
fmt.Println("AfterCreate")
return
}
我們會發現在上傳多個數據的時候,會執行多次hook
查詢
檢索單個對象
// 獲取第一條記錄(主鍵升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
// 獲取一條記錄,沒有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
// 獲取最后一條記錄(主鍵降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
result := db.First(&user)
result.RowsAffected // 返回找到的記錄數
result.Error // returns error or nil
// 檢查 ErrRecordNotFound 錯誤
errors.Is(result.Error, gorm.ErrRecordNotFound)
檢索多條記錄
檢索多條記錄時一定注意,傳入的是一個切片的形式
var user []User
db.Find(&user)
for _, u := range user {
fmt.Printf("u: %v\n", u.ID)
}
使用主鍵進行檢索
db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);
根據結構體查字段
var user []User
db.Where(&User{Name: "小剛"}).Find(&user)
for _, u := range user {
fmt.Printf("u: %v\n", u.ID)
}
根據結構體查詢字段
var user User
db.Select("name").First(&user)
fmt.Printf("user: %v\n", user.Name)
通過這種方式我們可以檢索到name
智能選擇字段
新建一個結構體
type UserSelect struct {
Name string
}
使用
db.Model(&User{}).Limit(10).Find(&user)
// SELECT `id`, `name` FROM `users` LIMIT 10
fmt.Println(user)
我們可以得到表中每一行的name
同時gorm還支持子查詢,form子查詢以及group子查詢
更新
保存所有字段
user.Name = "小小"
user.Age = 10
user.Birthday = time.Now()
db.Save(&user)
db.Save()會保存所有的字段,即使是0值也會被保存
執行上面的語句我們在查詢表的時候,可以發現表中多了一條名為小小的數據
更改一列
db.Select("name").Model(&user).Where("id=?", 15).Update("name", "小一")
fmt.Printf("user.Name: %v\n", user.Name)
上述語句相當于sql語句
SELECT name from UPDATE users SET name='小一', updated_at='2022-06-15 09:45:39.965' WHERE id=15 AND active=true;
更改多列
使用struct更新屬性,只能更新非零值
db.Select("name").Model(&user).Where("age=?", 10).Updates(&User{Name: "小三"})
使用map[string]更新屬性
db.Select("name").Model(&user).Where("age=?", 10).Updates(map[string]interface{}{"name":"小三"})
更新多列時,使用到的事updates而不再是update,并且需要傳一個結構體或者是map類型的數據
更新選定的字段
我們先定義一個結構體
var user User
user.Name = "new_name"
user.Age = 11
user.Birthday = time.Now()
有時我們不想把所有的字段全部都進行更改,我們可以使用到select或者是omit
db.Model(&user).Select("name").Where("age=?", 10).Updates(&user)
db.Model(&user).Omit("name").Where("age=?", 10).Updates(&user)
select是對所有age為10的數據的name全部進行更改,而omit則是除了name以外的其他字段進行更改
阻止全局更新
db.Model(&user).Select("name").Updates(&user)
像是這種不加任何判斷直接進行全局更新的情況,gorm一般是不會允許的,并拋出這樣的錯誤
WHERE conditions required
對此我們必須加一些條件或者使用sql原生語句
db.Exec("UPDATE users SET name = ?", "jinzhu")
// UPDATE users SET name = "jinzhu"
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu"
刪除
db.Where("name = ?", "小紅").Delete(&user)
// DELETE from emails where id = 10 AND name = "jinzhu";
直接刪除所有名字為小紅的數據
根據主鍵刪除
刪除主鍵為10的數據
db.Delete(&user,10)
刪除主鍵為1,2,
db.Delete(&users, []int{1,2,3})
阻止全局刪除
db.Exec("DELETE FROM users")
// DELETE FROM users
db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
// DELETE FROM users
軟刪除
我們引入了gorm.Model后,因為gorm.deletedat字段的存在,其實數據并沒有被真正的刪除,而是在deleted_at中加入了一個時間戳
查找被軟刪除的記錄
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
永久刪除
直接將數據從數據表中刪除
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;