GORM使用实践 | 青训营

58 阅读5分钟

由于在大项目中我们采用了GORM,因此我直接总结在大项目中数据库使用的相关操作

mysql数据库创建

首先,我们需要先对mysql进行配置,创建两个数据库分别用于user和video两个微服务的数据库。

# 该数据库存放用户信息
User:
  Address: 127.0.0.1
  Port: 3306
  Username: root
  Password: "520666"
  Dbname: tinytiktok-user

# 该数据库存放视频信息
Video:
  Address: 127.0.0.1
  Port: 3306
  Username: root
  Password: "520666"
  Dbname: tinytiktok-video

同时我们需要在mysql中创建对应的表,user服务中包含有message、relation、user三个表,而video服务中则包含comment、detail、like、video四个表。但是我们还没有将它和golang结合在一起,我们的抖音demo还不能从数据库读写数据。需要使用gorm来读写数据,我们还需要先给每个表定义一个模型。

模型定义

Message:

type Message struct {
    gorm.Model
    Id         int64  `json:"id" gorm:"user_id"`
    UserId     int64  `json:"user_id" gorm:"user_id"`
    ReceiverId int64  `json:"receiver_id" gorm:"receiver_id"`
    ActionType int64  `json:"action_type" gorm:"action_type"`
    MsgContent string `json:"msg_content" gorm:"msg_content"`
}

Relation:

type Relation struct {
    gorm.Model
    ID     int64 `gorm:"column:id" json:"id"`
    UserID int64 `gorm:"column:userid" json:"userid"` // 自己
    PID    int64 `gorm:"column:pid" json:"pid"`       // 好友
    State  bool  `gorm:"column:state" json:"state"`   // 状态 bool->uid关注pid  false uid没关注pid
}

User:

type User struct {
    gorm.Model
    ID             int64  `gorm:"column:id" json:"id"`
    Name           string `gorm:"column:name;unique" json:"name"`
    Password       string `gorm:"column:password" json:"-"`
    Salt           string `gorm:"column:salt" json:"-"`
    FollowCount    int64  `gorm:"column:follow_count" json:"follow_count"`
    FollowerCount  int64  `gorm:"column:follower_count" json:"follower_count"`
    IsFollow       bool   `gorm:"column:is_follow" json:"is_follow"`
    Avatar         string `gorm:"column:avatar" json:"avatar"`
    BackgroundImg  string `gorm:"column:background_image" json:"background_image"`
    Signature      string `gorm:"column:signature" json:"signature"`
    WorkCount      int64  `gorm:"-" json:"work_count"`
    FavoriteCount  int64  `gorm:"-" json:"favorite_count"`
    TotalFavorited int64  `gorm:"-" json:"total_favorited"`
}

Comment:

type Comment struct {
    gorm.Model
    ID      int64  `gorm:"primaryKey" json:"id"`
    UserID  int64  `gorm:"column:user_id" json:"user_id"`
    VideoID int64  `gorm:"column:video_id" json:"video_id"`
    Content string `gorm:"column:content" json:"content"`
}

Detail:

type Detail struct {
    gorm.Model
    ID int64 `gorm:"column:id" json:"id"`
    // 用户的作品数量
    WorkCount int64 `gorm:"column:work_count" json:"work_count"`
    // 用户喜欢的数量
    FavoriteCount int64 `gorm:"column:favorite_count" json:"favorite_count"`
    // 用户所有作品被点赞数量的总和 获赞数量
    TotalFavorited int64 `gorm:"column:total_favorited" json:"total_favorited"`
}

Like:

type Like struct {
    gorm.Model
    ID      int64 `gorm:"primaryKey" json:"id"`
    UserID  int64 `gorm:"column:user_id" json:"user_id"`
    VideoID int64 `gorm:"column:video_id" json:"video_id"`
    State   bool  `gorm:"column:state" json:"state"`
    // 用于redis同步数据库
    Table  string `gorm:"-"`
    IsEdit bool   `gorm:"-"`
}

Video:

type Video struct {
    gorm.Model
    ID            int64  `gorm:"primaryKey" json:"id"`
    AuthorID      int64  `gorm:"column:author_id" json:"author_id"`
    PlayURL       string `gorm:"column:play_url" json:"play_url"`
    CoverURL      string `gorm:"column:cover_url" json:"cover_url"`
    FavoriteCount int64  `gorm:"column:favorite_count" json:"favorite_count"`
    CommentCount  int64  `gorm:"column:comment_count" json:"comment_count"`
    Title         string `gorm:"column:title" json:"title"`
}

将上述模型定义完了之后,我们接下来就可以对数据进行操作了。

数据库连接

接MySQL数据库,需要引入 gorm 和mysql两个包:

import ( "gorm.io/driver/mysql" "gorm.io/gorm" )

借助上述mysql配置建立起与mysql的连接:

dbConfig := config.NewConfig(fmt.Sprintf("%s/config", path), "mysql.yaml", "yaml")
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local", username, password, address, port, dbName)
var err error
VideoDb, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})

为了让mysql可以更好的工作,我们还需要再设置一下给连接对象设置空闲时的最大连接数、设置与数据库的最大打开连接数,每一个连接的生命周期等信息,具体配置如下:

  sqlDB, err := db.DB()
	if err != nil {
		return err
	}
	sqlDB.SetMaxIdleConns(1000)//设置空闲时的最大连接数
	sqlDB.SetMaxOpenConns(100000)//设置与数据库的最大打开连接数
	sqlDB.SetConnMaxLifetime(-1)//每一个连接的生命周期等信息

自动迁移表

gorm还有一个强大的功能,就是自动迁移表功能。启用自动迁移模式可以保持mysql表更新到最新。

上一节我们已经创建好了7个表的模型,并且提到了可以使用 AutoMigrate 函数来实现自动迁移,现在我们将它们添加为自动迁移模式。

func Migrate() {
    err := VideoDb.AutoMigrate(&models.Video{})
    if err != nil {
       panic(msg.UnableCreateTable)
    }
    err = VideoDb.AutoMigrate(&models.Like{})
    if err != nil {
       panic(msg.UnableCreateTable)
    }
    err = VideoDb.AutoMigrate(&models.Detail{})
    if err != nil {
       panic(msg.UnableCreateTable)
    }
    err = VideoDb.AutoMigrate(&models.Comment{})
    if err != nil {
       panic(msg.UnableCreateTable)
    }
}

CRUD

接下来我们可以借助gorm来实现对数据的增删改查,这里由我实现的评论部分举例。

增:

首先要实现评论功能,我在发送评论时要将用户的评论存入数据库,具体代码如下:

// CommentVideo 将评论插入数据库
func CommentVideo(db *gorm.DB, comment *Comment) (int64, error) {
    result := db.Create(comment)
    if result.Error != nil {
       return -1, result.Error
    }

    var CommentId int64
    db.Raw("select LAST_INSERT_ID() as id").Pluck("id", &CommentId)

    return CommentId, nil
}

注意,在我们插入评论时,我们需要在评论列表中实时返回我们插入的评论,因此我们在返回体中加入评论的内容(由id获取),我们知道gorm中的id是自增的,在插入到数据表中才能获取,因此我们在“增”之后,利用“select LAST_INSERT_ID()”将最新的id取出,方便后续在返回体中获取comment的具体内容。

删:

func DeleteComment(db *gorm.DB, commentID int64) error {
    comment := Comment{}时时

    db.Where("id = ?", commentID).Take(&comment)
    result := db.Delete(&comment)
    if result.Error != nil {
       return result.Error
    }
    return nil
}

对于评论的删除,要注意的是,当模型的中存在deletedAt字段时,我们采用的是软删除,gorm只是将被删除的那行数据的deletedAt字段进行修改,该数据并没有被真正的删除。我们可以通过unscoped查询被软删除的数据

db.Unscoped().Where( "age = 20" ).Find(&users)

同样,我们也可以借助unscoped进行硬删除(永久删除):

db.Unscoped().Delete(&order)

我们可以借助视频的ID来获取其下面的所有评论,我们在初次打开视频时会需要用到这个操作。

// 通过VideoID获取评论列表
func GetCommentListByVideoID(db *gorm.DB, videoID int64) ([]*Comment, error) {
    var comments []*Comment
    err := db.Where("video_id = ?", videoID).Find(&comments).Error
    if err != nil {
       return nil, err
    }
    return comments, nil
}

同样,我们也可以借助用户id查询到该用户发布的所有评论,以及借助评论的id查询到评论内容。

在实际的设计当中,除了增删改查之外,我们还会将这些操作组合起来,实现更多的内容,例如借助视频的id来统计视频的评论数量,并在评论和删除评论时对评论数量进行改动:

func CalcCommentCountByVideoID(db *gorm.DB, videoID int64, isCommented bool) error {
    var video Video
    result := db.First(&video, videoID)
    if result.Error != nil {
       return result.Error
    }
    if isCommented {
       video.CommentCount++
    } else {
       video.CommentCount--
    }
    result = db.Save(&video)
    if result.Error != nil {
       db.Rollback()
       return result.Error
    }
    return nil
}