使用 GORM(Go 的 ORM 库)连接MySql数据库,并实现CRUD | 青训营

200 阅读4分钟

环境配置

安装:

go get -u gorm.io/gorm
// mysql驱动
go get -u gorm.io/driver/musql

项目引入:

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

模型定义

模型是标准的struct,根据数据表设计,创建Video模型如下:

type Video struct {  
    ID int 64
    AuthorID int64 
    PlayURL string  
    CoverURL string  
    Title string  
}

约定

默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,若要关闭蛇形复数命名,则创建连接时,需在gorm.Config中配置:

db, err := gorm.Open(mysql.Open(datasourceConstants.DSNString()),  
    &gorm.Config{  
        NamingStrategy: schema.NamingStrategy{
        SingularTable: true,
    },
})

标签

GORM默认使用ID作为表的主键。若要修改主键为其他字段,可使用primaryKey标签声明;通过为多个字段设置primaryKey,可创建复合主键:

type Favorite struct {  
    VideoID int64 `gorm:"primaryKey"`  
    UserID int64 `gorm:"primaryKey"`
}

默认情况下,整型PrioritizedPrimaryField 启用了 AutoIncrement,要禁用它,需要为整型字段关闭 autoIncrement

type Favorite struct {  
    VideoID int64 `gorm:"primaryKey;autoIncrement:false"`  
    UserID int64 `gorm:"primaryKey;autoIncrement:false"`
}

使用index标签创建索引,not null标签指定列为NOT NULL,更多字段标签见:模型定义 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

gorm.Model

GORM定义一个grom.Model结构体,可将其嵌入我们自己的模型中:

// gorm.Model 的定义
type Model struct {
    ID        uint           `gorm:"primaryKey"`
    CreatedAt time.Time
    UpdatedAt time.Time
    DeletedAt gorm.DeletedAt `gorm:"index"`
}

嵌入结构体,改进video模型如下:

type Video struct {  
    gorm.Model    //包含ID、CreatedAt、UpdatedAt、DeletedAt
    AuthorID int64 `gorm:"index"`  //索引
    PlayURL string  
    CoverURL string  
    Title string  
}

连接MySQL

GORM支持连接的数据库类型有:MySQL, PostgreSQL, SQLite, SQL Server 和 TiDB,下面以MySQL为例,展示连接数据库,并设置GORM配置的方法:

var datasourceConstants = &constant.AllConstants.Datasource  
  
func InitGorm() *gorm.DB {  
    db, err := gorm.Open(mysql.Open(datasourceConstants.DSNString()),  
        &gorm.Config{  
            PrepareStmt: true,  \\开启PrepareStmt以提高效率
            SkipDefaultTransaction: true,  \\跳过默认事务
    })  
    ...  
    return db  
}

上面代码中连接到数据库时初始化了GORM配置,其中:

SkipDefaultTransaction为跳过默认事务:为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除),没有这方面需求可设为true,将获得性能提升,反之为false

PrepareStmt:开启后在执行任何 SQL 时都会创建一个 prepared statement 并将其缓存,以提高后续的效率;

CRUD

创建

创建记录:

先填充好要创建的记录的结构体,即上文定义的模型,再通过传递数据指针调用db.Create创建,返回的结果中携带插入数据的主键、error、插入记录条数。插入一条video记录:

package dal

var db *gorm.DB  
  
type Video struct {  
gorm.Model  
AuthorID int64 `gorm:"index"`  
PlayURL string  
CoverURL string  
Title string  
}

func SetVideoDB() {  
    db = dal.InitGorm()  
}

func CreateVideo(video *Video) (int64, error) {  
result := db.Create(video)  
return int64(video.ID), result.Error  
}
package service

vid, err := dal.CreateVideo(&dal.Video{  
    AuthorID: id,  
    PlayURL: videoURL,  
    CoverURL: coverURL,  
    Title: req.Title,  
    })

创建记录时也可指定字段:分为为为指定字段分配值和为指定字段忽略值两种: db.Select("PlayURL", "CoverURL").Create(&Video)

db.Omint("AuthorID", "Title").Create(&Video)

Create方法也可以创建多条记录

查询

查询单条记录:

GORM提供的First、Take、Last方法用于从数据库中检索单个对象,查询时添加了LIMIT 1,没有找到记录时会返回ErrRecordNotFound错误(可以使用db.Limit(1).Find(&video)来避免ErrRecordNotFound错误,但对单个对象使用db.Find(&video)不带limit,将会查询整个表且只返回第一个对象,该做法性能不高且返回结果不确定),其中:

db.First(&video)等价于:MYSQLSELECT * FROM videos ORDER BY id LIMIT 1;

db.Take(&video)等价于:MYSQLSELECT * FROM users LIMIT 1;

db.Last(&video)等价于:MYSQLdb.Last(&user)

若要根据主键检索,如根据视频id检索视频,则在上述方法中添加主键参数: (当主键为string类型时,使用该方法应小心SQL注入)

func GetVideo(vID int64) (*Video, error) {  
    var video Video  
    result := GormDB.First(&video, vID)  //SELECT * FROM videos WHERE id = ;
    return &video, result.Error  
}
列表查询:

db.Find(&videos, []int{1, 2, 3})等价于: SELECT * FROM users WHERE id IN (1,2,3);

根据视频id列表查询视频列表:

func GetVideosByIDs(vIDs []int64) ([]*Video, error) {  
    var videos []*Video  
    result := GormDB.Find(&videos, vIDs)  
    return videos, result.Error  
}
条件查询:

db.Where("author_id = ?", 1).Find(&video)等价于: SELECT * FROM videos WHERE author_id = 1; (使用条件查询时,若传入的video中查询字段已经有值,条件不会覆盖,而是and合并)

根据视频作者id获取视频列表:

func GetVideosByAuthor(authorID int64) ([]*Video, error) {  
    var videos []*Video  
    result := GormDB.Where("author_id = ?", authorID).Find(videos)  
    return videos, result.Error  
}
排序:

db.Order("created_at desc").Order("xxx").Find(&video)等价于: SELECT * FROM video ORDER BY created_at desc, xxx;

按发布时间倒序排列视频列表:

func GetVideosByAuthor(authorID int64) ([]*Video, error) {  
    var videos []*Video  
    result := GormDB.Where("author_id = ?", authorID).Order("created_at desc").Find(videos)  
    return videos, result.Error  
}

按时间倒序获取获取指定时间前发布的30条视频列表:

func GetVideosDescByTimeLimit30(latestTime int64) ([]*Video, error) {  
    var videos []*Video  
  
    videoTime := time.UnixMilli(latestTime).Format("2006-01-02 15:04:05")  
    result := GormDB.Where("created_at < ?", videoTime).Order("created_at desc").Limit(30).Find(&videos)  
    return videos, result.Error  
}

更新

更新所有列:

db.Save(&video)方法会保存或更新video模型中的所有字段,即使字段是零值;当主键字段无值时,save会执行Create,否则执行Update。(Save方法不支持和Model连用)

单个列更新:

使用Update更新单个列时需要给定更新条件,否则会报ErrMissingWhereClause错误;当使用的Model方法且其具有主键值时,主键值将会被用于构造更新条件。

如video中id为1, db.Model(&video).Where("author_id = ?", 2).Update("title", "newTitle")方法更新id为1且author_id为2的视频的title列值为newTitle

多列更新:

Updates方法支持structmap[string]interface{},通过struct更新时,默认只更新其中的非零字段(可使用map或select来更新其它指定列)。

db.Model(&video).Updates(Video{author_id: 2, title: "newTitle"})

db.Model(&video).Updates(map[string]interface{}{"author_id": 2, "title": "newTitle"})

删除

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,删除一条记录时,删除记录需要指定主键。如果指定的值不包括主键,那么 GORM 会执行批量删除,它将删除所有匹配的记录。当执行不带任何条件的批量删除时,GORM将不会运行并返回ErrMissingWhereClause 错误

包含 gorm.DeletedAt字段(该字段也被包含在gorm.Model中)的模型具有软删除的功能。调用Delete时,GORM并不会从数据库中删除该记录,而是将该记录的DeleteAt设置为当前时间,而后的一般查询方法将无法查找到此条记录。Unscoped可用来来查询到被软删除的记录:db.Unscoped().Find(&video)、也可用来永久删除记录:db.Unscoped().Delete(&video)

删除指定user_id和video_id的全部favorite记录:

type Favorite struct {  
    gorm.Model  //软删除能力
    VideoID int64 `json:"video_id"`  
    UserID int64 `json:"user_id"`  
}

func DeleteFavorite(userID int64, videoID int64) error {  
    return db.Where("user_id = ? AND video_id = ?", userID, videoID).Delete(&Favorite{}).Error  //删除所匹配记录
}