• gorm的简单操作


    1. 什么是orm

    ORM全称是:Object Relational Mapping(对象关系映射),其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来。举例来说就是,我定义一个对象,那就对应着一张表,这个对象的实例,就对应着表中的一条记录。
    对于数据来说,最重要最常用的是表:表中有列, orm就是将一张表映射成一个类,表中的列映射成类中的一个类。java 、python,但是针对go语言而言,struct,就是列如何映射,是因为列可以映射成struct中的类型,int->int,但是有另一个问题? 就是数据库中的列具备很好的描述性,但是struct有tag(标签)。执行sql, 需要我们有足够的sql语句基础、需要我们懂得不同的数据的sql

    2. 常用orm

    个人而言,不用太去纠结应该选择哪一个orm框架,但是实际上你用熟悉了一个,其他的orm迁移成本很低,我们选个一个star数量最高的一定不会有错,这些差异也不会很大sql语言远比orm重要的多
    https://github.com/go-gorm/gorm
    https://github.com/facebook/ent
    https://github.com/jmoiron/sqlx
    https://gitea.com/xorm/xorm/src/branch/master/README_CN.md
    https://github.com/didi/gendry/blob/master/translation/zhcn/README.md

    gorm文档

    https://gorm.io/zh_CN/docs/create.html
    
    • 1

    3. orm的优缺点

    优点:

    1. 提高了开发效率。
    2. 屏蔽sql细节。可以自动对实体Entity对象与数据库中的Table进行字段与属性的映射;不用直接SQL编码
    3. 屏蔽各种数据库之间的差异

    缺点:

    1. orm会牺牲程序的执行效率和会固定思维模式
    2. 太过依赖orm会导致sql理解不够
    3. 对于固定的orm依赖过重,导致切换到其他的orm代价高

    4. 如何正确看待orm和sql之间的关系

    1. sql为主,orm为辅
    2. orm主要目的是为了增加代码可维护性和开发效率

    gorm连接数据库

    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    )
    
    func main() {
    	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
    	//loc 本地时区
    	//想要正确的处理 time.Time ,您需要带上 parseTime 参数
    	dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    	_, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    	if err != nil {
    		panic(err)
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    注意:想要正确的处理 time.Time ,您需要带上 parseTime 参数, (更多参数) 要支持完整的 UTF-8 编码,您需要将 charset=utf8 更改为 charset=utf8mb4
    MySQL 驱动程序提供了 一些高级配置 可以在初始化过程中使用,例如:

    db, err := gorm.Open(mysql.New(mysql.Config{
      DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
      DefaultStringSize: 256, // string 类型字段的默认长度
      DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
      DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
      DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
      SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
    }), &gorm.Config{})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    //设置全局的logger,这个logger在我们执行每个sql语句的时候会打印每一行sql

    日志

    Gorm 有一个 默认 logger 实现,默认情况下,它会打印慢 SQL 和错误

    Logger 接受的选项不多,您可以在初始化时自定义它,例如:

    newLogger := logger.New(
      log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
      logger.Config{
        SlowThreshold:              time.Second,   // Slow SQL threshold
        LogLevel:                   logger.Silent, // Log level
        IgnoreRecordNotFoundError: true,           // Ignore ErrRecordNotFound error for logger
        ParameterizedQueries:      true,           // Don't include params in the SQL log
        Colorful:                  false,          // Disable color
      },
    )
    
    // Globally mode
    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
      Logger: newLogger,
    })
    
    // Continuous session mode
    tx := db.Session(&Session{Logger: newLogger})
    tx.First(&user)
    tx.Model(&user).Update("Age", 18)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    日志级别

    GORM 定义了这些日志级别:Silent、Error、Warn、Info

    db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{
      Logger: logger.Default.LogMode(logger.Silent),
    })
    
    • 1
    • 2
    • 3

    Debug

    Debug 单个操作,将当前操作的 log 级别调整为 logger.Info

    db.Debug().Where("name = ?", "jinzhu").First(&User{})
    
    • 1

    自定义 Logger

    参考 GORM 的 默认 logger 来定义您自己的 logger

    Logger 需要实现以下接口,它接受 context,所以你可以用它来追踪日志

    type Interface interface {
        LogMode(LogLevel) Interface
        Info(context.Context, string, ...interface{})
        Warn(context.Context, string, ...interface{})
        Error(context.Context, string, ...interface{})
        Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    创建表

    _ = db.AutoMigrate(&Product{}) //此次应该有sql语句跟日志级别有关系
    
    • 1

    通过NullString解决不能更新零值的问题
    整体源码

    package main
    
    import (
    	"database/sql"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type Product struct {
    	gorm.Model
    	//Code  string
    	Code  sql.NullString
    	Price uint
    }
    
    func main() {
    	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
    	//loc 本地时区
    	//想要正确的处理 time.Time ,您需要带上 parseTime 参数
    	dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
    		logger.Config{
    			SlowThreshold: time.Second, // 慢SQL阈值
    			LogLevel:      logger.Info, // 日志级别
    			// 忽略记录器的ErrRecordNotFound错误 是否忽略 record not found 错误,默认为 false,如果设置为 true,则在查询结果为空时不会打印 record not found 错误信息
    			IgnoreRecordNotFoundError: false,
    			ParameterizedQueries:      false, // 不要在SQL日志中包含参数
    			Colorful:                  true,  // 禁言彩色打印
    		},
    	)
    
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    
    	//设置全局的logger,这个logger在我们执行每个sql语句的时候会打印每一行sql
    
    	//定义一个表结构,将表结构直接生成对应的表---migrations(迁移)
    	//迁移
    	_ = db.AutoMigrate(&Product{}) //此次应该有sql语句
    
    	// 新增
    	//db.Create(&Product{Code: "D42", Price: 100})
    	db.Create(&Product{Code: sql.NullString{"D42", true}, Price: 100})
    	// 查询
    	var product Product
    	db.First(&product, 1) // 根据整型主键查找
    	db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
    
    	// 更新一个值 - 将 product 的 price 更新为 200
    	db.Model(&product).Update("Price", 200)
    	// Update - 更新多个字段
    	//db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
    	db.Model(&product).Updates(Product{Price: 200, Code: sql.NullString{String: "", Valid: true}}) // 仅更新非零值字段
    	//db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
    
    	// Delete - 删除 product
    	//并没有执行deleta语句,而是逻辑删除
    	//db.Delete(&product, 1)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    解决仅更新非零值字段的方法有两种

    	/**
    	1 将string设置为*string
    	2 使用sql的NULLxxx来结局
    	*/
    	empty := ""
    	db.Model(&User{ID: 1}).Updates(User{Email: &empty})
    	//updates语句不会更新零值,但update语句会更新
    	//db.Model(&User{ID: 1}).Update("Name", "")
    	//db.Model(&User{ID: 1}).Updates(User{Name: ""})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    约定

    GORM 倾向于约定优于配置 默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间
    如果您遵循 GORM 的约定,您就可以少写的配置、代码。 如果约定不符合您的实际要求,GORM 允许你配置它们

    嵌入结构体

    对于匿名字段,GORM 会将其字段包含在父结构体中,例如:

    type User struct {
      gorm.Model
      Name string
    }
    // 等效于
    type User struct {
      ID        uint           `gorm:"primaryKey"`
      CreatedAt time.Time
      UpdatedAt time.Time
      DeletedAt gorm.DeletedAt `gorm:"index"`
      Name string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    对于正常的结构体字段,你也可以通过标签 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    并且,您可以使用标签 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    字段标签

    声明 model 时,tag 是可选的,GORM 支持以下 tag: tag 名大小写不敏感,但建议使用 camelCase 风格–>去官网看源码

    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type User struct {
    	UserID        uint `gorm:"primarykey"`
    	Name string `gorm:"column:user_name;type:varchar(50);index:idx_user_name;unique;default:'bobby'"`
    }
    
    func main() {
    	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
    	//loc 本地时区
    	//想要正确的处理 time.Time ,您需要带上 parseTime 参数
    	dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢SQL阈值
    			LogLevel:                  logger.Info, // 日志级别
    			IgnoreRecordNotFoundError: false,
    			ParameterizedQueries:      false, // 不要在SQL日志中包含参数
    			Colorful:                  true,  // 禁言彩色打印
    		},
    	)
    
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    
    	_ = db.AutoMigrate(&User{}) //此次应该有sql语句
    		db.Create(&User{})
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    在这里插入图片描述

    通过create方法插入记录

    user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
    
    result := db.Create(&user) // 通过数据的指针来创建
    
    user.ID             // 返回插入数据的主键
    result.Error        // 返回 error
    result.RowsAffected // 返回插入记录的条数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们还可以使用 Create() 创建多项记录:

    users := []*User{
        User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
        User{Name: "Jackson", Age: 19, Birthday: time.Now()},
    }
    
    result := db.Create(users) // 传递切片以插入多行数据
    
    result.Error        // 返回 error
    result.RowsAffected // 返回插入记录的条数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    package main
    
    import (
    	"database/sql"
    	"fmt"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type User struct {
    	ID           uint
    	Name         string
    	Email        *string //使用指针的方法解决空字符串
    	Age          uint8
    	Birthday     *time.Time
    	MemberNumber sql.NullString
    	ActivatedAt  sql.NullTime
    	CreatedAt    time.Time
    	UpdatedAt    time.Time
    }
    
    func main() {
    	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
    	//loc 本地时区
    	//想要正确的处理 time.Time ,您需要带上 parseTime 参数
    	dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢SQL阈值
    			LogLevel:                  logger.Info, // 日志级别
    			IgnoreRecordNotFoundError: false,
    			ParameterizedQueries:      false, // 不要在SQL日志中包含参数
    			Colorful:                  true,  // 禁言彩色打印
    		},
    	)
    
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    
    	_ = db.AutoMigrate(&User{}) //此次应该有sql语句
    
    	user := User{
    		Name: "bobby2",
    	}
    	//user.ID             // 返回插入数据的主键
    	//result.Error        // 返回 error
    	//result.RowsAffected // 返回插入记录的条数
    	fmt.Println(user.ID)
    	result := db.Create(&user)
    	fmt.Println(user.ID)
    	fmt.Println(result.Error)
    	fmt.Println(result.RowsAffected)
    
    	//updates语句不会更新零值,但update语句会更新
    	//db.Model(&User{ID: 1}).Update("Name", "")
    	//db.Model(&User{ID: 1}).Updates(User{Name: ""})
    
    	//解决仅更新非零值字段的方法有两种
    	/**
    	1 将string设置为*string
    	2 使用sql的NULLxxx来结局
    	*/
    	//empty := ""
    	//db.Model(&User{ID: 1}).Updates(User{Email: &empty})
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76

    用指定的字段创建记录

    创建记录并为指定字段赋值。

    db.Select("Name", "Age", "CreatedAt").Create(&user)
    // INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")
    
    • 1
    • 2

    创建记录并忽略传递给 ‘Omit’ 的字段值

    db.Omit("Name", "Age", "CreatedAt").Create(&user)
    // INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 00:00:00.000", "2020-07-04 11:05:21.775")
    
    • 1
    • 2

    批量插入

    要高效地插入大量记录,请将切片传递给Create方法。GORM将生成一条SQL语句来插入所有数据并回填主键值,钩子方法也将被调用。. 当记录可以分成多个批处理时,它将开始一个 交易。

    var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
    db.Create(&users)
    
    for _, user := range users {
      user.ID // 1,2,3
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    你可以在使用CreateInBatches创建时指定批处理大小,例如:

    var users = []User{{Name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}
    
    // batch size 100
    db.CreateInBatches(users, 100)
    
    • 1
    • 2
    • 3
    • 4

    为什么不一次性提交所有的,还要分批次?
    答:sql语句是有长度限制!

    在使用Upsert(插入更新)和Create With Associations(使用关联创建)时,也支持批量插入
    初始化GORM时使用CreateBatchSize选项,所有INSERT在创建记录和关联时都将遵循此选项

    db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
      CreateBatchSize: 1000,
    })
    
    db := db.Session(&gorm.Session{CreateBatchSize: 1000})
    
    users = [5000]User{{Name: "jinzhu", Pets: []Pet{pet1, pet2, pet3}}...}
    
    db.Create(&users)
    // INSERT INTO users xxx (5 batches)
    // INSERT INTO pets xxx (15 batches)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    创建钩子

    GORM允许用户定义钩子来实现前保存,前创建,后保存,后创建。这些钩子方法将在创建记录时被调用,有关生命周期的详细信息请参阅Hooks

    func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
      u.UUID = uuid.New()
    
        if u.Role == "admin" {
            return errors.New("invalid role")
        }
        return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果你想跳过Hooks方法,你可以使用SkipHooks会话模式,例如:

    DB.Session(&gorm.Session{SkipHooks: true}).Create(&user)
    DB.Session(&gorm.Session{SkipHooks: true}).Create(&users)
    DB.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(users, 100)
    
    • 1
    • 2
    • 3

    根据 Map 创建

    GORM支持从map[string]interface{}和[]map[string]interface{}创建,例如:

    db.Model(&User{}).Create(map[string]interface{}{
      "Name": "jinzhu", "Age": 18,
    })
    
    // batch insert from `[]map[string]interface{}{}`
    db.Model(&User{}).Create([]map[string]interface{}{
      {"Name": "jinzhu_1", "Age": 18},
      {"Name": "jinzhu_2", "Age": 20},
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    当创建from map时,钩子不会被调用,关联不会被保存,主键值不会被回填

    关联创建—>这个很像一对一

    当创建一些具有关联的数据时,如果它的关联值不是零值,那么这些关联将被替换,并且它的Hooks方法将被调用。

    type CreditCard struct {
      gorm.Model
      Number   string
      UserID   uint
    }
    
    type User struct {
      gorm.Model
      Name       string
      CreditCard CreditCard
    }
    
    db.Create(&User{
      Name: "jinzhu",
      CreditCard: CreditCard{Number: "411111111111"}
    })
    // INSERT INTO `users` ...
    // INSERT INTO `credit_cards` ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    你可以使用Select, Omit跳过保存关联,例如:

    db.Omit("CreditCard").Create(&user)
    // skip all associations
    db.Omit(clause.Associations).Create(&user)
    
    • 1
    • 2
    • 3

    查询

    检索单个对象
    GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误

    // 获取第一条记录(主键升序)
    db.First(&user)
    // SELECT * FROM users ORDER BY id LIMIT 1;
    
    // 获取一条记录,没有指定排序字段
    db.Take(&user)
    // SELECT * FROM users LIMIT 1;
    
    // 获取最后一条记录(主键降序)
    db.Last(&user)
    // SELECT * FROM users ORDER BY id DESC LIMIT 1;
    
    result := db.First(&user)
    result.RowsAffected // 返回找到的记录数
    result.Error        // returns error or nil
    
    // 检查 ErrRecordNotFound 错误===>没有找到数据
    errors.Is(result.Error, gorm.ErrRecordNotFound)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    如果你想避免ErrRecordNotFound错误,你可以使用Find,比如db.Limit(1).Find(&user),Find方法可以接受struct和slice的数据。
    对单个对象使用Find而不带limit,db.Find(&user)将会查询整个表并且只返回第一个对象,这是性能不高并且不确定的。

    First and Last 方法会按主键排序找到第一条记录和最后一条记录 (分别)。 只有在目标 struct 是指针或者通过 db.Model() 指定 model 时,该方法才有效。 此外,如果相关 model 没有定义主键,那么将按 model 的第一个字段进行排序。 例如:

    var user User
    var users []User
    
    // works because destination struct is passed in
    db.First(&user)
    // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
    
    // works because model is specified using `db.Model()`
    result := map[string]interface{}{}
    db.Model(&User{}).First(&result)
    // SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1
    
    // doesn't work
    result := map[string]interface{}{}
    db.Table("users").First(&result)
    
    // works with Take
    result := map[string]interface{}{}
    db.Table("users").Take(&result)
    
    // no primary key defined, results will be ordered by first field (i.e., `Code`)
    type Language struct {
      Code string
      Name string
    }
    db.First(&Language{})
    // SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    //通过first查询单个数据
    var user User
    db.First(&user)
    
    //通过主键查询
    //我们不能给user赋值
    result := db.First(&user, 2)
    
    if errors.Is(result.Error, gorm.ErrRecordNotFound) {
    	fmt.Println("未找到")
    }
    fmt.Println(user.ID)
    
    var users []User
    result1 := db.Find(&users)
    fmt.Println("总共记录", result1.RowsAffected)
    
    for _, user := range users {
    	fmt.Println(user.ID)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    根据主键检索

    如果主键是数字类型,您可以使用 内联条件 来检索对象。 当使用字符串时,需要额外的注意来避免SQL注入;查看 Security 部分来了解详情。

    db.First(&user, 10)
    // SELECT * FROM users WHERE id = 10;
    
    db.First(&user, "10")
    // SELECT * FROM users WHERE id = 10;
    
    db.Find(&users, []int{1,2,3})
    // SELECT * FROM users WHERE id IN (1,2,3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    //通过first查询单个数据
    	var user User
    	db.First(&user)
    
    	//通过主键查询
    	//我们不能给user赋值
    	result := db.First(&user, 2)
    
    	if errors.Is(result.Error, gorm.ErrRecordNotFound) {
    		fmt.Println("未找到")
    	}
    	fmt.Println(user.ID)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    如果主键是字符串(例如像uuid),查询将被写成如下:

    db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
    // SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";
    
    • 1
    • 2

    当目标对象有一个主键值时,将使用主键构建查询条件,例如:

    var user = User{ID: 10}
    db.First(&user)
    // SELECT * FROM users WHERE id = 10;
    
    var result User
    db.Model(User{ID: 10}).First(&result)
    // SELECT * FROM users WHERE id = 10;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    NOTE: 如果您使用 gorm 的特定字段类型(例如 gorm.DeletedAt),它将运行不同的查询来检索对象。

    type User struct {
      ID           string `gorm:"primarykey;size:16"`
      Name         string `gorm:"size:24"`
      DeletedAt    gorm.DeletedAt `gorm:"index"`
    }
    
    var user = User{ID: 15}
    db.First(&user)
    //  SELECT * FROM `users` WHERE `users`.`id` = '15' AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    检索全部对象

    // Get all records
    result := db.Find(&users)
    // SELECT * FROM users;
    
    result.RowsAffected // returns found records count, equals `len(users)`
    result.Error        // returns error
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    var users []User
    	result1 := db.Find(&users)
    	fmt.Println("总共记录", result1.RowsAffected)
    
    	for _, user := range users {
    		fmt.Println(user.ID)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    条件

    String 条件

    // 获取第一条匹配记录
    db.Where("name = ?", "jinzhu").First(&user)
    // SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;
    
    // 获取所有匹配的记录--->这个获取的所有匹配的记录之外的
    db.Where("name <> ?", "jinzhu").Find(&users)
    // SELECT * FROM users WHERE name <> 'jinzhu';
    
    // IN===>jinzhu或者jinzhu2
    db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
    // SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');
    
    // LIKE
    db.Where("name LIKE ?", "%jin%").Find(&users)
    // SELECT * FROM users WHERE name LIKE '%jin%';
    
    // AND
    db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
    // SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
    
    // Time
    db.Where("updated_at > ?", lastWeek).Find(&users)
    // SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';
    
    // BETWEEN
    db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
    // SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    如果对象设置了主键,条件查询将不会覆盖主键的值,而是用 And 连接条件。 例如:

    var user = User{ID: 10}
    db.Where("id = ?", 20).First(&user)
    // SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1
    
    • 1
    • 2
    • 3

    这个查询将会给出record not found错误 所以,在你想要使用例如 user 这样的变量从数据库中获取新值前,需要将例如 id 这样的主键设置为nil。

    Struct & Map 条件

    / Struct
    db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
    
    // Map
    db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
    
    // Slice of primary keys
    db.Where([]int64{20, 21, 22}).Find(&users)
    // SELECT * FROM users WHERE id IN (20, 21, 22);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    当使用struct查询时,GORM只会查询非零字段,这意味着如果字段的值为0,",false或其他零值,它将不会用于构建查询条件,例如:

    db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu";
    
    • 1
    • 2

    要在查询条件中包含零值,您可以使用映射,它将包括所有键值作为查询条件,例如:

    db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
    // SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
    
    • 1
    • 2
    //通过where查询
    	var user User
    	db.Where("name = ?", "bobby1").First(&user)
    	var users []User
    	db.Where(&User{Name: "bobby1"}).Find(&users) //这个才是查询所有的
    	for _, user1 := range users {
    		fmt.Println(user1.ID)
    	}
    
    	db.Where("name IN ?", []string{"bobby1", "bobby2"}).Find(&users)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    更新

    保存所有字段
    Save 会保存所有的字段,即使字段是零值

    db.First(&user)
    
    user.Name = "jinzhu 2"
    user.Age = 100
    db.Save(&user)
    // UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    Save是一个组合功能。如果save value不包含主键,它将执行Create,否则将执行Update(包含所有字段)。

    db.Save(&User{Name: "jinzhu", Age: 100})
    // INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")
    
    db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
    // UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE `id` = 1
    
    • 1
    • 2
    • 3
    • 4
    • 5

    更新单个列

    当使用Update更新单个列时,它需要有任何条件,否则会引发错误ErrMissingWhereClause,请查看Block Global Updates了解详细信息。当使用Model方法并且它的值有一个主值时,主键将被用来构建条件,例如:

    // Update with conditions
    db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
    // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;
    
    // User's ID is `111`:
    db.Model(&user).Update("name", "hello")
    // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
    
    // Update with conditions and model value
    db.Model(&user).Where("active = ?", true).Update("name", "hello")
    // UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    更新多列

    Updates支持使用struct或map[string]接口{}进行更新,当使用struct进行更新时,默认情况下只更新非零字段

    // Update attributes with `struct`, will only update non-zero fields
    db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
    // UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
    
    // Update attributes with `map`
    db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
    // UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    当使用struct进行更新时,GORM只会更新非零字段。您可能希望使用map来更新属性,或者使用Select来指定要更新的字段

    更新选定字段

    如果要更新选定的字段或在更新时忽略某些字段,可以使用Select, Omit

    // Select with Map
    // User's ID is `111`:
    db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
    // UPDATE users SET name='hello' WHERE id=111;
    
    db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
    // UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
    
    // Select with Struct (select zero value fields)
    db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
    // UPDATE users SET name='new_name', age=0 WHERE id=111;
    
    // Select all fields (select all fields include zero value fields)
    db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
    
    // Select all fields but omit Role (select all fields include zero value fields)
    db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    更新 Hook

    GORM允许钩子beforeave, BeforeUpdate, AfterSave, AfterUpdate。这些方法将在更新记录时调用,详情请参考Hooks

    func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {
        if u.Role == "admin" {
            return errors.New("admin user not allowed to update")
        }
        return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    批量更新

    如果我们没有使用Model指定具有主键值的记录,GORM将执行批处理更新

    // Update with struct
    db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
    // UPDATE users SET name='hello', age=18 WHERE role = 'admin';
    
    // Update with map
    db.Table("users").Where("id IN ?", []int{10, 11}).Updates(map[string]interface{}{"name": "hello", "age": 18})
    // UPDATE users SET name='hello', age=18 WHERE id IN (10, 11);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    阻止全局更新

    如果你执行一个批处理更新没有任何条件,GORM将不会运行它,并将返回ErrMissingWhereClause错误默认
    例如,您必须使用某些条件或使用原始SQL或启用AllowGlobalUpdate模式

    db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClause
    
    db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
    // UPDATE users SET `name` = "jinzhu" WHERE 1=1
    
    db.Exec("UPDATE users SET name = ?", "jinzhu")
    // UPDATE users SET name = "jinzhu"
    
    db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
    // UPDATE users SET `name` = "jinzhu"
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    更新的记录数

    获取受更新影响的行数

    // Get updated records count with `RowsAffected`
    result := db.Model(User{}).Where("role = ?", "admin").Updates(User{Name: "hello", Age: 18})
    // UPDATE users SET name='hello', age=18 WHERE role = 'admin';
    
    result.RowsAffected // returns updated records count
    result.Error        // returns updating error
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    删除

    删除一条记录
    删除一条记录时,删除对象需要指定主键,否则会触发 批量删除,例如:

    // Email 的 ID 是 `10`
    db.Delete(&email)
    // DELETE from emails where id = 10;
    
    // 带额外条件的删除
    db.Where("name = ?", "jinzhu").Delete(&email)
    // DELETE from emails where id = 10 AND name = "jinzhu";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    根据主键删除

    GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(如以下例子。也可以使用字符串——译者注)。查看 查询-内联条件(Query Inline Conditions) 了解详情。

    db.Delete(&User{}, 10)
    // DELETE FROM users WHERE id = 10;
    
    db.Delete(&User{}, "10")
    // DELETE FROM users WHERE id = 10;
    
    db.Delete(&users, []int{1,2,3})
    // DELETE FROM users WHERE id IN (1,2,3);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    钩子函数

    对于删除操作,GORM 支持 BeforeDelete、AfterDelete Hook,在删除记录时会调用这些方法,查看 Hook 获取详情

    func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
        if u.Role == "admin" {
            return errors.New("admin user not allowed to delete")
        }
        return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    批量删除

    如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录

    db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
    // DELETE from emails where email LIKE "%jinzhu%";
    
    db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
    // DELETE from emails where email LIKE "%jinzhu%";
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以将一个主键切片传递给Delete 方法,以便更高效的删除数据量大的记录

    var users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
    db.Delete(&users)
    // DELETE FROM users WHERE id IN (1,2,3);
    
    db.Delete(&users, "name LIKE ?", "%jinzhu%")
    // DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3); 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    阻止全局删除

    当你试图执行不带任何条件的批量删除时,GORM将不会运行并返回ErrMissingWhereClause 错误,如果一定要这么做,你必须添加一些条件,或者使用原生SQL,或者开启AllowGlobalUpdate 模式,如下例:

    db.Delete(&User{}).Error // gorm.ErrMissingWhereClause
    
    db.Delete(&[]User{{Name: "jinzhu1"}, {Name: "jinzhu2"}}).Error // gorm.ErrMissingWhereClause
    
    db.Where("1 = 1").Delete(&User{})
    // DELETE FROM `users` WHERE 1=1
    
    db.Exec("DELETE FROM users")
    // DELETE FROM users
    
    db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
    // DELETE FROM users
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    返回删除行的数据

    返回被删除的数据,仅当数据库支持回写功能时才能正常运行,如下例:

    // 回写所有的列
    var users []User
    DB.Clauses(clause.Returning{}).Where("role = ?", "admin").Delete(&users)
    // DELETE FROM `users` WHERE role = "admin" RETURNING *
    // users => []User{{ID: 1, Name: "jinzhu", Role: "admin", Salary: 100}, {ID: 2, Name: "jinzhu.2", Role: "admin", Salary: 1000}}
    
    // 回写指定的列
    DB.Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "salary"}}}).Where("role = ?", "admin").Delete(&users)
    // DELETE FROM `users` WHERE role = "admin" RETURNING `name`, `salary`
    // users => []User{{ID: 0, Name: "jinzhu", Role: "", Salary: 100}, {ID: 0, Name: "jinzhu.2", Role: "", Salary: 1000}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    软删除

    如果你的模型包含了 gorm.DeletedAt字段(该字段也被包含在gorm.Model中),那么该模型将会自动获得软删除的能力!
    当调用Delete时,GORM并不会从数据库中删除该记录,而是将该记录的DeleteAt设置为当前时间,而后的一般查询方法将无法查找到此条记录。

    // user's ID is `111`
    db.Delete(&user)
    // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
    
    // Batch Delete
    db.Where("age = ?", 20).Delete(&User{})
    // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
    
    // Soft deleted records will be ignored when querying
    db.Where("age = 20").Find(&user)
    // SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果你并不想嵌套gorm.Model,你也可以像下方例子那样开启软删除特性:

    type User struct {
      ID      int
      Deleted gorm.DeletedAt
      Name    string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    查找被软删除的记录

    你可以使用Unscoped来查询到被软删除的记录

    db.Unscoped().Where("age = 20").Find(&users)
    // SELECT * FROM users WHERE age = 20;
    
    • 1
    • 2

    永久删除

    你可以使用 Unscoped来永久删除匹配的记录

    db.Unscoped().Delete(&order)
    // DELETE FROM orders WHERE id=10;
    
    • 1
    • 2

    删除标志

    默认情况下,gorm.Model使用*time.Time作为DeletedAt 的字段类型,不过软删除插件gorm.io/plugin/soft_delete同时也提供其他的数据格式支持
    提示 当使用DeletedAt创建唯一复合索引时,你必须使用其他的数据类型,例如通过gorm.io/plugin/soft_delete插件将字段类型定义为unix时间戳等等

    import "gorm.io/plugin/soft_delete"
    
    type User struct {
      ID        uint
      Name      string                `gorm:"uniqueIndex:udx_name"`
      DeletedAt soft_delete.DeletedAt `gorm:"uniqueIndex:udx_name"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    Unix 时间戳

    使用unix时间戳作为删除标志

    import "gorm.io/plugin/soft_delete"
    
    type User struct {
      ID        uint
      Name      string
      DeletedAt soft_delete.DeletedAt
    }
    
    // 查询
    SELECT * FROM users WHERE deleted_at = 0;
    
    // 软删除
    UPDATE users SET deleted_at = /* current unix second */ WHERE ID = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    你同样可以指定使用毫秒 milli或纳秒 nano作为值,如下例:

    type User struct {
      ID    uint
      Name  string
      DeletedAt soft_delete.DeletedAt `gorm:"softDelete:milli"`
      // DeletedAt soft_delete.DeletedAt `gorm:"softDelete:nano"`
    }
    
    // 查询
    SELECT * FROM users WHERE deleted_at = 0;
    
    // 软删除
    UPDATE users SET deleted_at = /* current unix milli second or nano second */ WHERE ID = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    使用 1 / 0 作为 删除标志

    import "gorm.io/plugin/soft_delete"
    
    type User struct {
      ID    uint
      Name  string
      IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"`
    }
    
    // 查询
    SELECT * FROM users WHERE is_del = 0;
    
    // 软删除
    UPDATE users SET is_del = 1 WHERE ID = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    混合模式

    混合模式可以使用 0,1或者unix时间戳来标记数据是否被软删除,并同时可以保存被删除时间

    type User struct {
      ID        uint
      Name      string
      DeletedAt time.Time
      IsDel     soft_delete.DeletedAt `gorm:"softDelete:flag,DeletedAtField:DeletedAt"` // use `1` `0`
      // IsDel     soft_delete.DeletedAt `gorm:"softDelete:,DeletedAtField:DeletedAt"` // use `unix second`
      // IsDel     soft_delete.DeletedAt `gorm:"softDelete:nano,DeletedAtField:DeletedAt"` // use `unix nano second`
    }
    
    // 查询
    SELECT * FROM users WHERE is_del = 0;
    
    // 软删除
    UPDATE users SET is_del = 1, deleted_at = /* current unix second */ WHERE ID = 1;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    Belongs To

    belongs to 会与另一个模型建立了一对一的连接。 这种模型的每一个实例都“属于”另一个模型的一个实例。
    例如,您的应用包含 user 和 company,并且每个 user 能且只能被分配给一个 company。下面的类型就表示这种关系。 注意,在 User 对象中,有一个和 Company 一样的 CompanyID。 默认情况下, CompanyID 被隐含地用来在 User 和 Company 之间创建一个外键关系, 因此必须包含在 User 结构体中才能填充 Company 内部结构体。

    // `User` 属于 `Company`,`CompanyID` 是外键
    type User struct {
      gorm.Model
      Name      string
      CompanyID int
      Company   Company
    }
    
    type Company struct {
      ID   int
      Name string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    重写外键

    要定义一个 belongs to 关系,数据库的表中必须存在外键。默认情况下,外键的名字,使用拥有者的类型名称加上表的主键的字段名字
    例如,定义一个User实体属于Company实体,那么外键的名字一般使用CompanyID。
    GORM同时提供自定义外键名字的方式,如下例所示。

    type User struct {
      gorm.Model
      Name         string
      CompanyRefer int
      Company      Company `gorm:"foreignKey:CompanyRefer"`
      // 使用 CompanyRefer 作为外键
    }
    
    type Company struct {
      ID   int
      Name string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    重写引用

    对于 belongs to 关系,GORM 通常使用数据库表,主表(拥有者)的主键值作为外键参考。 正如上面的例子,我们使用主表Company中的主键字段ID作为外键的参考值。
    如果设置了User实体属于Company实体,那么GORM会自动把Company中的ID属性保存到User的CompanyID属性中。
    同样的,您也可以使用标签 references 来更改它,例如:

    type User struct {
      gorm.Model
      Name      string
      CompanyID string
      Company   Company `gorm:"references:Code"` // 使用 Code 作为引用
    }
    
    type Company struct {
      ID   int
      Code string
      Name string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    NOTE 如果外键名恰好在拥有者类型中存在,GORM 通常会错误的认为它是 has one 关系。我们需要在 belongs to 关系中指定 references

    type User struct {
      gorm.Model
      Name      string
      CompanyID string
      Company   Company `gorm:"references:CompanyID"` // 使用 Company.CompanyID 作为引用
    }
    
    type Company struct {
      CompanyID   int
      Code        string
      Name        string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type User struct {
    	gorm.Model
    	Name      string
    	CompanyID int //数据库中存储的字段  sql使用它
    	Company   Company
    }
    
    type Company struct {
    	ID   int
    	Name string
    }
    
    func main() {
    	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
    	//loc 本地时区
    	//想要正确的处理 time.Time ,您需要带上 parseTime 参数
    	dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢SQL阈值
    			LogLevel:                  logger.Info, // 日志级别
    			IgnoreRecordNotFoundError: false,
    			ParameterizedQueries:      false, // 不要在SQL日志中包含参数
    			Colorful:                  true,  // 禁言彩色打印
    		},
    	)
    
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    
    	//db.AutoMigrate(&User{}) //新建了user表和company表,并且设置外键
    	//db.Create(&User{
    	//	Name: "bobby",
    	//	Company: Company{
    	//		Name: "chengpeng",
    	//	},
    	//})
    
    	//解决外键存在的问题
    	db.Create(&User{
    		Name: "bobby2",
    		Company: Company{
    			ID: 1,
    		},
    	})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    Belongs to 的 CRUD

    查看 关联模式 获取 belongs to 相关的用法

    预加载

    GORM 可以通过 Preload、Joins 预加载 belongs to 关联的记录,查看 预加载 获取详情

    外键约束

    你可以通过 constraint 标签配置 OnUpdate、OnDelete 实现外键约束,在使用 GORM 进行迁移时它会被创建,例如:

    type User struct {
      gorm.Model
      Name      string
      CompanyID int
      Company   Company `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
    }
    
    type Company struct {
      ID   int
      Name string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    var user User
    //Preload
    //db.Preload("Company").First(&user)
    db.Joins("Company").First(&user)
    fmt.Println(user)
    
    • 1
    • 2
    • 3
    • 4
    • 5

    Has Many

    has many 与另一个模型建立了一对多的连接。 不同于 has one,拥有者可以有零或多个关联模型。

    例如,您的应用包含 user 和 credit card 模型,且每个 user 可以有多张 credit card。

    声明

    // User 有多张 CreditCard,UserID 是外键
    type User struct {
      gorm.Model
      CreditCards []CreditCard
    }
    
    type CreditCard struct {
      gorm.Model
      Number string
      UserID uint
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    检索

    // 检索用户列表并预加载信用卡
    func GetAll(db *gorm.DB) ([]User, error) {
        var users []User
        err := db.Model(&User{}).Preload("CreditCards").Find(&users).Error
        return users, err
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    重写外键

    要定义 has many 关系,同样必须存在外键。 默认的外键名是拥有者的类型名加上其主键字段名
    例如,要定义一个属于 User 的模型,则其外键应该是 UserID。
    此外,想要使用另一个字段作为外键,您可以使用 foreignKey 标签自定义它:

    type User struct {
      gorm.Model
      CreditCards []CreditCard `gorm:"foreignKey:UserRefer"`
    }
    
    type CreditCard struct {
      gorm.Model
      Number    string
      UserRefer uint
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    重写引用

    GORM 通常使用拥有者的主键作为外键的值。 对于上面的例子,它是 User 的 ID 字段。
    为 user 添加 credit card 时,GORM 会将 user 的 ID 字段保存到 credit card 的 UserID 字段。
    同样的,您也可以使用标签 references 来更改它,例如:

    type User struct {
      gorm.Model
      MemberNumber string
      CreditCards  []CreditCard `gorm:"foreignKey:UserNumber;references:MemberNumber"`
    }
    
    type CreditCard struct {
      gorm.Model
      Number     string
      UserNumber string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    多态关联

    GORM 为 has one 和 has many 提供了多态关联支持,它会将拥有者实体的表名、主键都保存到多态类型的字段中。

    type Dog struct {
      ID   int
      Name string
      Toys []Toy `gorm:"polymorphic:Owner;"`
    }
    
    type Toy struct {
      ID        int
      Name      string
      OwnerID   int
      OwnerType string
    }
    
    db.Create(&Dog{Name: "dog1", Toys: []Toy{{Name: "toy1"}, {Name: "toy2"}}})
    // INSERT INTO `dogs` (`name`) VALUES ("dog1")
    // INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","dogs"), ("toy2","1","dogs")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    您可以使用标签 polymorphicValue 来更改多态类型的值,例如:

    type Dog struct {
      ID   int
      Name string
      Toys []Toy `gorm:"polymorphic:Owner;polymorphicValue:master"`
    }
    
    type Toy struct {
      ID        int
      Name      string
      OwnerID   int
      OwnerType string
    }
    
    db.Create(&Dog{Name: "dog1", Toys: []Toy{{Name: "toy1"}, {Name: "toy2"}}})
    // INSERT INTO `dogs` (`name`) VALUES ("dog1")
    // INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","master"), ("toy2","1","master")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    Has Many 的 CURD

    查看 关联模式 获取 has many 相关的用法

    预加载

    GORM 可以通过 Preload 预加载 has many 关联的记录,查看 预加载 获取详情

    自引用 Has Many

    type User struct {
      gorm.Model
      Name      string
      ManagerID *uint
      Team      []User `gorm:"foreignkey:ManagerID"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    外键约束

    你可以通过为标签 constraint 配置 OnUpdate、OnDelete 实现外键约束,在使用 GORM 进行迁移时它会被创建,例如:

    type User struct {
      gorm.Model
      CreditCards []CreditCard `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
    }
    
    type CreditCard struct {
      gorm.Model
      Number string
      UserID uint
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    你也可以在删除记录时通过 Select 来删除 has many 关联的记录,查看 Delete with Select 获取详情

    package main
    
    import (
    	"fmt"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    // User 有多张 CreditCard,UserID 是外键
    type User struct {
    	gorm.Model
    	CreditCards []CreditCard `gorm:"foreignKey:UserID"`
    }
    
    type CreditCard struct {
    	gorm.Model
    	Number string
    	UserID uint
    }
    
    func main() {
    	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
    	//loc 本地时区
    	//想要正确的处理 time.Time ,您需要带上 parseTime 参数
    	dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢SQL阈值
    			LogLevel:                  logger.Info, // 日志级别
    			IgnoreRecordNotFoundError: false,
    			ParameterizedQueries:      false, // 不要在SQL日志中包含参数
    			Colorful:                  true,  // 禁言彩色打印
    		},
    	)
    
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    
    	//db.AutoMigrate(&User{}) //如果一个一个创建,就会发现没有外键
    	//db.AutoMigrate(&CreditCard{})
    	//db.AutoMigrate(&User{}, &CreditCard{}) //这样创建就会有外键
    	
    
    	//user := &User{}
    	//db.Create(&user)
    	//db.Create(&CreditCard{
    	//	Number: "123",
    	//	UserID: 1,
    	//})
    	//
    	//db.Create(&CreditCard{
    	//	Number: "1234",
    	//	UserID: 1,
    	//})
    
    	var user User
    	db.Preload("CreditCards").First(&user)
    	for _, card := range user.CreditCards {
    		fmt.Println(card.Number)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    在大型的系统中,不建议使用外键约束,外键约束也有很大的优点:数据的完整性
    外键约束会让你的数据很完整,即使是业务代码,有些人考虑的不严谨也不会造成数据不一致
    在大型的系统,高并发的系统重一般不使用外键约束,自己在业务层面保证数据的一致性

    Many To Many

    Many to Many 会在两个 model 中添加一张连接表。
    例如,您的应用包含了 user 和 language,且一个 user 可以说多种 language,多个 user 也可以说一种 language。

    // User 拥有并属于多种 language,`user_languages` 是连接表
    type User struct {
      gorm.Model
      Languages []Language `gorm:"many2many:user_languages;"`
    }
    
    type Language struct {
      gorm.Model
      Name string
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    当使用 GORM 的 AutoMigrate 为 User 创建表时,GORM 会自动创建连接表

    反向引用

    声明

    // User 拥有并属于多种 language,`user_languages` 是连接表
    type User struct {
      gorm.Model
      Languages []*Language `gorm:"many2many:user_languages;"`
    }
    
    type Language struct {
      gorm.Model
      Name string
      Users []*User `gorm:"many2many:user_languages;"`
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    检索

    // 检索 User 列表并预加载 Language
    func GetAllUsers(db *gorm.DB) ([]User, error) {
        var users []User
        err := db.Model(&User{}).Preload("Languages").Find(&users).Error
        return users, err
    }
    
    // 检索 Language 列表并预加载 User
    func GetAllLanguages(db *gorm.DB) ([]Language, error) {
        var languages []Language
        err := db.Model(&Language{}).Preload("Users").Find(&languages).Error
        return languages, err
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    重写外键

    对于 many2many 关系,连接表会同时拥有两个模型的外键,例如:

    type User struct {
      gorm.Model
      Languages []Language `gorm:"many2many:user_languages;"`
    }
    
    type Language struct {
      gorm.Model
      Name string
    }
    
    // 连接表:user_languages
    //   foreign key: user_id, reference: users.id
    //   foreign key: language_id, reference: languages.id
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    若要重写它们,可以使用标签 foreignKey、references、joinforeignKey、joinReferences。当然,您不需要使用全部的标签,你可以仅使用其中的一个重写部分的外键、引用。

    type User struct {
        gorm.Model
        Profiles []Profile `gorm:"many2many:user_profiles;foreignKey:Refer;joinForeignKey:UserReferID;References:UserRefer;joinReferences:ProfileRefer"`
        Refer    uint      `gorm:"index:,unique"`
    }
    
    type Profile struct {
        gorm.Model
        Name      string
        UserRefer uint `gorm:"index:,unique"`
    }
    
    // 会创建连接表:user_profiles
    //   foreign key: user_refer_id, reference: users.refer
    //   foreign key: profile_refer, reference: profiles.user_refer
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    注意: 某些数据库只允许在唯一索引字段上创建外键,如果您在迁移时会创建外键,则需要指定 unique index 标签。

    自引用 Many2Many

    自引用 many2many 关系

    type User struct {
      gorm.Model
        Friends []*User `gorm:"many2many:user_friends"`
    }
    
    // 会创建连接表:user_friends
    //   foreign key: user_id, reference: users.id
    //   foreign key: friend_id, reference: users.id
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    预加载

    GORM 可以通过 Preload 预加载 has many 关联的记录,查看 预加载 获取详情

    Many2Many 的 CURD

    查看 关联模式 获取 many2many 相关的用法

    自定义连接表

    JoinTable可以是一个全功能的模型,像有软删除,钩子支持和更多的字段,你可以设置它与SetupJoinTable,例如:
    注意: 自定义连接表要求外键是复合主键或复合唯一索引

    type Person struct {
      ID        int
      Name      string
      Addresses []Address `gorm:"many2many:person_addressses;"`
    }
    
    type Address struct {
      ID   uint
      Name string
    }
    
    type PersonAddress struct {
      PersonID  int `gorm:"primaryKey"`
      AddressID int `gorm:"primaryKey"`
      CreatedAt time.Time
      DeletedAt gorm.DeletedAt
    }
    
    func (PersonAddress) BeforeCreate(db *gorm.DB) error {
      // ...
    }
    
    // 修改 Person 的 Addresses 字段的连接表为 PersonAddress
    // PersonAddress 必须定义好所需的外键,否则会报错
    err := db.SetupJoinTable(&Person{}, "Addresses", &PersonAddress{})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    外键约束

    你可以通过为标签 constraint 配置 OnUpdate、OnDelete 实现外键约束,在使用 GORM 进行迁移时它会被创建,例如:

    type User struct {
      gorm.Model
      Languages []Language `gorm:"many2many:user_speaks;"`
    }
    
    type Language struct {
      Code string `gorm:"primarykey"`
      Name string
    }
    
    // CREATE TABLE `user_speaks` (`user_id` integer,`language_code` text,PRIMARY KEY (`user_id`,`language_code`),CONSTRAINT `fk_user_speaks_user
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    你也可以在删除记录时通过 Select 来删除 many2many 关系的记录,查看 Delete with Select 获取详情

    复合外键

    如果您的模型使用了 复合主键,GORM 会默认启用复合外键。

    您也可以覆盖默认的外键、指定多个外键,只需用逗号分隔那些键名,例如:

    type Tag struct {
      ID     uint   `gorm:"primaryKey"`
      Locale string `gorm:"primaryKey"`
      Value  string
    }
    
    type Blog struct {
      ID         uint   `gorm:"primaryKey"`
      Locale     string `gorm:"primaryKey"`
      Subject    string
      Body       string
      Tags       []Tag `gorm:"many2many:blog_tags;"`
      LocaleTags []Tag `gorm:"many2many:locale_blog_tags;ForeignKey:id,locale;References:id"`
      SharedTags []Tag `gorm:"many2many:shared_blog_tags;ForeignKey:id;References:id"`
    }
    
    // 连接表:blog_tags
    //   foreign key: blog_id, reference: blogs.id
    //   foreign key: blog_locale, reference: blogs.locale
    //   foreign key: tag_id, reference: tags.id
    //   foreign key: tag_locale, reference: tags.locale
    
    // 连接表:locale_blog_tags
    //   foreign key: blog_id, reference: blogs.id
    //   foreign key: blog_locale, reference: blogs.locale
    //   foreign key: tag_id, reference: tags.id
    
    // 连接表:shared_blog_tags
    //   foreign key: blog_id, reference: blogs.id
    //   foreign key: tag_id, reference: tags.id
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    package main
    
    import (
    	"fmt"
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"log"
    	"os"
    	"time"
    )
    
    type User struct {
    	gorm.Model
    	Languages []Language `gorm:"many2many:user_languages;"`
    }
    
    type Language struct {
    	gorm.Model
    	Name string
    }
    
    func main() {
    	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
    	//loc 本地时区
    	//想要正确的处理 time.Time ,您需要带上 parseTime 参数
    	dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢SQL阈值
    			LogLevel:                  logger.Info, // 日志级别
    			IgnoreRecordNotFoundError: false,
    			ParameterizedQueries:      false, // 不要在SQL日志中包含参数
    			Colorful:                  true,  // 禁言彩色打印
    		},
    	)
    
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    
    	//db.AutoMigrate(&User{})
    
    	//插入
    	//languages := []Language{}
    	//languages = append(languages, Language{Name: "go"})
    	//languages = append(languages, Language{Name: "java"})
    	//user := User{
    	//
    	//	Languages: languages,
    	//}
    	//
    	//db.Create(&user)
    
    	//获取数据--第一种
    	//var user User
    	//db.Preload("Languages").First(&user)
    	//for _, language := range user.Languages {
    	//	fmt.Println(language.Name)
    	//}
    
    	//如果我们已经取出一个用户来了,但是这个用户我们之前没有使用preload来加载对应的Languages
    	//不是说用户有language我们就一定要取出来
    	// 开始关联模式
    	//查找关联-->第二种方法
    	var user User
    	db.First(&user)
    	var laguages []Language
    	_ = db.Model(&user).Association("Languages").Find(&laguages)
    	for _, laguage := range laguages {
    		fmt.Println(laguage.Name)
    	}
    
    	fmt.Println(user.ID)
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    我们自己定义表面是什么
    同一的给所有的表面加上前缀
    NamingStrategy和TableName不能同时配置

    package main
    
    import (
    	"gorm.io/driver/mysql"
    	"gorm.io/gorm"
    	"gorm.io/gorm/logger"
    	"gorm.io/gorm/schema"
    	"log"
    	"os"
    	"time"
    )
    
    //type Language1 struct {
    //	gorm.Model
    //	Name string
    //}
    
    type Language2 struct {
    	Name    string
    	AddTime time.Time //每个记录创建的时候自动加上当前加入到AddTime中  不加BeforeCreate这个方法就会报错
    	//也可以加这样加
    	//AddTime1 sql.NullTime
    }
    
    func (l *Language2) BeforeCreate(tx *gorm.DB) (err error) {
    	l.AddTime = time.Now()
    	return
    }
    
    //在数据默认是加一个默认值
    
    // TableName 在gorm中可以通过给某一个struct添加TableName方法自定义表面
    // 自定义表名
    //func (Language1) TableName() string {
    //	return "my_language"
    //}
    
    // 同一个表名加一个前缀
    func main() {
    	// 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
    	//loc 本地时区
    	//想要正确的处理 time.Time ,您需要带上 parseTime 参数
    	dsn := "root:root@tcp(192.168.0.102:3306)/gorm_test?charset=utf8mb4&parseTime=True&loc=Local"
    
    	//NamingStrategy和TableName不能同时配置
    	newLogger := logger.New(
    		log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
    		logger.Config{
    			SlowThreshold:             time.Second, // 慢SQL阈值
    			LogLevel:                  logger.Info, // 日志级别
    			IgnoreRecordNotFoundError: false,
    			ParameterizedQueries:      false, // 不要在SQL日志中包含参数
    			Colorful:                  true,  // 禁言彩色打印
    		},
    	)
    
    	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
    		NamingStrategy: schema.NamingStrategy{
    			TablePrefix: "chengpeng_",
    		},
    		Logger: newLogger,
    	})
    	if err != nil {
    		panic(err)
    	}
    
    	db.AutoMigrate(&Language2{})
    	db.Create(&Language2{Name: "Python"})
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
  • 相关阅读:
    linux命令安装python Django
    开关电源模块 遥控开/关电路
    python加密Django框架代码(通过修改Cpython解释器)
    使用PyTorch处理多维特征输入的完美指南
    今天做了冰红茶
    解析类型参数
    Flink SQL系列之:解析Debezium数据格式时间字段常用的函数
    【博学谷学习记录】超强总结,用心分享|Hadoop读写流程
    ‘iostream‘ file not foundclang(pp_file_not_found)
    大数据Flink(九十四):DML:TopN 子句
  • 原文地址:https://blog.csdn.net/qq_40432598/article/details/134439252