GO_gorm

gorm

  • 概念

    • 将GO中的结构体与数据库中的表相对应:

      数据表 --- 结构体

      数据行---结构体示例

      字段---结构体字段

    • 优缺点 

      • 提升开发效率
      • 牺牲执行效率
      • 牺牲灵活性
      • 弱化SQL能力
  • 环境准备

    • 安装

      • go get -u gorm.io/gorm
        //根据所用数据库执行相应的指令 go get -u gorm.io/driver/sqlite go get -u gorm.io/driver/mysql
    • 连接到数据库

      • func main() {
        // 使用 GORM 连接到 MySQL 数据库
        db, err := gorm.Open("mysql", "usr1:12345@tcp(*******:3306)/db1?charset=utf8mb4&parseTime=True&loc=Local")
        if err != nil {
        panic(err.Error())
        }
        fmt.Println("Connected to the database successfully!")
        }

 

    • 导入

      • //gorm导入
        go get gorm.io/gorm
        
        ​//终端下载mysql的驱动:
        go get gorm.io/driver/mysql

         

 

  • 基础

    • 模型

      • type User struct {
          ID           uint           // Standard field for the primary key
          Name         string         // A regular string field
          Email        *string        // A pointer to a string, allowing for null values
          Age          uint8          // An unsigned 8-bit integer
          Birthday     *time.Time     // A pointer to time.Time, can be null
          MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings
          ActivatedAt  sql.NullTime   // Uses sql.NullTime for nullable time fields
          CreatedAt    time.Time      // Automatically managed by GORM for creation time
          UpdatedAt    time.Time      // Automatically managed by GORM for update time
          ignored      string         // fields that aren't exported are ignored
        }
      • 具体数字类型如 uintstring和 uint8 直接使用。
      • 指向 *string 和 *time.Time 类型的指针表示可空字段。
      • 来自 database/sql 包的 sql.NullString 和 sql.NullTime 用于具有更多控制的可空字段。
      • CreatedAt 和 UpdatedAt 是特殊字段,当记录被创建或更新时,GORM 会自动向内填充当前时间。
    • 约定

      • 主键:

        • GORM 使用一个名为ID 的字段作为每个模型的默认主键
        • 也可以使用使用gorm的tag设置主键。
        • // 使用`AnimalID`作为主键
          type Animal struct {
            AnimalID int64 `gorm:"primary_key"`
            Name     string
            Age      int64
          }
      • 表名:

        • 表名默认就是结构体名称的复数。
        • 禁用默认表名的复数形式,如果置为 true,则 User 的默认表名是 user,如下:
        • func main() {//禁用默认表名的复数形式,如果置为 true,则 `User` 的默认表名是 `user`
              db.SingularTable(true)//
              db.AutoMigrate(&User{})
              fmt.Println("Connected to the database successfully!")
          }
      • 列名:

        • 默认:列名由字段名称进行下划线分割来生成:两个单词以上组成的字段会使用下划线分割。
        • 时间戳字段:
          • CreatedAt:字段的值将会是初次创建记录的时间。

            • db.Create(&user) // `CreatedAt`将会是当前时间
              
              // 可以使用`Update`方法来改变`CreateAt`的值
              db.Model(&user).Update("CreatedAt", time.Now())
          • UpdatedAt

            • db.Save(&user) // `UpdatedAt`将会是当前时间
              
              db.Model(&user).Update("name", "jinzhu") // `UpdatedAt`将会是当前时间
          • DeletedAt

            • db.Save(&user) // `UpdatedAt`将会是当前时间
              
              db.Model(&user).Update("name", "jinzhu") // `UpdatedAt`将会是当前时间
    • 预定义结构体

GORM提供了一个预定义的结构体,名为gorm.Model,其中包含常用字段:

      • // gorm.Model 的定义
        typeModel struct {
          ID        uint           `gorm:"primaryKey"`
          CreatedAt time.Time
          UpdatedAt time.Time
          DeletedAt gorm.DeletedAt `gorm:"index"`
        }
    • 高级选项

      • 嵌入结构体

        对于匿名字段,GORM 会将其字段包含在父结构体中,例如:
      • type Author struct {
          Name  string
          Email string
        }
        
        type Blog struct {
          Author
          ID      int
          Upvotes int32
        }
        // equals
        type Blog struct {
          ID      int64
          Name    string
          Email   string
          Upvotes int32
        }
      • 对于正常的结构体字段,你也可以通过标签 embedded 将其嵌入,例如:

      • type Author struct {
            Name  string
            Email string
        }
        
        type Blog struct {
          ID      int
          Author  Author `gorm:"embedded"`
          Upvotes int32
        }
        // 等效于
        type Blog struct {
          ID    int64
          Name  string
          Email string
          Upvotes  int32
        }
      • 并且,您可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀,例如:

      • type Blog struct {
          ID      int
          Author  Author `gorm:"embedded;embeddedPrefix:author_"`
          Upvotes int32
        }
        // 等效于
        type Blog struct {
          ID          int64
          AuthorName string
          AuthorEmail string
          Upvotes     int32
        }
    • 字段标签

      • 标签名说明
        column 指定 db 列名
        type 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not nullsizeautoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT
        serializer 指定将数据序列化或反序列化到数据库中的序列化器, 例如: serializer:json/gob/unixtime
        size 定义列数据类型的大小或长度,例如 size: 256
        primaryKey 将列定义为主键
        unique 将列定义为唯一键
        default 定义列的默认值
        precision 指定列的精度
        scale 指定列大小
        not null 指定列为 NOT NULL
        autoIncrement 指定列为自动增长
        autoIncrementIncrement 自动步长,控制连续记录之间的间隔
        embedded 嵌套字段
        embeddedPrefix 嵌入字段的列名前缀
        autoCreateTime 创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
        autoUpdateTime 创建/更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli
        index 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情
        uniqueIndex 与 index 相同,但创建的是唯一索引
        check 创建检查约束,例如 check:age > 13,查看 约束 获取详情
        <- 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限
        -> 设置字段读的权限,->:false 无读权限
        - 忽略该字段,- 表示无读写,-:migration 表示无迁移权限,-:all 表示无读写迁移权限
        comment 迁移时为字段添加注释
  • CURD

    • create

      • Create 方法:创建新记录并将其保存到数据库中

        • func main() {
            //连接数据库...

            user := User{Name: "John Doe", Email: "john@example.com"}
            db.Create(&user)

          }

      • BatchInsert 方法:批量插入多个记录
        • users := []User{
              {Name: "John Doe", Email: "john@example.com"},
              {Name: "Jane Smith", Email: "jane@example.com"},
          }
          db.Create(&users)
      • FirstOrCreate 方法:查找符合条件的记录,如果不存在则创建新记录

        • user := User{Name: "John Doe", Email: "john@example.com"}
          db.FirstOrCreate(&user, User{Name: "John Doe"})

      • Save 方法:保存记录到数据库,如果记录不存在则创建新记录

        • user := User{Name: "John Doe", Email: "john@example.com"}
          db.Save(&user)

      • Omit 方法:忽略指定字段,在保存记录时不更新这些字段

        • db.Omit("Email").Create(&user)

      • Assign 方法:为记录的字段赋值,但不保存到数据库

        • db.Model(&user).Assign(User{Email: "newemail@example.com"}).FirstOrCreate(&user)

    • Read(读取/查询)

      • 一般查询

        • Find 方法:查询所有符合条件的记录。
          var users []User
          db.Find(&users)


          First 方法:查询符合条件的第一条记录。
          var user User
          db.First(&user)


          Last 方法:查询符合条件的最后一条记录。
          var user User
          db.Last(&user)


          Take 方法:随机获取一条记录。
          var user User
          db.Take(&user)


          Find 方法(带条件):根据条件查询符合条件的记录。
          var users []User
          db.Where("age > ?", 8).Find(&users)


          Find 方法(多个条件):根据多个条件查询符合条件的记录。
          var users []User
          db.Where("age > ? AND name LIKE ?", 8, "%Doe%").Find(&users)


          First 方法(带条件):根据条件查询符合条件的第一条记录。
          var user User
          db.Where("name = ?", "John Doe").First(&user)


          Last 方法(带条件):根据条件查询符合条件的最后一条记录。
          var user User
          db.Where("name = ?", "John Doe").Last(&user)


          Take 方法(带条件):根据条件随机获取一条记录。
          var user User
          db.Where("age > ?", 8).Take(&user)


          Find 方法(分页查询):根据分页条件查询记录。
          var users []User
          db.Offset(0).Limit(5).Find(&users)


          Select 方法:选择特定的字段进行查询。
          var names []string
          db.Model(&User{}).Select("name").Find(&names)


          Pluck 方法:从查询结果中提取特定字段值。
          var names []string
          db.Model(&User{}).Pluck("name", &names)

      • 高级查询

        • Where 方法:设置查询条件。
          var users []User
          db.Where("age > ?", 8).Where("name LIKE ?", "%Doe%").Find(&users)
          
          Or 方法:设置 OR 条件。
          var users []User
          db.Where("age > ?", 8).Or("name LIKE ?", "%Doe%").Find(&users)
          
          Not 方法:设置 NOT 条件。
          var users []User
          db.Not("age", 5).Find(&users)
          
          Order 方法:设置结果排序方式。
          var users []User
          db.Order("age desc").Find(&users)
          
          Limit 方法:设置结果返回数量的限制。
          var users []User
          db.Limit(0).Find(&users)
          
          Offset 方法:设置结果的偏移量。
          var users []User
          db.Offset(5).Find(&users)
          
          Select 方法:选择特定的字段进行查询。
          var names []string
          db.Model(&User{}).Select("name").Find(&names)
          
          Joins 方法:执行联接查询。
          var users []User
          db.Joins("JOIN profiles ON users.id = profiles.user_id").Find(&users)
          
          Group 方法:按指定字段进行分组。
          var results []map[string]interface{}
          db.Model(&User{}).Select("age, count(*) as count").Group("age").Scan(&results)
          
          Having 方法:设置分组后的条件。
          var results []map[string]interface{}
          db.Model(&User{}).Select("age, count(*) as count").Group("age").Having("count > ?", ).Scan(&results)
      • Struct & Map查询

        • // Struct
          db.Where(&User{Name: "李四", Age: 20}).First(&user)
          //// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;
          
          // Map
          db.Where(map[string]interface{}{"name": "李四", "age": 20}).Find(&user)
          //// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
          
          
          // 主键的切片
          db.Where([]int64{1, 2}).Find(&users)
          //// SELECT * FROM users WHERE id IN (1, 2);
    • update

      • 
        

        Save 方法:保存记录到数据库,包括更新已存在的记录。
        user := User{Name: "John Doe", Email: "john@example.com"}
        db.Save(&user)

        
        

        Update 方法:更新指定的字段值。
        db.Model(&user).Update("Email", "newemail@example.com")

        
        

        Updates 方法:更新指定字段的值,并将更改保存到数据库。
        db.Model(&user).Updates(User{Email: "newemail@example.com"})

        
        

        UpdateColumn 方法:更新指定字段的值,但不更新其他字段。
        db.Model(&user).UpdateColumn("Email", "newemail@example.com")

        
        

        UpdateColumns 方法:更新指定字段的值,但不更新其他字段。
        db.Model(&user).UpdateColumns(map[string]interface{}{"Email": "newemail@example.com", "Name": "John Doe"})

        
        

        Select 方法:更新指定字段的值。
        db.Model(&user).Select("Email").Updates(User{Email: "newemail@example.com"})

        
        

        Omit 方法:忽略指定字段,在更新记录时不更新这些字段。
        db.Omit("Email").Save(&user)

        
        

        Assign 方法:为记录的字段赋值,但不保存到数据库。
        db.Model(&user).Assign(User{Email: "newemail@example.com"}).Save(&user)

        
        
    • delete

      • Delete 方法:删除指定的记录。
        db.Delete(&user)

        Delete 方法(带条件):根据条件删除符合条件的记录。
        db.Where("name = ?", "John").Delete(&User{})

        Unscoped 方法:永久删除记录,包括软删除的记录。
        db.Unscoped().Delete(&user)

        Delete 方法(批量删除):批量删除符合条件的记录。
        db.Where("age < ?", 18).Delete(&User{})

        Delete 方法(根据主键):根据主键删除记录。
        db.Delete(&User{}, 1)

        Truncate 方法:清空表中的所有记录。
        db.Exec("TRUNCATE TABLE users")

  •  关联

    FOREIGN KEY: 一个表中的 FOREIGN KEY 指向另一个表中的 UNIQUE KEY(唯一约束的键)。

  REFERENCES: 确定外键指向的另外一个表的字段

    • 多对一

      • 默认情况

      • // `User` 属于 `Company`,`CompanyID` 是外键
        type User struct {
          gorm.Model
          Name      string
          CompanyID int        // 默认外键
          Company   Company
        }
        
        type Company struct {
          ID   int
          Name string
        }
        
        ompanyID 被隐含地用来在 User 和 Company 之间创建一个外键关系, 因此必须包含在 User 结构体中才能填充 Company 内部结构体
      • 重写外键

      • type UserB struct {
            gorm.Model
            Name       string
            CompanyIDS int
            Company    Company `gorm:"foreignKey:CompanyIDS"`
        }
        
        type Company struct {
            ID         int
            CardNumber int `gorm:"unique"`
            Name       string
        }

        指定
        CompanyIDS 作为外键, 和Company 中的ID对应
      • 重写引用

      • type User struct {
          gorm.Model
          Name      string
          CompanyID string
          Company   Company `gorm:"references:Code"` // 使用 Code 作为引用
        }
        
        type Company struct {
          ID   int
          Code string
          Name string
        }
        
        User 中CompanyID 作为外键,指定与 comapny中的code相对应
    • 一对一

      • // User 有一张 CreditCard,UserID 是外键
        type User struct {
          gorm.Model
          CreditCard CreditCard
        }
        
        type CreditCard struct {
          gorm.Model
          Number string
          UserID uint
        }
        亦可以使用
        foreignKey和references
    • 一对多

      • // User 有多张 CreditCard,UserID 是外键
        type User struct {
          gorm.Model
          CreditCards []CreditCard
        }
        
        type CreditCard struct {
          gorm.Model
          Number string
          UserID uint
        }
    • 多对多

      • // User 拥有并属于多种 language,`user_languages` 是连接表
        type User struct {
          gorm.Model
          Languages []Language `gorm:"many2many:user_languages;"`
        }
        
        type Language struct {
          gorm.Model
          Name string
        }
  • 事务

  实际上gorm是每执行一个命令函数就向数据库发送一条SQL,事务的功能是通过数据库保证的,gorm库并没有对应的逻辑实现事务。

  当我们执行db.Begin()时,gorm库会帮我们发送“Start Transaction”这条SQL语句到数据库中执行,在我们执行相应的Select、Update命令时也会生成对应的SQL语句并发送到数据库中执行。最后,当我们执行tx.Commit()时,gorm库会发送“COMMIT”到数据库中执行,从而完成整个数据库的运行。

    • 手动事务(Manual Transactions)

  手动事务需要显式调用 Begin 开启事务,通过 Commit 提交事务,或 Rollback 回滚事务,全程由开发者控制事务生命周期,适合复杂业务逻辑(如多步操作需手动判断是否提交)。

      • 基本流程

开启事务:调用 db.Begin() 获取事务对象 tx *gorm.DB。
执行操作:通过事务对象 tx 执行数据库操作(如 Create、Update、Delete)。
判断结果:若所有操作成功,调用 tx.Commit() 提交;若失败,调用 tx.Rollback() 回滚。

      • 示例

      • // 定义模型
        type User struct {
          gorm.Model
          Name  string
          Balance int // 余额
        }
        
        // 手动事务实现转账
        func transfer(db *gorm.DB, fromID, toID, amount int) error {
          // 1. 开启事务
          tx := db.Begin()
          if tx.Error != nil {
            return tx.Error // 开启事务失败(如数据库连接问题)
          }
        
          // 2. 执行事务内操作
          var fromUser, toUser User
          // 查A的账户
          if err := tx.First(&fromUser, fromID).Error; err != nil {
            tx.Rollback() // 查询失败,回滚
            return err
          }
          // 查B的账户
          if err := tx.First(&toUser, toID).Error; err != nil {
            tx.Rollback()
            return err
          }
          // A的余额不足,回滚
          if fromUser.Balance < amount {
            tx.Rollback()
            return fmt.Errorf("余额不足")
          }
          // A扣钱
          if err := tx.Model(&fromUser).Update("balance", fromUser.Balance - amount).Error; err != nil {
            tx.Rollback()
            return err
          }
          // B加钱
          if err := tx.Model(&toUser).Update("balance", toUser.Balance + amount).Error; err != nil {
            tx.Rollback()
            return err
          }
        
          // 3. 所有操作成功,提交事务
          return tx.Commit().Error
        }
    • 自动事务(Auto Transactions)

        自动事务通过 db.Transaction 方法封装事务逻辑,传入一个函数作为事务内的操作,Gorm 会自动开启事务、执行函数、根据函数返回值决定提交或回滚,简化代码。

      • // 自动事务实现转账
        func transferAuto(db *gorm.DB, fromID, toID, amount int) error {
          // 调用 db.Transaction,传入事务内操作的函数
          return db.Transaction(func(tx *gorm.DB) error {
            var fromUser, toUser User
            // 查A的账户
            if err := tx.First(&fromUser, fromID).Error; err != nil {
              return err // 返回错误,Gorm 自动回滚
            }
            // 查B的账户
            if err := tx.First(&toUser, toID).Error; err != nil {
              return err
            }
            // 余额不足,返回错误
            if fromUser.Balance < amount {
              return fmt.Errorf("余额不足")
            }
            // A扣钱
            if err := tx.Model(&fromUser).Update("balance", fromUser.Balance - amount).Error; err != nil {
              return err
            }
            // B加钱
            if err := tx.Model(&toUser).Update("balance", toUser.Balance + amount).Error; err != nil {
              return err
            }
        
            return nil // 无错误,Gorm 自动提交
          })
        }
    • 嵌套事务(Nested Transactions)

        嵌套事务指在一个事务内部开启另一个事务,外层事务称为“父事务”,内层称为“子事务”。Gorm 通过数据库的 SAVEPOINT(保存点)实现嵌套事务,支持内层事务独立回滚而不影响外层。

      • // 嵌套事务示例
        func nestedTransaction(db *gorm.DB) error {
          return db.Transaction(func(parentTx *gorm.DB) error {
            // 父事务:创建用户A
            userA := User{Name: "Alice", Balance: 1000}
            if err := parentTx.Create(&userA).Error; err != nil {
              return err // 父事务回滚
            }
        
            // 开启子事务(嵌套事务)
            if err := parentTx.Transaction(func(childTx *gorm.DB) error {
              // 子事务:创建用户B
              userB := User{Name: "Bob", Balance: 500}
              if err := childTx.Create(&userB).Error; err != nil {
                return err // 子事务回滚(仅回滚自身操作,不影响父事务的userA)
              }
        
              // 子事务内主动回滚(例如业务判断失败)
              return fmt.Errorf("子事务主动回滚") // 仅子事务回滚,userB 不会被创建
            }); err != nil {
              fmt.Printf("子事务失败:%v,但父事务可继续\n", err)
            }
        
            // 父事务继续执行:更新用户A的余额
            return parentTx.Model(&userA).Update("balance", 2000).Error
          })
        }

         

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 

posted @ 2025-10-29 13:53  直至成伤  阅读(8)  评论(0)    收藏  举报