GORM连接数据库原理与实践 | 青训营

224 阅读6分钟

一.简介与数据介绍

1. GORM

首先了解ORM:Object-Relationl Mapping,即对象关系映射,这里的Relationl指的是关系型数据库。

它的作用是在关系型数据库和对象之间作一个映射,这样,我们在具体的操作数据库的时候,就不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了。Gorm框架能够通过Go语言结构体来定义数据库模型,它自动处理了数据库表和结构体之间的映射,允许通过操作结构体来进行数据查询、插入、更新和删除等操作,而无需直接编写SQL语句。GORM框架的特点:

  • 1.全功能ORM;
  • 2.关联(包含一个,包含多个,属于,多对多,多种包含);
  • 3.Callbacks(创建/保存/更新/删除/查找之前/之后),后面会提到相应的钩子函数;
  • 4.预加载;
  • 5.事务;
  • 6.自动迁移;
  • 7.日志;
  • 8.可扩展,编写基于GORM回调的插件;

2. Model结构定义

GORM通过定义结构体来定义GORM Model,同时我们需要实现TableName接口来帮助GORM找到数据库中对应表。否则,表名应按照GORM默认定义为结构体名+s,不然会报错找不到对应表。本文中我们使用User结构,定义如下:

type User struct {  
    Id int64 `gorm:"column:id"`  
    Name string `gorm:"column:name"`  
    Avatar string `gorm:"column:avatar"`  
    Level int `gorm:"column:level"`  
    CreateTime time.Time `gorm:"column:create_time"`  
    ModifyTime time.Time `gorm:"column:modify_time"`  
}  
  
func (User) TableName() string {  
    return "user"  
}

在定义Gorm model时可以使用各种标签(tags)和方法来为模型添加不同的操作和功能:

  • 表名和列名映射:使用gorm:"column:name"标签来映射模型字段到数据库表的列名;
  • 主键设置:使用gorm:"primaryKey"标签来指定主键字段;
  • 自增字段:使用gorm:"autoIncrement"标签来指定自增字段;
  • 唯一索引:使用gorm:"unique"标签来为字段添加唯一索引;
  • 默认值:使用gorm:"default:value"标签来为字段指定默认值;
  • 关联关系:使用gorm:"foreignKey"、gorm:"references"和其他关联关系标签来定义模型之间的关联关系;
  • 自定义方法:你可以在模型上定义自己的方法,用于执行特定的数据库操作或业务逻辑;
  • 查询作用域:使用scope方法创建查询作用域,以便在查询中复用特定的条件;
  • 忽略字段:使用gorm:"-"标签来忽略模型中的某些字段,使其不参与数据库操作;
  • 表名设置:使用gorm:"tableName"标签来指定数据库表的名称;
  • 软删除设置:使用Deleted gorm.DeletedAt标志字段,表示对该模型进行删除操作时,不直接进行物理删除,删除时并不会从数据库中移除数据记录,而是在记录中添加一个标志字段(deleted_at),表示该记录已被删除,在查询时一般的查询语句会直接忽略软删除的数据进行查询,同时也可以使用Unscoped不忽略软删除数据进行查询;
  • 结构体嵌套:通过在模型中嵌套其他结构体来表示更复杂的数据库关系。

二. 数据操作增删改查

1.数据查询

常用到以下函数:

  • Find 函数:用于查询满足条件的多条数据记录,查询不到数据不返回错误;
  • First 函数:用于查询满足条件的第一条数据记录,查询不到数据则返回ErrRecordNotFound容易造成线上问题,换用where,未找到返回空数组;
  • Take 函数:用于随机获取一条数据记录;
  • Where 函数:用于添加条件查询;
  • Order 函数:用于指定查询结果的排序方式;
  • Limit 函数:用于限制查询结果的数量;
  • Offset 函数:用于设置查询结果的偏移量;
  • Select 函数:用于选择要查询的字段;
  • Joins 函数:用于关联查询。

注意:当使用结构体查询时,GORM只会对非零值进行查询。若需要查询零值需要使用map来构造。

下面的代码中使用了几种不同的查询模式,包括多条数据查询、复合查询等:

func (*UserDao) QueryUserById(id int64) (*User, error) {  
    var user User  
    err := db.Where("id = ?", id).Find(&user).Error  
    if err == gorm.ErrRecordNotFound {  
        return nil, nil  
    }  
    if err != nil {  
        util.Logger.Error("find user by id err:" + err.Error())  
        return nil, err  
    }  
    return &user, nil  
}

var users []*User  
err := db.Where("id in (?)", ids).Find(&users).Error

db.Where("name IN ?", []string{"Jerry", "Tom"}).Find(&users)

db.Where("name=? AND id>=?", "Jerry", "2").Find(&users)

db.Where(&User{Name: "Jerry", Id: 2}).Find(&users)

db.Where(map[string]interface{}{"name": "Jerry", "id": 2}).Find(&users)

简单实例:

image.png

image.png

2.数据创建

常用函数:

  • Create函数:用于创建新的数据记录,需要使用指针传递;
  • Save函数:用于创建或更新数据记录,根据主键(或唯一索引)是否存在来决定操作;
  • FirstOrCreate函数:查找第一条满足条件的记录,如果不存在则创建;

其中:Save函数当主键不存在时进行Insert,而存在时则进行Update.

func (*UserDao) CreateUser(user *User) error {  
    if err := db.Create(user).Error; err != nil {  
        util.Logger.Error("insert post err:" + err.Error())  
        return err  
    }  
    return nil  
}

user := &repository.User{  
    Id: 3,  
    Name: "Jame",  
    Level: 3,  
    CreateTime: time.Now(),  
    ModifyTime: time.Now(),  
 }  
error := userDao.CreateUser(user)

image.png

3.数据删除

  • Deleted gorm.DeletedAt标志字段,设置模型删除模式为软删除;
  • Delete 函数:用于删除数据记录;
  • Where 函数 + Delete 函数:使用 Where 函数指定删除的条件,然后调用 Delete 函数删除数据;
  • Unscoped 函数 + Delete 函数:使用 Unscoped 函数删除包括软删除在内的记录;
  • Model 函数 + Where 函数 + Delete 函数:通过 Model 函数指定模型和条件,然后使用 Delete 函数删除数据。

image.png

4.数据更新

  • Model 函数 + Update 函数:通过Model函数指定表名和条件,然后使用 Update函数更新单列数据;
  • Updates函数:更新多列数据,使用结构体更新时只会更新非零值,如果要更新零值需要使用map传递或者用Select选择字段;
  • Save函数:用于创建或更新数据记录,根据主键(或唯一索引)是否存在来决定操作;
  • UpdatesColumns函数:用于更新指定列的数据记录。

更新的过程中可以用到Where等函数以增加条件,注意此处上课强调,必须在Update函数之前增加条件,否则更新已经执行,条件无效。

db.Model(&User{Id: id}).Where("name=?", "Tom").Update("name", "Jame")

image.png

5.事务

  • Begin 函数:用于开始一个事务;
  • Commit 函数:用于提交事务,将操作永久保存到数据库;
  • Rollback 函数:用于回滚事务,取消之前的操作;
  • Transaction 函数:执行事务操作。在这个函数中执行数据库操作,并根据每个操作的错误情况来决定是否提交事务或回滚事务。如果返回的错误为 nil,事务将被提交;如果返回的错误不为 nil,事务将被回滚,用于自动提交事务,避免漏写CommitRollback。 通过db.Transaction函数实现事务,如果闭包函数返回错误,则回滚事务:
db.Transaction(func(tx *gorm.DB) error {
  // 在事务中执行一些 db 操作(从这里开始,应该使用 'tx' 而非 'db')
  if err := tx.Create(&User{Name: "Lili"}).Error; err != nil {
    // 返回任何错误都会回滚事务
    return err
  }
  if err := tx.Create(&User{Name: "xiaoming"}).Error; err != nil {
    return err
  }
  // 返回 nil 提交事务
  return nil
})

6.钩子函数

Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。一般命名为BeforeCreateAfterUpdate,可进行如验证、审计、日志记录、触发器等操作。

func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
	if u.Id == 1 {
		return errors.New("无法删除Jerry")
	}
	return nil
}