这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
重点内容
- 三件套介绍
- 三件套使用
- 实战案例
详细知识点
Gorm
文档: gorm.cn
Gorm是迭代10年+的ORM框架
基本使用
定义gorm model
type Product struct {
Code string
Price uint
}
//为model定义表名
func (p Product) TableName() string {
return "product"
}
func main(){
db, err := gorm.Open (
mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),&gorm.Config{})
if err != nil {
panic("failed to connect database")
}
//Create
db.Create(&Product{Code: "D42", Price: 100})
//Read
var product Product
db.First(&product,1) //根据整形主键查找
db.First(&product,"code = ?", "D42")
//Update
db.Model(&product).Update("Price",200)//更新单列数据
//Update多个字段
db.Model(&product).Updates(Product{Price:200, Code: "F42"})//仅更新非零值字段
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"}) //更新零值
//Delete
db.Delete(&product, 1)
}
这里对Model的用处用法有些不太清楚?
几个约定:
- 默认使用名为ID的字段为主键
- 使用结构体的 蛇形负数作为表名
- 字段名的蛇形作为列名
- 使用CreateAt、UpdateAt字段作为创建、更新时间
支持的数据库
MySQL、SQLServer、PostgreSQL、SQLite
import (
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
)
dsn := "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{})
GORM创建数据
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type Product struct {
ID uint `gorm:"primarykey"` //定义字段为主键
Code string `gorm:"column: code"` //定义列名
Price uint `gorm:"column: user_id"`
}
func main() {
db, err := gorm.Open(mysql.Open("..."), &gorm.Config{})
if err != nil // 处理错误
//创建一条
p := &Product{Code: "D42"}
res := db.Create(p)
fmt.Println(res.Error)
fmt.Println(p.ID)
//创建多条
products := []*Product{{Code: "D41"},{Code: "D42"},{Code: "D43"}}
res = db.Create(products)
fmt.Println(res.Error)
for _, p := range products {
fmt.Println(p.ID)
}
}
创建的时候有些字段并没有赋值,可以通过在结构体中字段后面加标签设置默认值,比如 'gorm:"default:galeone',类型要匹配上
查询数据
u := &User{}
db.First{u}
First查询,获取第一条记录(主键升序),如果查询不到数据则返回ErrRecordNotFound,不常用,一般用Find
users := make([]*User, 0)
result := db.Where("age > 10").Find(&users)
fmt.Println(result.RowsAffected) //返回找到的记录数
fmt.Println(result.Error)
//IN
db.Where("name IN ?", []string{"jinzhu","jinzhu 2"}).Find(&users)
//LIKE
db.Where("name LIKE ?", "%jin%"}).Find(&users)
//AND
db.Where("name = ? AND age > ?", "jinzhu", "22").Find(&users)
Find查询,查询不到则返回空数组,不会出问题
使用结构体作为查询条件时,GORM只查询非零值字段,如果字段值为0值,则该字段不会被构建为查询条件,解决方案是使用Map构建查询条件
更新数据
//更新单列
db.Model(&User{ID: 111}).Where("age > ?", 18).Update("name", "hello")
//更新多个列
db.Model(&User{ID: 111}).Update(User{Name: "hello", Age: 18})
//使用map
db.Model(&User{ID: 111}).Update(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
//更新选定字段
db.Model(&User{ID: 111}).Select("name").Update(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
//SQL表达式更新
db.Model(&User{ID: 111}).Update("age", gorm.EXpr("age * ? + ?", 2, 100))
删除数据
物理删除
db.Delete(&User{}, 10)
db.Delete(&User{}, "10")
db.Delete(&User{}, []int{1,2,3})
db.Where("name LIKE ?", "%jinzhu%").Delete(User{})
db.Delete(User{}, "email LIKE ?", "%jinzhu%")
物理删除,一旦删除就找不回来了
软删除
GORM提供gorm.DeleteAt用于实现软删除
拥有软删除能力的Model调用Delete时,记录不会被真正删除。GORM会将DeteleAt置为当前时间,不能通过正常的查询方法找到该记录
使用Unscoped可以查询到被软删的数据
GORM事务
Gorm提供了Begin、Commit、Rollback方法用于事务
db, err := gorm.Open(mysql.Open("..."), &gorm.Config{})
if err != nil {
panic("错误信息")
}
tx := db.Begin() // 开始事务
//执行db操作,这里开始应该用tx,而不是db
if err = tx.Create(&User{Name: "name"}).Error; err != nil {
tx.Rollback()
//遇到错误时回滚事务
return
}
if err = tx.Create(&User{Name: "name1"}).Error; err != nil {
tx.Rollback()
//遇到错误时回滚事务
return
}
//提交
tx.Commit()
问题: 多个表时容易忘记rollback() 所以Gorm提供了Tansaction方法用于自动提交事务,避免漏写commit和rollback
db, err := gorm.Open(mysql.Open("..."), &gorm.Config{})
if err != nil {
panic("错误信息")
}
if err = db.Transaction(func(tx *gorm.DB) error {
if err = tx.Create(&User{Name: "name"}).Error; err != nil {
//tx.Rollback()
return err
}
if err = tx.Create(&User{Name: "name1"}).Error; err != nil {
tx.Rollback()
return err
}
return nil
}); err != nil {
return
}
Hook
Hook是在创建、查询、更新、删除等操作之前、之后自动调用的函数。如果任何Hook返回错误,GORM将停止后续操作并回滚事务
type User struct {
ID int64
Name string 'gorm:"default:galeone"'
Age int64 'gorm:"18"'
}
type Email struct {
ID int64
Name string
Email string
}
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
if u.Age < 0 {
return errors.New("...")
}
return
}
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
return tx.Create(&Email{ID: u.ID, Email: u.Name + "@***.com"}).Error
}
GORM性能提高
对于写操作,GORM会封装在事务内运行,但会降低性能,可以用SkipDefaultTransaction关闭默认事务
使用PrepareStmt缓存预编译语句可以提高后续调用的速度。
&gorm.config{
SkipDefaultTransaction: true,
PrepareStmt: true
}