这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。 PPT
目录
1. 理解 database/sql
database/sql 的基本用法,设计原理,基础概念介绍
2. GORM 使用简介
GORM的基本用法,Model定义,惯例约定,关联介绍
3. GORM 设计原理
SQL生成,扩展机制, ConnPool,Dialector等
4. GORM 最佳实践
GORM 最佳实践,企业级开发,FAQ等
理解 database/sql
1. 基本用法 - Quick Start
import driver实现,使用 driver + DSN 初始化 DB 连接。
执行一条 SQL,通过 rows 取回返回的数据,处理完毕需要释放连接。
数据、错误处理。(最后一个rows.Err()不要忘记!)
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
type User struct {
ID int64
Name string
}
func main() {
db, err := sql.Open("mysql", "user:password@(127.0.0.1:3306)/hello?parseTime=true")
rows, err := db.Query("select id , name from users where id = ?", 1)
if err != nil {
// xxx
}
defer rows.Close()
var users []User
for rows.Next(){
var user User
err := rows.Scan(&user.ID , &user.Name)
if err != nil {
// xxx
}
users = append(users,user)
}
if rows.Err() != nil{
// xxx
}
}
2. 设计原理
操作过程伪实现。
for i := 0; i < maxBadConnRetries; i++ {
// 从连接池获取连接或通过 driver 新建连接
dc, err := db.conn(ctx, strategy)
// 有空闲连接 -> reuse -> max life time
// 新建连接 -> max open ...
// 将连接放回连接池
defer dc.db.putConn(dc, err, true)
// validateConnection 有无错误
// max life time , max idle conns 检查
// 连接实现 driver.Queryer,driver.Execer 等 interface
if err == nil {
err = dc.ci.Query(sql, args...)
}
isBadConn = errors.Is(err, driver.ErrBadConn)
if !isBadConn {
break
}
}
操作接口
DB连接的几种类型 :直接连接 / Conn , 预编译 / Stmt , 事务 / Tx 。
处理返回数据的几种方式 :Exec / ExecContext -> Result ,
Query / QueryContext -> Rows(Columns) ,
QueryRow / QueryRowContext -> Row(Rows简化) 。
GORM 使用简介
1. 背景知识
“设计简洁、功能强大、自由扩展的全功能 ORM”
设计原则 :API精简、测试优先、最小惊讶、灵活扩展、无依赖 可依赖
功能完善 :......
2. 基本用法
注意!!!!!!!!!!!!!结构体首字母一定要大写!!!!!!!!!!!
如下代码,使用 gorm 可达成最开始使用 datebase/sql 的代码一样的效果。
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
ID int64
Name string
}
func main() {
// gorm
db, err := gorm.Open(
mysql.Open("root:929275@tcp(127.0.0.1:3306)/csj"))
var users []User
err = db.Select("id", "name").Find(&users, 1).Error
}
基本用法 - CRUD
// 操作数据库
db.AutoMigrate(&Product{})
db.Migrator().CreateTable(&Product{})
// 创建
user := User{Name: "Jinzhu"}
result := db.Create(&user)
user.ID // 返回主键
result.Error // 返回 error
result.RowsAffected // 返回影响的行数
// 批量创建
var users = []User{{Name: "xianghong"}, {Name: "xiaohuang"}, {Name: "xiaolan"}}
db.Create(&users)
db.CreateInBatches(users, 100)
for _, user := range users {
user.ID
}
// 读取
var product Product
db.First(&product, 1) // 查询 id 为 1 的 product
db.First(&product, "code = ?", "L1212") // 查询 code 为 L1212 的product
result := db.Find(&users, []int{1, 2, 3})
result.RowsAffected // 返回找到的记录数
errors.Is(result.Error, gorm.ErrRecordNotFound) // First , Last , Take 查不到数据
// 更新某个字段
db.Model(&product).Update("Price", 2000)
db.Model(&product).UpdateColumns("Price", 2000)
// 更新多个字段
db.Model(&product).Updates(Product{Price: 2000, Code: "L1212"})
db.Model(&product).Updates(map[string]interface{}{"Price":2000, "Code":"L1212"})
// 批量更新
db.Model(&Product{}).Where("price < ?" , 2000).Updates(map[string]interface{}{"Price":2000, "Code":"L1212"})
//删除 - 删除 product
db.Delete(&product)
3. 模型定义
惯例约定
约定优于配置
- 表名为 struct name 的 snake_cases 复数格式
- 字段名为 field name 的 snake_case 单数格式
- ID / id 字段为主键,如果为数字,则为自增主键
- CreateAt 字段,创建时,保存当前时间
- UpdateAt 字段,创建、更新时,保存当前时间
- gorm.DeleteAt 字段,默认开启 soft delete 模式
一切都可配置
4. 关联介绍
见PPT
GORM 设计原理
本小节实在是听不太懂,还是功底不够,以后再来回顾。
1. SQL 是怎么生成的
先看一个简单的 SQL , 由一些基本子句构成,比如 SELECT 、WHERE等。有的里面包含很多的表达式构成,如 WHERE 。
SELECT 'name', 'age', 'employee_number'
FROM 'users'
WHERE
role <> "maneger" AND
age > 35
ORDER BY age DESC
LIMIT 10 OFFSET 10
FOR UPDATE
再来看看 gorm 代码,由 Chain Method 和 Finisher Method 构成。
Chain Method : 添加相关子句的方法。如Where等
Finisher Method : 决定类型并且执行的方法。如Find方法,决定了是查找。
db.Where("role <> ?","manager").Where("age > ?",35).Limit(100).Order("age desc").Find(&user)
生成对应 SQL
SELECT * FROM users WHERE role <>"manager" AND age > 35 ORDER BY age desc LIMIT 100
GORM API 方法添加 Clauses 到 GORM Statement
// Where add conditions
func (db *DB) Where(query interface{}, args ...interface{}) (tx *DB) {
tx = db.getInstance()
if conds := tx.Statement.BuildCondition(query, args...); len(conds) > 0 {
tx.Statement.AddClause(clause.Where{Exprs: conds})
}
return
}
// Limit specify the number of records to be retrieved
func (db *DB) Limit(limit int) (tx *DB) {
tx = db.getInstance()
tx.Statement.AddClause(clause.Limit{Limit: limit})
return
}
// Find find records that match given conditions
func (db *DB) Find(dest interface{}, conds ...interface{}) (tx *DB) {
tx = db.getInstance()
if len(conds) > 0 {
if exprs := tx.Statement.BuildCondition(conds[0], conds[1:]...); len(exprs) > 0 {
tx.Statement.AddClause(clause.Where{Exprs: exprs})
}
}
tx.Statement.Dest = dest
return tx.callbacks.Query().Execute(tx)
}
自定义 Clause Builder ,方便扩展 Clause ,自由选择 Clauses 。
不同的数据库甚至不同版本的数据库支持的 SQL 不同,虽然都是用的 SQL 但是写 SQL 的时候甚至要写不同的版本。gorm 就希望把细节都隐藏起来,我们只要去实现一个版本就行。
对不同的数据库都支持,可以自己去扩展。
还可以扩展子句,使得用起来更加灵活。如下 :
db.Clauses(hints.New("MRR(idx1)")).Find(&User{})
// SELECT /*+ MRR(idx1) */ * FROM 'users'
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{})
// SELECT * FROM 'users' USE INDEX ('idx_user_name')
db.Clauses(hints.ForceIndex("idx_user_name", "idx_user_id").ForJoin()).Find(&User{})
// SELECT * FROM 'users' FORCE INDEX FOR JOIN ('idx_user_name','idx_user_id')
db.Clauses(hints.Comment("select", "master")).Find(&User{})
// SELECT /*master*/ * FROM 'users' ;
db.Clauses(hints.CommentBefore("insert", "node2")).Create(&user)
// /*node2*/ INSERT INTO 'users' ... ;
db.Clauses(hints.CommentAfter("where", "hint")).Find(&User{})
选择子句。不同数据库不一样,不改变代码的情况下兼容其他数据库。
/* PostgreSQL */
DELETE FROM users WHERE condition
DELETE FROM WHERE
/* MySQL */
DELETE FROM users WHERE condition ORDER BY sge desc LIMIT 100
DELETE FROM WHERE ORDER BY LIMIT
/* Clickhouse */
ALTER TABLE 'users' DELETE WHERE condition
ALTER/DELETE WHERE
2. 插件是怎么工作的
自由定制扩展操作。
Callback 在每次操作时都会被全部调用。比如说注册一个 Create 的 Callback 。每次调用 Create 时会按照顺序调用所有的 Create 下的 Callback 函数。如下:
3. ConnPool 是什么
4. Dialector 是什么
定义各种数据库的连接,还可以定义 io 的缓存。
可以定制 SQL 生成 、 定制 GORM 插件 、 定制 ConnPool 、 定制企业特征逻辑。
GORM 最佳实践
本节看PPT吧,码量太大,顶不住
写不那么理解的代码实在是头疼
1. 数据序列化与 SQL 表达式
关于SQL的表达式,不同数据库之间的统一性也不是很好,各种 SQL 方法有很多不一致的地方。为了支持所有的情况 GORM 提供了一些表达式的支持。
SQL 表达式更新创建。
// 方法1:通过 gorm.Expr 使用 SQL 表达式
db.Model(User{}).Create(map[string]interface{}{
"Name": "jinzhu",
"Location": gorm.Expr("ST_PointFromText(?)", "POINT(100 100)"),
})
//INSERT INTO "user_with_points" ("name", "location") VALUES ("jinzhu", ST_PointFromText("POINT(100 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(User{Name: "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(&Company{}).Select("name").Where("companies.id = users.company_id")
db.Model(&user).Updates(map[string]interface{}{}{"company_name":subQuery})