gorm 实战 | 青训营

118 阅读6分钟

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函数中我们会使用

  1. 构建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:关注列表"`
}
  1. 巧用 init函数

    我们利用init函数进行模型装载到 migrate切片

func init() {
	addMigrate(&User{})
}

ok! ,进行对表构建,执行数据库连接

  1. 连接数据库进行模型构建
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

  1. 模型迁移
// 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初始化数据库

关键点:

  1. 通过dialector实现不同数据库类型的连接封装
  2. 配置gorm连接选项如日志记录等
  3. gorm.Open打开连接,初始化数据库实例
  4. 初始化自定义数据库结构或数据

这提供了一个通用的数据库连接初始化方式:

  • 根据配置支持多种数据库
  • 统一的日志和错误处理
  • 初始化自定义数据库操作

是基于配置文件的数据库连接建立的一个好例子。

场景一,实现

// 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
}

这段代码实现了关注和取关的操作:

主要步骤:

  1. 开始数据库事务

  2. 根据fid获取following模型对象fval

  3. 根据tid获取followed用户模型对象tval

  4. 调用Model(fval).Association("Follow")获取关联关系

  5. 根据ActionType类型执行关联操作:

  • 1时,调用Append方法添加关联

  • 2时,调用Delete方法删除关联

  1. 判断错误,提交或回滚事务
  2. 将操作缓存到Redis

作用:

  • 根据用户id获取模型对象
  • 操作关联关系表实现关注或取关
  • 在数据库事务中执行
  • 将操作结果同步到缓存

这里关注点在于:

  1. 根据id从数据库加载模型对象
  2. 使用Association方法获取关联关系
  3. 调用Append/Delete方法进行关联表操作
  4. 提交或回滚事务保证数据一致性

是Gorm常用方法关联操作的一个应用案例。

详细讲解Association函数,此函数执行必须构建model,tx.Model(fval)

Association函数是GORM框架中很重要的一环,它用于查询和处理关系型数据。

主要功能有:

  1. 指定关系表名

调用Association函数主要目的是指定当前查询要关联的关系表名。

例如:

db.Model(&User).Association("Orders") 

就指定了当前要查询的关系表名是Orders表。

  1. 构建JOIN查询

GORM会自动将当前表和指定的关系表进行INNER JOIN,从而将两表进行关联查询。

  1. 预加载关联数据

常用于一对一或一对多关系的预加载,可以避免N+1查询问题。

例如:

db.Preload("Orders").Find(&users)
  1. 关联条件查询

通过向Association函数传入条件参数,可以增加关联表的WHERE条件 filtler数据。

例如:

db.Where("status = 1").Association("Orders")
  1. 关联更新/删除

可以一次操作关联表更新或删除多条记录。

所以总的来说,Association函数用来:

  • 指定要关联的关系表
  • 构建关联查询语句
  • 预加载或过滤关联数据
  • 方便进行关联表更新等操作

是GORM开发关系型数据库应用不可或缺的功能。