GORM 最佳实践(3) | 青训营笔记

298 阅读4分钟

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请求中获取pagepage_size两个参数,并根据它们计算出相应的offsetlimit,最终通过db.Offset(offset).Limit(pageSize)将这些分页条件应用到数据库查询中。

调用方可以通过使用db.Scopes(Paginate(r))来将分页条件应用到数据库查询中。例如,上面的示例代码中,db.Scopes(Paginate(r)).Find(&users)db.Scopes(Paginate(r)).Find(&articles)分别在查询usersarticles时应用了该分页条件。

代码复用、分库分表、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表和NotificationAuditLog表,使用了Snowflake算法作为主键生成器,并指定了user_id作为分片键,并设置了分片数量。接着,使用db.Create方法插入一条Order记录,其中UserID2。最后,使用db.Exec方法执行INSERT语句插入一条orders记录,其中user_id3

混沌工程

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作为其日志记录器。