这是我参与【第五届青训营】伴学笔记创作活动的第6天
一、本堂课重
- 1、GORM设计原理
- SQL生成
- 扩展机制
- ConnPool
- Dialector
- 2、GORM最佳实践
- 企业级开发
- FAQ
二、详细知识点介绍
1、GORM设计原理
SQL---GORM
SELECT —— FROM —— WHERE ——GROUP BY —— ORDER BY —— LIMIT —— FOR
GORM:
db.Where("role <> ?", "manager").Where("age > ?", 35).Limit(100).Order("age desc").Find(&user)
ConnPool
【GORM】 --- SQL ---> 【DB Conn】---连接池---> 【数据库】
//全局模式,所有 DB 操作都会预编译并缓存 (缓存不含参数部分)
db,err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{PrepareStmt: true})
db.First(&user,1)
// 会话模式,后续会话的操作都会预编译并缓存
tx := db.Session(&SessionfPrepareStmt: true})
tx.Find(&users)
tx.Model(&user).Update("Age",18)
// 全局缓存的语句可被会话使用
tx.First(&user,2)
stmtManger,ok := tx.ConnPool.(*PreparedStmtDB)
//关闭当前会话的预编译语句
stmtManger.Close)
Callback
多租户
多数据库、读写分离
DB.Use(dbresolver.Register(dbresolver.Config{
// `db2`作为 sources、`db3`、`db4`作为 replicas
Sources: []gorm.Dialector{ mysql.Open("db2_dsn")},
Replicas: []gorm.Dialector[ mysql.Open("db3_dsn"),mysql.Open("db4_dsn")},
// sources/repLicas 负载均衡策略
Policy: dbresolver.RandomPolicy{},
}).Register(dbresolver.Config{
//`db1`作为 sources (DB 的默认连接),对于UserAddress使用db5 作为 replicas
Replicas:[]gorm.Dialectorf mysql.Open("db5_dsn")},
},&User{},&Address{}).Register(dbresolver.Config{
//`db6`、`db7`作为 sources,对于orders、Product 使用`db8` 作为 replicas
Sources:[]gorm.Dialector mysql.Open("db6_dsn"),mysql.Open("db7_dsn")},
Replicas: []gorm.Dialectorf mysql.Open("db8_dsn")},
},"orders",&Product{},"secondary"))
//使用 Write 模式:从 sources db `db1` 读取 user
DB.Clauses(dbresolver.Write).First(&user)
//指定 Resolver: 从 secondary’的 replicas db db8 读取 user
DB.Clauses(dbresolver.Use("secondary")).First(&user)
//指定 Resolver 和 Write 模式:从`secondary`的 sources db `db6`或`db7`读取 user
DB.Clauses(dbresolver.Use("secondary"),dbresolver.Write).First(&user)
bytegorm
import(
"code .byted.org/gorm/bytedgorm"
"gorm.io/gorm"
)
// 初始化
DB,err := gorm.Open(
// psm 的格式为 p.s.m 无需 _write,_read 等后缀,dbname 为数据库名
bytedgorm.MySQL("p.s.m"/*数据库PSM*/,"dbname"/*数据库名\*/).WithReadReplicas()
bytedgorm.WithDefaults(),
)
Dialector
import "gorm.io/driver/mysql"
dsn := "user:passetcp(127.0.0.1:3306)/dbname?charset=utf8n"seTime=True&loc=Local
db,err := gorm.Open(mysql .Open(dsn).&gorm.Config)
import "gorm.io/driver/postgres"
db, err := gorm.Open(postgres .Open(dsn), &gorm.Configf)
import "gorm.io/driver/clickhouse"
db,err := gorm.Open(clickhouse.Open(dsn), &gorm.Config.)
import "xxx.io/caches"
db,err := gorm.Open(caches .New(caches.Config{
Fallback: mysql.Open(dsn),
Store: lru.New(lru.Config{}),
}),&gorm.Config{})
2、GORM最佳实践
SQL表达式更新创建
//方法1:通过 gorm.Expr 使用 SQL 表达式
db.Model(User3)Create(map[stringlinterface{}{
"Name":"jinzhu",
"Location": gorm.Expr("ST_PointFromText(?)","POINT(100 100)"),
}
// INSERT INTO "user_with_points" ("name""location") VALUES ("jinzhu"ST_PointFromText("POINT(100 100)");
db.Model(&product).Update("price",gorm.Expr("price * ? + ?", 2, 100))
// 方法2: 使用 GORMValuer 使用 SQL 表达式 / SubQuery
type Location struct {
X, Y int
}
func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
return gorm.Expr("ST_PointFromText(?)", fmt.Sprintf("POINT(%d %d)", loc.X, loc.Y))
}
db.Create(UserName:"jinzhu",Location:Location{X: 100,Y: 100}})
db.Model(&User{ID: 1}).Updates(User{Name:"jinzhu",Location: Location{X: 100,Y: 100}})
//方法3: 通过 *gorm.DB 使用 SubQuery
subQuery := db,Model(&Companyf+).Select("name"),Where("companies.id = users,company_id")
db.Model(&user)Updates(map[stringlinterface{}{}{"company-name": subQuery})
// UPDATE "users"SET "company-name" (SELECT name FROM companies WHERE companies,id = users.company_id);
SQL表达式查询
// 方法1:使用gorm.Expr
db.Where("location = ?", gorm.Expr("ST_PointFromText(?)", "POINT(100 100)")).First(&user)
// SELECT * FROM `USERS` WHERE `location` = ST_PointFromText("POINT(100 100)");
// 方法2:Struct定义GormValuer
func (loc Location) GormValue(ctx context.Context, db *gorm.DB) clause.Expr{
return gorm.Expr("ST_PointFromText(?)", fmt.Sprint("POINT(%d %d)", loc.X, loc.Y))
db.Where("location = ?", LocationfX: 100,Y: 1003)First(&user)
// SELECT *FROM users’ WHERE Location'= ST_PointFromText("POINT(100 100)");
// 方法3:自定义查询 SQL 实现接口 clause.Expression
type Expression interface {
Build(builder Builder)
}
db.Find(&user,datatypes .JSONQuery("attributes").HasKey("role"))
db . Clauses(datatypes .JSONQuery("attributes").HasKey("org","name")).Find(&user)
// 方法4: SubQuery
db.where("name in (?)",db.Model(&User ).Select("name").Where("id > 10")).Find(&user)
数据序列化
type User struct{
Name []byte `gorm:"serializer:json"`
Roles Roles `gorm:"serializer:json"`
Contracts map[string]interface{} `gorm:"serializer:gob"`
JobInfo Job `gorm:"type:bytes;serializer:gob"`
CreatedTime int64 `gorm:"serializer:unixtime;type:time"`
Password Password
Attributes datatypes.JSON
}
// 自定义数据格式实现接口 Scanner,Valuer
func (j JSON) Value) (driver .Value, error) {/** **/}
func (j *JSON) Scan(value interface{}) error f {/** **/}
//自定义数据格式实现 Serializer 接口
type Password string
type Serializer interface {
{Scan(context.Context,f *schema.Field,dst reflect.Value,dbValue interface{}) error
Value(context.Context,f *schema.Field,dst reflect.Value,fieldV interface{})
(interface{},error
}
批量创建/查询
// 批量创建
var users = []User{{Name: "jinzhu1"},{Name: "jinzhu2"},{Name: "jinzhu3"}}
db.Create(&users)
db.CreateInBatches(users,100)
for _, user := range users {
user.ID // 1,2,3
}
//批量查询
rows,err := db.Model(&User{}).Where("role = ?","admin").Rows()
for rows.Next() {
// 方法1: sql.Rows Scan
rows.Scan(&name,&age,&email) // NULL 值的情况?
// 方法2: gorm ScanRows
db.ScanRows(rows,&user)
// xxx
}
DB.Where("role = ?""admin").FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
})
批量更新
//忽路数据冲突
db.Clauses(clause.0nConflictfDoNothing: true}).Create(&users)
// INSERT INTO `users`*** ON DUPLICATE KEY DO NOTHING; // PostgresQL
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `id`=`id`; // MySQL
db,Clauses(clause.Insert{Modifier: "IGNORE"}).Create(&users)
//INSERT IGNORE INTO `users` ***; // MySQL
// 数据冲突时更新某些字段
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"deleted_at": nil}),
}).Create(&users)
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `deleted_at`=NULL;
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"count": gorm.Expr("GREATEST(count, VALUES(count))")}
}).Create(&users)
// 使用 SOL 表达式更新INSERT INTO `users` ON DUPLICATE KEY UPDATE `count`=GREATEST(count,VALUES(count));
//数据冲突时更新某些字段为新值
db.Clauses(clause.OnConflict{
Columns:[]clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string {"nare""age"}),
}).Create &users)
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age);
// 数据冲突时更新全部字段(除主键) 为新值
db.Clauses(clause.0nConflictfUpdateAll: true )Create(&users)
// INSERT INTO `users` *** O DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age),...;
……
Gen代码生成/Raw SQL - Raw SQL
//原生 SQL
db.Raw("SELECT id,name,age FROM users WHERE name = ?", "jinzhu")Scan(&result)
db.Raw("SELECT id,name,age FROM users WHERE name = jinzhu").Scan(&result)
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN ?", time.Now(), []int64{1,2,3})
//Row & Rows
//使用 GORM API 构建 SQL
row := db.Table("users").Where("name = ?","jinzhu").Select("name", "age").Row()
// SELECT name,age FROM users WHERE name = "jinzhu"
row.Scan(&name,&age)
// 使用 Named Argument 做为 Where 条件
DB.Where("namel = @name OR name2 = @name"sql,Named("name","jinzhu")).Find(&user)
DB.Where("name1= @name OR name2= @name", map[string]interface{}{"name":"jinzhu"}).Find &user)
DB.Where("name1 = @Name OR name2 = @Name", User{Name:"jinzhu"}).Find(&user)
// SELECT * FROM users’ WHERE name1 = "jinzhu" OR name2 = "jinzhu'
// 命名参数和原生 SQL
DB.Raw(
"SELECT * FROM users WHERE name1 = @name OR name2 = @name2 OR name3 = @name", sql.Named("name","jinzhu1"), sql.Named("name2","jinzhu2")
).Find(&user)
// SELECT * FROM users WHERE name1 = "jinzhu1"OR name2 = "jinzhu2" OR name3 = "jinzhu1"
Gen代码生成/Raw SQL - Gen
type Query interface {
// SELECT *FROM @@table
// {{where}}
// {{if id>0}} id-@id {{end}}
// {{if name !=""}}AND name=@name {{end}}
// {{if age >18}}AND age>@age {{end}}
// {{end}}
FindUser(id int32,name string,age int)(gen.T,err)
}
// 生成自定义 SQL 静态代码
g := gen.NewGenerator(gen.Config{
OutPath:"../dal/query",
})
g.AppLyBasic(model.User{}) // 生成对应的 CURD API
g.ApplyInterface(func(query method.Query) {} ,model.User{}) // 给 User 生成 Query Interface 方法
g.Execute()
//查询数据
user,err := query.User.FindUser(10,"jinzhu",16)
// SELECT * FROM users WHERE id = 10 AND name ="jinzhu"
安全问题
// 安全,会以参数传入
db.Create(UserfName: userInput})
db.Model(user).Update( "name",userInput)
db.Where(UserName: userInput3).First(&user)
db.Where("name = ?",userInput).First(&user)
// 危险,SQL 注入
sql := fmt.Sprintf("name = %v", userInput)
db .Where(sql).First &user)
db.Select("name; drop table users;").First(&user)
db.Distinct("name; drop table users;").First(&user)
db.Model(&user).Pluck("name; drop table users;", &names)
db.Group("name; drop table users;").First(&user)
db.Group("name").Having("1 = 1;drop table users;").First(&user)
db.Raw("select name from users; drop table users;").First(&user)
db.Exec("select name from users; drop table users;")
三、课后个人总结
从 SQL、插件扩展、ConnlPool以及 Dialector 分别展开,了解到GORM设计原理等等。