GORM 最佳实践(3) | 青训营笔记
- 数据序列化与 SQL 表达式 √
- 批量数据操作 √
- 代码复用、分库分表、Sharding
- 混沌工程/压测
- Logger/Trace
- Migrator
- Gen 代码生成/Raw SQL
- 安全
代码复用、分库分表、Sharding - 代码复用
func Paginate(r *http.Request) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
page, _ := strconv.Atoi(r.Query("page"))
if page == 0 {
page = 1
}
pageSize, _ := strconv.Atoi(r.Query("page_size"))
switch{
case pageSize > 100:
pageSize = 100
case pageSize <= 0:
pageSize = 10
}
offset := (page -1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
}
db.Scopes(Paginate(r)).Find(&users)
db.Scopes(Paginate(r)).Find(&articles)
码定义了一个名为Paginate的函数,其返回一个类型为func(*gorm.DB) *gorm.DB的闭包函数。该闭包函数接受gorm.DB类型的参数,并返回一个gorm.DB类型的值。
Paginate函数的实现是基于分页的逻辑。它将从HTTP请求中获取page和page_size两个参数,并根据它们计算出相应的offset和limit,最终通过db.Offset(offset).Limit(pageSize)将这些分页条件应用到数据库查询中。
调用方可以通过使用db.Scopes(Paginate(r))来将分页条件应用到数据库查询中。例如,上面的示例代码中,db.Scopes(Paginate(r)).Find(&users)和 db.Scopes(Paginate(r)).Find(&articles)分别在查询users和articles时应用了该分页条件。
代码复用、分库分表、Sharding - 分库分表
func TableOfYear(user *User, year int) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
tableName := user.TableName() + strconv.Itoa(year)
return db.Table(tableName)
}
}
DB.Scopes(TableOfYear(user, 2019)).Find(&users)
func TableOfOrg(user *User, dbName string) func(db *gorm.DB) *gorm.DB {
return func(db * gorm.DB) *gorm.DB {
tableName := dbName + "." +user.TableName()
return db.Table(tableName)
}
}
DB.Scopes(TableOfOrg(user, "org1")).Find(&users)
func TableOfUser(user *User) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB{
year := getYearInfoFromUserID(user.ID)
return db.Table(user.TableName() + strconv.Itoa(year))
}
}
TableOfYear函数接受一个User类型的指针和一个年份 year,返回一个函数类型,这个函数类型接受一个gorm.DB类型的指针,并返回一个gorm.DB类型的指针。具体实现是将 用户表名 和 年份 拼接起来作为表名,然后返回一个以这个表名作为参数的db.Table(tableName)调用结果。
TableOfOrg函数接受一个User类型的指针和一个数据库名dbName,返回一个函数类型,这个函数类型接受一个gorm.DB类型的指针,并返回一个gorm.DB类型的指针。具体实现是以dbName和 用户表名 拼接成新的表名,然后返回一个以这个表名作为参数的db.Table(tableName)调用结果。
TableOfUser函数接受一个User类型的指针,返回一个函数类型,这个函数类型接受一个gorm.DB类型的指针,并返回一个gorm.DB类型的指针。具体实现是通过调用getYearInfoFromUserID函数获取年份信息,然后将 用户表名 和 年份 拼接起来作为表名,最后返回一个以这个表名作为参数的db.Table(tableName)调用结果。
代码复用、分库分表、Sharding - Sharding
import "gorm.io/sharding"
db.Use(sharding.Register(sharding.Donfig{
ShardingKey: "user_id",
NumberOfShards: 64,
PrimaryKeyGenerator: sharding.PKSnowflake,
}, "orders").Register(sharding.Config{
ShardingKey: "user_id",
NumberOfShards: 256,
PrimaryKeyGenerator: sharding.PKSnowflake,
}, Notification{}, AuditLog{}))
db.Create(&Order{UserID: 2})
db.Exec("INSERT INTO orders(user_id) VALUES(?)", int64(3))
使用了GORM库中的Sharding功能,其中Sharding是指将一个大表拆分成若干个小表分别存储,以此提高查询和写入的性能。具体实现上,该代码注册了两个分片配置,分别针对orders表和Notification,AuditLog表,使用了Snowflake算法作为主键生成器,并指定了user_id作为分片键,并设置了分片数量。接着,使用db.Create方法插入一条Order记录,其中UserID为2。最后,使用db.Exec方法执行INSERT语句插入一条orders记录,其中user_id为3。
混沌工程
import(
"example.com/gorm/sqlchaos"
"gorm.io/gorm"
)
db, err := gorm.Open(
mysql.Open(dsn),
&gorm.Config{},
sqlchaos.WithChais(sqlchaos.Config{
PSM: "service name",
DBName: "dbname",
EnvList: []string{"ppe", "boe"},
}),
)
db.Create(&User{ID: 1024, Name: "rick", Result: 10})
首先导入了两个包:"example.com/gorm/sqlchaos":实现了GORM框架下数据操作中的混沌工程(Chaos Engineering);"gorm.io/gorm":GORM框架本身。
打开数据库连接,并将混沌工程配置作为附加选项传递给 gorm.Open() 函数。此处使用的是MySQL数据库的DSN连接字符串。最后,使用Create()方法向数据库中插入一条记录。
Logger/Trace
type Interface {
LogMode(LogLevel) Interface
Info(context.Context, string, ...interface{})
Warn(context.Context, string, ...interface{})
Error(context.Context, string, ...interface{})
Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowAffected int64), err error)
}
newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags),
logger.Config{
SlowThreshold: time.Second,
LogLevel: logger.Silent,
IgnoreRecordNotFoundError: true,
Colorful: false,
},
)
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{Logger: newLogger})
tx := db.Session(&Session{Logger: newLogger})
声明了一个接口Interface,其中定义了日志相关的方法 LogMode()、Info()、Warn()、Error()和Trace()。
初始化一个新的logger,使用了log.New()函数作为其Writer,并设置了一些配置项,如慢查询阈值、日志级别、是否忽略记录找不到错误等。
使用GORM(一个Go语言的ORM框架)打开一个SQLite数据库,并将新建的logger作为其配置项中的Logger。
通过db.Session()函数,生成一个新的Session对象,并将上一步中新建的logger作为其日志记录器。