gorm 实战 | 青训营
既然是项目实战,我们当然离开不了业务场景,这样我模拟一下抖音的功能,咱们在此基础上进行简单的实战,对全面理解gorm 很有帮助。
场景一:用户进行关注其他用户操作
大家一看就知道一个词:点关注
对这一用户行为,前端传递的信息一定有:
对方用户的ID,操作码(注意:1为关注,2为取关)我们暂且不对用户进行token认证
在此之前,我们要有进行数据库建表操作,使用gorm ,这里我给大家带来一个好的建立数据库模型方案
1 . 构建自己的gorm模型,在其他结构体中使用此模型
var migrate = make([]any, 0, 10)
type Model struct {
ID int64 `json:"id" gorm:"primarykey;comment:主键"`
CreatedAt time.Time `json:"-" gorm:"comment:创建时间"`
UpdatedAt time.Time `json:"-" gorm:"comment:修改时间"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"comment:删除时间"`
}
func (m *Model) BeforeCreate(tx *gorm.DB) (err error) {
return
}
func GetMigrate() []any {
return migrate
}
// addMigrate 加入自动迁移列表中
func addMigrate(model ...any) {
migrate = append(migrate, model...)
}
注意
addMigrate(model ...any) 函数,在init函数中我们会使用
- 构建user用户表
type User struct {
Model
Name string `json:"name" gorm:"index:,unique;size:32;comment:用户名称"`
Pawd string `json:"-" gorm:"size:128;comment:用户密码"`
Avatar string `json:"avatar" gorm:"comment:用户头像"`
BackgroundImage string `json:"background_image" gorm:"comment:用户个人页顶部大图"`
Signature string `json:"signature" gorm:"default:此人巨懒;comment:个人简介"`
WorkCount int64 `json:"work_count" gorm:"default:0;comment:作品数量"`
Follow []*User `json:"follow,omitempty" gorm:"many2many:UserFollow;comment:关注列表"`
}
-
巧用 init函数
我们利用init函数进行模型装载到
migrate切片
func init() {
addMigrate(&User{})
}
ok! ,进行对表构建,执行数据库连接
- 连接数据库进行模型构建
func InitDb() {
var dialector gorm.Dialector
var dB *gorm.DB
var err error
log.Info("开始初始化 Database 服务!")
database := conf.Conf.Database
switch strings.ToUpper(database.Type) {
case "SQLITE3":
sqliteUrl := fmt.Sprintf("%s?_journal=WAL&_vacuum=incremental", database.DbFile)
if database.DbFile == "" {
sqliteUrl = "file::memory:?cache=shared"
}
dialector = sqlite.Open(sqliteUrl)
case "MYSQL":
dialector = mysql.Open(fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
database.User, database.Password, database.Host, database.Port, database.Name))
case "POSTGRES":
dialector = postgres.Open(fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable TimeZone=Asia/Shanghai",
database.Host, database.User, database.Password, database.Name, database.Port))
default:
log.Fatalf("not supported database type: %s,supported:[sqlite3,mysql,postgres]", database.Type)
}
}
logLevel := logger.Silent
{
logLevel = logger.Info
}
dB, err = gorm.Open(dialector, &gorm.Config{
Logger: logger.New(
stdlog.New(log.StandardLogger().Out, "\r\n", stdlog.LstdFlags),
logger.Config{
SlowThreshold: time.Second, // 设置慢查询阈值
LogLevel: logLevel, // 设置日志级别
IgnoreRecordNotFoundError: true, // 忽略记录未找到的错误
Colorful: true, // 启用彩色日志输出
},
),
NamingStrategy: schema.NamingStrategy{
SingularTable: true, //表名以单数形式命名
},
TranslateError: true, // 启用错误翻译功能
})
if err != nil {
log.Fatalf("无法连接到数据库:%s", err.Error())
}
db.InitDb(dB)
log.Info("初始化 Database 成功!")
}
我们把连接数据库信息放在conf.Conf.Database结构体下面,
使用函数对其进行初始化func DefaultConfig() Config { return Config{ Address: "0.0.0.0", Port: 23724, // 2023-07-24 Database: confDatabase{ Type: "mysql", Host: "localhost", Port: 3306, User: "root", Password: "root", Name: "douyin", DbFile: "data/data.db", }, Redis: confRedis{ Host: "127.0.0.1", Port: 6379, Password: "", Db: 3, }, Log: confLog{ Enable: true, Level: "info", Name: "data/log/log.log", MaxSize: 10, MaxBackups: 5, MaxAge: 28, Compress: false, }, } }
连接数据库搞定,使用单数命名表,即,在调用db.AutoMigrate(parms...结构体)创建的表都是单数,上述user表就是user,而不是users
- 模型迁移
// InitDb 初始化数据库服务
func InitDb(d *gorm.DB) {
db = d
for _, m := range model.GetMigrate() {
err := db.AutoMigrate(m)
if err != nil {
log.Fatalf("%s 模型自动迁移失败: %s", reflect.TypeOf(m), err.Error())
}
}
}
代码执行到这里,数据库表信息已经建立完毕
下面对InitDb函数进行剖析
这段代码实现了基于配置文件的数据库连接初始化:
- 首先读取配置文件中database部分的类型和连接信息
- 根据类型使用对应的gorm dialector打开连接
- sqlite3: 使用sqlite dialector
- mysql: 使用mysql dialector
- postgres: 使用postgres dialector
- 配置gorm.Config
- 设置日志记录器,记录SQL日志
- 命名策略使用单数表名
- 翻译SQL错误
- 使用gorm.Open函数打开数据库连接
- 设置全局dB变量
- 调用db.InitDb初始化数据库
关键点:
- 通过dialector实现不同数据库类型的连接封装
- 配置gorm连接选项如日志记录等
- gorm.Open打开连接,初始化数据库实例
- 初始化自定义数据库结构或数据
这提供了一个通用的数据库连接初始化方式:
- 根据配置支持多种数据库
- 统一的日志和错误处理
- 初始化自定义数据库操作
是基于配置文件的数据库连接建立的一个好例子。
场景一,实现
// RelationAction 关注/取关 parm:fid 自己id, tid 对方id
func RelationAction(fid, tid int64, ActionType int) error {
var (
association *gorm.Association
err error
)
tx := db.Begin()
fval := &model.User{Model: id(fid)}
tval := &model.User{Model: id(tid)}
association = tx.Model(fval).Association("Follow")
switch ActionType {
case 1:
err = association.Append(tval)
//使用redis进行缓存操作
//-------------------
case 2:
err = association.Delete(tval)
//使用redis进行缓存操作
//-------------------
default:
return errors.New("不合法的 ActionType")
}
if err != nil {
tx.Rollback()
return err
}
tx.Commit()
return nil
}
这段代码实现了关注和取关的操作:
主要步骤:
开始数据库事务
根据fid获取following模型对象fval
根据tid获取followed用户模型对象tval
调用Model(fval).Association("Follow")获取关联关系
根据ActionType类型执行关联操作:
1时,调用Append方法添加关联
2时,调用Delete方法删除关联
- 判断错误,提交或回滚事务
- 将操作缓存到Redis
作用:
- 根据用户id获取模型对象
- 操作关联关系表实现关注或取关
- 在数据库事务中执行
- 将操作结果同步到缓存
这里关注点在于:
- 根据id从数据库加载模型对象
- 使用Association方法获取关联关系
- 调用Append/Delete方法进行关联表操作
- 提交或回滚事务保证数据一致性
是Gorm常用方法关联操作的一个应用案例。
详细讲解
Association函数,此函数执行必须构建model,tx.Model(fval)Association函数是GORM框架中很重要的一环,它用于查询和处理关系型数据。
主要功能有:
- 指定关系表名
调用Association函数主要目的是指定当前查询要关联的关系表名。
例如:
db.Model(&User).Association("Orders")就指定了当前要查询的关系表名是Orders表。
- 构建JOIN查询
GORM会自动将当前表和指定的关系表进行INNER JOIN,从而将两表进行关联查询。
- 预加载关联数据
常用于一对一或一对多关系的预加载,可以避免N+1查询问题。
例如:
db.Preload("Orders").Find(&users)
- 关联条件查询
通过向Association函数传入条件参数,可以增加关联表的WHERE条件 filtler数据。
例如:
db.Where("status = 1").Association("Orders")
- 关联更新/删除
可以一次操作关联表更新或删除多条记录。
所以总的来说,Association函数用来:
- 指定要关联的关系表
- 构建关联查询语句
- 预加载或过滤关联数据
- 方便进行关联表更新等操作
是GORM开发关系型数据库应用不可或缺的功能。