Go ORM:GORM框架

397 阅读7分钟

「本文正在参加金石计划

前言

  1. 什么是GORM

    先来说ORM。简单来说可以理解为是用来描述需要保存的对象与数据库表的映射关系。而GORM是Go的高效的ORM架构。

  2. Hook函数⚠️

    • 什么是钩子(Hook)函数

      钩子,就是系统在发消息的时候你突然要截取,并进行一些操作。比如说你看到你喜欢的人跟别的异性在传纸条,而你在他们之间,因此他们在扔纸条的时候,你可以突然拦截下来,先看看,或者再修改一下再传出去。这就是钩子🪝函数。

    • 在GORM里的Hooks

      GORM在CRUD操作之前或之后都会运行Hook函数。由于在GORM中,所有的写操作都是默认开启事务的,因此要是在内部产生错误,后续的操作将会被中断,并且当前事务将会被回滚。 Hook函数的类型必须是func(*gorm.DB) error

  3. 联想——Associations⚠️

    通俗来说就是,A属于B的这种联系就叫联想,这种联系可以是一对一、一对多、多对多。当对象被CreateUpdate的时候,GORM将会自动保留一份联想并且UpsertUpdateInsert)。

  4. 什么是CRUD操作?

    增加(Create)、读取(Read)、更新(Update)、删除(Delete)


Hello GORM

  1. 最简单的启动

    1. 下载GORM包 ——go get -u gorm.io/gorm

    2. 下载GORM的MySQL驱动器 ——gorm.io/driver/mysql

    3. 启动基础框架

      //创建DSN
      dsn := "%s:%s@tcp(%s)%s?charset=%s&parseTime=%t&loc=Local"
      dsn = fmt.Sprintf(dsn, "root""1234""127.0.0.1:3306""schema""utf8", True)
      //获取mysql会话
      dialect := mysql.Open(dsn)
      //创建DB链接
      db, err := gorm.Open(dialect)
      if err != nil{
       panic("Failed to connect database")
      }
      defer db.Close()
      
  2. 创建数据表的关联模型(Model)

    type User struct {
    ID   uint32 `json:"id" gorm:"primary_key"`
    Name string `json:"name" gorm:"default:lyf"`
    Age  uint   `json:"age" gorm:"default:18"`
    Role string `json:"role" gorm:"default:user"`
    }
    
    • 默认设置

      1. 表名为结构体的名字转换为小写驼峰命名格式并加s,以上结构体的表名为users

      2. 列名通常为属性名转换为小写驼峰命名格式,以上ID对应的列名为id

      3. ID通常为主键,CreatedAt、UpdatedAt与DeletedAt分别为创建时、更新时与删除时的时间戳。(DeleteAt通常用于软删除

      // GORM定义了gorm.Model,用户可以直接将它嵌入自己的模型里
      type Model struct {
      ID        uint           `gorm:"primaryKey"`
      CreatedAt time.Time
      UpdatedAt time.Time
      DeletedAt gorm.DeletedAt `gorm:"index"`
      }
      
      type UserInfo struct{
       *gorm.Model
       Name string
      }
      
    • 默认的配置不和胃口?自定义配置!

      1. 设置主键与列名 —— GORM Tag

        • 主键:gorm:"primaryKey"
        • 列名:gorm:"column:columnName"
      2. 设置表名

        • 在编写模型的时候设置

          // 这里是实现了gorm.Tabler接口,方法类型是func() string
          func (u User) TableName() string{
          return "usr"
          }
          

          注意⚠️:此方法只适用于静态的表名,若需要动态生成表名需要用Scopes

        • 在执行SQL的时候设置db.Table("usr")

      3. 设置时间戳

        省流:可通过gorm:"autoUpdateTime:false"gorm:"autoCreateTime:false"关闭自动设置


CRUD

user := User{Name: "catlinie", Age:21}

  • 通用

    • GORM的写操作(create/update/delete)默认会开启事务。

    • 零值(0, false, "")通常会被GORM屏蔽,但是用map作为参数的时候GORM会传入所有的k-v(包括空值)。

    • 所有操作都会返回DB的指针,因此可以进行迭代操作。

    • DB会保存两个常用属性——一是Error;一是RowsAffected。前者表示的是操作执行后返回的错误;后者表示当前操作影响的列的数量。

    • 查询与删除操作默认情况下禁止全局操作,为了全局刷新你需要:

      1. Where里面添加总是true的表达式,如1=1

        db.Model(&User{}).Where("1 = 1").Delete(User{Age: 18})

      2. 用原生态的SQL语句

        db.Exec("UPDATE usr SET age = ?", 18)

      3. AllowGlobalUpdate设置为true

        db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("age", 18)

  • 条件

    • Where

      • 传入string:db.Where("name = ?", "catlinie").First(&user)

        默认情况下,假如对象已经有主键值了,那么Where将会把主键值加入条件中

        user.ID = 18
        db.Where("id = ?", 1).First(&user)
        //// SELECT * FROM usr WHERE id = 1 and id = 18 ORDER BY id ASC LIMIT 1
        

        很明显,这会返回ErrRecordNotFound

      • 传入结构体、map和切片

        db.Where(&User{Name: "Jack", Age: 20}).First(&user)
        // SELECT * FROM usr WHERE name = "Jack" AND age = 20 ORDER BY id LIMIT 1;
        ​
        db.Where(map(string)interface{}{"age": 20}).Find(&user)
        // SELECT * FROM usr WHERE age = 20;
        ​
        db.Where([]int{20,21,22}).Find(&user)
        //SELECT * FROM usr WHERE id IN(20,21,22)
        
    • Select

      • 指定字段查询。可以用string、切片和SQL的筛选语句作为参数

        db.Select("name", "age").First(&user)

      • 用结构体提供需要查询的字段名

        type APIUser struct{
         Namestring`gorm:"column:name"`
         Ageuint`gorm:"column:age"`
        }
        ​
        db.Model(&User{}).Find(&APIUser{})
        // SELECT 'name', 'age' FROM 'usr' 
        
    • 其他

  • CRUD

    • Create

      1. 单个插入 db.Create(&user)

      2. 批量插入

        • 以切片作为参数

          var users := []User{{Name: "cat1"}, {Name: "cat2"}, {Name: "cat3"}}
          db.Create(&users)
          

          批量插入时会回填主键的值,并且Hooks也会执行!

        • 以map作为参数

          db.Model(&User{}).Create([]map[string]interface{}{
          {"Name""jinzhu_1""Age"18},
          {"Name""jinzhu_2""Age"20},
          })
          

          批量插入时不会回填主键的值,并且Hooks也不会执行!

    • Query

      1. 单个查询 —— 默认会添加Limit(1)条件

        // 获取第一个匹配的元素
        db.Take(&user)
        ​
        // 以下两个都会对主键进行排序
        // 获取首个匹配的元素
        db.First(&user)
        ​
        //获取最后一个匹配的元素
        db.Last(&user)
        

        注意⚠️

        • 当获取不到的时候会返回ErrRecordNotFound

        • FirstLast只有在用Model指定数据表或直接传入指针时才有用,假如使用Table指定数据表则需要使用Take

          db.First(&user)
          db.Model(&User{}).First(&user)
          ​
          //这个将不会被正确执行
          //db.Table("usr").First(&user)
          //这样才行
          db.Table("usr").Take(&user)
          
        • 在使用FirstLast的时候,假如参数的主键为空值,则会根据首个字段值进行排序

        • 假如只需要一条记录,请使用Take而不是Find,因为Find会查询整张表

      2. 查询所有 db.Find(&users)

    • Update

      • 一次性更新所有字段(包括没有被修改的字段)—— Save

        假如需要更新的对象未设置主键,则会执行Create

        newUser := User{Name: "Jessica", Age:"31"}
        db.Save(&newUser)
        // INSERT INTO `usr` (`name`,`age`) VALUES ("Jessica", 31)
        

        假如需要更新的对象有主键值,则会执行Update

        user.ID = 7
        user.Name = "Tom"
        db.Save(&user)
        // UPDATE `usr` SET `name`="Tom" WHERE `id` = 7
        

        不要用Model配合使用Save

      • 更新指定字段

        • 更新单个字段 —— Update

          由于GORM默认禁止全局操作,因此Update操作总会需要指定条件(包括当Model的参数有主键值的时候),否则将会抛出ErrMissingWhereClause

          db.Where("id = ?", 14).Update("age", 18)
          ​
          // user.ID = 7
          db.Model(&user).Update("age", 33)
          // UPDATE usr SET age=33 WHERE id=16;
          
        • 更新多个字段 —— Updates

          struct、map(不支持切片)作为Updates的参数,并进行多个字段的更新。

          // user的ID为7
          db.Model(&user).Updates(User{Name: "Jack", Age: 18})
          // UPDATE usr SET name='Jack', age=18 WHERE id = 7;
          ​
          db.Model(&user).Updates(map[string]interface{}{"name": "Amy", "age": 0})
          // UPDATE usr SET name='Any', age=0 WHERE id=16;
          
    • Delete

      • 删除的操作比较简单,一共分为三种情况

        1. 传入的参数有主键值 —— 主键值将会作为条件插入SQL语句

          db.Delete(&user) //user的ID为7

        2. 传入的参数没有主键值,但是有条件语句

          db.Where("id = ?", 7).Delete(&user)

        3. 传入的参数没有主键值也没有条件语句 —— 将会执行全局操作,删除所有数据(同1)

        注意⚠️:官方文档中提到获取被删除的数据,这项功能是不支持MySQL的!!!

        我:为什么我用MySQL无法获得与官方文档同样的结果?

        GORM成员之一:MySQL不支持

      • 软删除⚠️

        1. 什么是软删除? 平常我们理解删除就是将不要的数据直接丢弃,但是像我们的手机相册在我们删除图片的时候,它会放进回收站,此时的文件并不会直接消失,像这种就叫软删除。 在数据库中,我们会添加一个IsDel的字段,值为false则该字段为启用状态,反之则为禁用状态,此时为服务器提供了 “撤销” 的机会

        2. GORM里面怎么软删除?

          GORM提供了一个gorm.DeleteAt的类型,只要在Model中定义此类型的变量(也可以使用gorm.Model),那么将会自动获取软删除的操作。(此时数据库中的is_del的类型是DATETIME,否则会抛出Data truncated for column 'is_del' at row 1

          type User struct {
          Id    uint32 `json:"id" gorm:"primary_key"`
          Name  string `json:"name" gorm:"default:lyf"`
          Age   uint   `json:"age"`
          Role  string `json:"role" gorm:"default:user"`
          IsDel gorm.DeletedAt
          }
          

          软删除之后将无法用普通的查询操作,需要用Unscoped,并且将可以执行永久删除

          db.Unscoped().Where("id = 33").Take(&user)
          // SELECT * FROM usr WHERE id = 33;
          ​
          db.Unscoped().Delete(&user)
          // DELETE FROM usr WHERE id=33;
          

          用其他数据类型定义软删除字段