模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成,如:
type User struct { ID uint Name string Email *string Age uint8 Birthday *time.Time MemberNumber sql.NullString ActivedAt sql.NullTime CreatedAt time.Time UpdatedAt time.Time}
gorm默认情况下约定使用结构体的名字作为表名,结构体字段名作为列名,使用ID作为主键。
gorm.Model是gorm定义的一个结构体,它包含了几个常见的字段名。
// gorm.Model 的定义type Model struct { ID uint `gorm:"primaryKey"` CreatedAt time.Time UpdatedAt time.Time DeletedAt gorm.DeletedAt `gorm:"index"`}
对于可导出的字段,可以使用结构体标签来控制字段的权限。
type User struct { Name string `gorm:"<-:create"` // 允许读和创建 Name string `gorm:"<-:update"` // 允许读和更新 Name string `gorm:"<-"` // 允许读和写(创建和更新) Name string `gorm:"<-:false"` // 允许读,禁止写 Name string `gorm:"->"` // 只读(除非有自定义配置,否则禁止写) Name string `gorm:"->;<-:create"` // 允许读和写 Name string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读) Name string `gorm:"-"` // 读写操作均会忽略该字段}
GORM 约定使用 CreatedAt、UpdatedAt 追踪创建 / 更新时间。如果您定义了这种字段,GORM 在创建、更新时会自动填充当前时间。
type User struct { CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充 UpdatedAt int // 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充 Updated int64 `gorm:"autoUpdateTime:nano"` // 使用时间戳填纳秒数充更新时间 Updated int64 `gorm:"autoUpdateTime:milli"` // 使用时间戳毫秒数填充更新时间 Created int64 `gorm:"autoCreateTime"` // 使用时间戳秒数填充创建时间}
当一个结构体中嵌入了一个匿名结构体时,匿名结构体的字段也被认为是父结构体的字段。而如果是嵌入了一个普通结构体时,可以使用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}
gorm支持以下tag,tag名大小写不敏感,但建议使用驼峰命名。
以连接MySQL数据库为例。
import ( "gorm.io/driver/mysql" "gorm.io/gorm")func main() { // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情 dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local" db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})}
连接数据库的高级配置
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{})
gorm允许通过一个现有的数据库连接来初始化*gorm.DB
import ( "database/sql" "gorm.io/gorm")sqlDB, err := sql.Open("mysql", "mydb_dsn")gormDB, err := gorm.Open(mysql.New(mysql.Config{ Conn: sqlDB,}), &gorm.Config{})
连接池
sqlDB, err := db.DB()// SetMaxIdleConns 设置空闲连接池中连接的最大数量sqlDB.SetMaxIdleConns(10)// SetMaxOpenConns 设置打开数据库连接的最大数量。sqlDB.SetMaxOpenConns(100)// SetConnMaxLifetime 设置了连接可复用的最大时间。sqlDB.SetConnMaxLifetime(time.Hour)
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}result := db.Create(&user) // 通过数据的指针来创建user.ID // 返回插入数据的主键result.Error // 返回 errorresult.RowsAffected // 返回插入记录的条数
创建记录并更新给出的字段。用Select,指定你想从数据库中检索出的字段
db.Select("Name", "Age", "CreatedAt").Create(&user)// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")
创建记录并更新未给出的字段
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")
要有效地插入大量记录,请将一个 slice 传递给 Create 方法。 将切片数据传递给 Create 方法,GORM 将生成一个单一的 SQL 语句来插入所有数据,并回填主键的值,钩子方法也会被调用。
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}db.Create(&users)for _, user := range users { user.ID // 1,2,3}
使用 CreateInBatches 创建时,你还可以指定创建的数量
var users = []User{name: "jinzhu_1"}, ...., {Name: "jinzhu_10000"}}db.CreateInBatches(users, len(users))
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},})
注意: 根据 map 创建记录时,association 不会被调用,且主键也不会自动填充
2. 查询
(1) 检索单个对象
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// 检查 ErrRecordNotFound 错误errors.Is(result.Error, gorm.ErrRecordNotFound)
First、Last 方法会根据主键查找到第一个、最后一个记录, 它仅在通过 struct 或提供 model 值进行查询时才起作用。 如果 model 类型没有定义主键,则按第一个字段排序。
var user User// 可以db.First(&user)// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1// 可以result := map[string]interface{}{}db.Model(&User{}).First(&result)// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1// 不行result := map[string]interface{}{}db.Table("users").First(&result)// 但可以配合 Take 使用result := map[string]interface{}{}db.Table("users").Take(&result)// 根据第一个字段排序type Language struct { Code string Name string}db.First(&Language{})// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1
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);
// 获取全部记录result := db.Find(&users)// SELECT * FROM users;result.RowsAffected // 返回找到的记录数,相当于 `len(users)`result.Error // returns error
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';// INdb.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');// LIKEdb.Where("name LIKE ?", "%jin%").Find(&users)// SELECT * FROM users WHERE name LIKE '%jin%';// ANDdb.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;// Timedb.Where("updated_at > ?", lastWeek).Find(&users)// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';// BETWEENdb.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';
struct或map条件
// Structdb.Where(&User{Name: "jinzhu", Age: 20}).First(&user)// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;// Mapdb.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;// 主键切片条件db.Where([]int64{20, 21, 22}).Find(&users)// SELECT * FROM users WHERE id IN (20, 21, 22);
**注意:**当使用结构体作为条件查询时,GORM 只会查询非零值字段。这意味着如果您的字段值为0、''、false 或其他零值,该字段不会被用于构建查询条件。
// SELECT * FROM users WHERE id = 23;// 根据主键获取记录,如果是非整型主键db.First(&user, "id = ?", "string_primary_key")// SELECT * FROM users WHERE id = 'string_primary_key';// Plain SQLdb.Find(&user, "name = ?", "jinzhu")// SELECT * FROM users WHERE name = "jinzhu";db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;// Structdb.Find(&users, User{Age: 20})// SELECT * FROM users WHERE age = 20;// Mapdb.Find(&users, map[string]interface{}{"age": 20})// SELECT * FROM users WHERE age = 20;
db.Not("name = ?", "jinzhu").First(&user)// SELECT * FROM users WHERE NOT name = "jinzhu" ORDER BY id LIMIT 1;// Not Indb.Not(map[string]interface{}{"name": []string{"jinzhu", "jinzhu 2"}}).Find(&users)// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");// Structdb.Not(User{Name: "jinzhu", Age: 18}).First(&user)// SELECT * FROM users WHERE name <> "jinzhu" AND age <> 18 ORDER BY id LIMIT 1;// 不在主键切片中的记录db.Not([]int64{1,2,3}).First(&user)// SELECT * FROM users WHERE id NOT IN (1,2,3) ORDER BY id LIMIT 1;
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';// Structdb.Where("name = 'jinzhu'").Or(User{Name: "jinzhu 2", Age: 18}).Find(&users)// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);// Mapdb.Where("name = 'jinzhu'").Or(map[string]interface{}{"name": "jinzhu 2", "age": 18}).Find(&users)// SELECT * FROM users WHERE name = 'jinzhu' OR (name = 'jinzhu 2' AND age = 18);
db.Select("name", "age").Find(&users)// SELECT name, age FROM users;db.Select([]string{"name", "age"}).Find(&users)// SELECT name, age FROM users;db.Table("users").Select("COALESCE(age,?)", 42).Rows()// SELECT COALESCE(age,'42') FROM users;
db.Order("age desc, name").Find(&users)// SELECT * FROM users ORDER BY age desc, name;// 多个 orderdb.Order("age desc").Order("name").Find(&users)// SELECT * FROM users ORDER BY age desc, name;db.Clauses(clause.OrderBy{ Expression: clause.Expr{SQL: "FIELD(id,?)", Vars: []interface{}{[]int{1, 2, 3}}, WithoutParentheses: true},}).Find(&User{})// SELECT * FROM users ORDER BY FIELD(id,1,2,3)
Limit 指定获取记录的最大数量 Offset 指定在开始返回记录之前要跳过的记录数量
db.Limit(3).Find(&users)// SELECT * FROM users LIMIT 3;// 通过 -1 消除 Limit 条件db.Limit(10).Find(&users1).Limit(-1).Find(&users2)// SELECT * FROM users LIMIT 10; (users1)// SELECT * FROM users; (users2)db.Offset(3).Find(&users)// SELECT * FROM users OFFSET 3;db.Limit(10).Offset(5).Find(&users)// SELECT * FROM users OFFSET 5 LIMIT 10;// 通过 -1 消除 Offset 条件db.Offset(10).Find(&users1).Offset(-1).Find(&users2)// SELECT * FROM users OFFSET 10; (users1)// SELECT * FROM users; (users2)
db.Model(&User{}).Select("name, sum(age) as total").Group("name").Having("name = ?", "group").Find(&result)// SELECT name, sum(age) as total FROM `users` GROUP BY `name` HAVING name = "group"
db.Distinct("name", "age").Order("name, age desc").Find(&results)
type result struct { Name string Email string}db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.idrows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()for rows.Next() { ...}db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)// 带参数的多表连接db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
(14) scan
Scan 把查询结果存至 struct,用法与 Find 类似。使用 Scan 方法的时候需要我们显示指定数据库的表名。回调函数注册的不一样,Find 函数支持更多的 Callback 注入。
Scan支持的数据类型仅为struct及struct slice以及它们的指针类型。
type Result struct { Name string Age int}var result Resultdb.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)// 原生 SQLdb.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
GORM 允许在 Table 方法中通过 FROM 子句使用子查询
db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18}).Find(&User{})// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18subQuery1 := db.Model(&User{}).Select("name")subQuery2 := db.Model(&Pet{}).Select("name")db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
GORM 允许保存查询结果到 map[string]interface{} 或 []map[string]interface{},此时需要指定 Model 或 Table
var result map[string]interface{}db.Model(&User{}).First(&result, "id = ?", 1)var results []map[string]interface{}db.Table("users").Find(&results)
GORM 支持通过行进行迭代
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()defer rows.Close()for rows.Next() { var user User // ScanRows 方法用于将一行记录扫描至结构体 db.ScanRows(rows, &user) // 业务逻辑...}
Count 用于获取匹配的记录数
var count int64db.Model(&User{}).Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Count(&count)// SELECT count(1) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'// Count with Distinctdb.Model(&User{}).Distinct("name").Count(&count)// SELECT COUNT(DISTINCT(`name`)) FROM `users`db.Table("deleted_users").Select("count(distinct(name))").Count(&count)// SELECT count(distinct(name)) FROM deleted_users
Save 会保存所有的字段,即使字段是零值
user.Name = "jinzhu 2"user.Age = 100db.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;
当使用 Update 更新单个列时,你需要指定条件,否则会返回 ErrMissingWhereClause 错误。当使用了 Model 方法,且该对象主键有值,该值会被用于构建条件。
// 条件更新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 的 ID 是 `111`db.Model(&user).Update("name", "hello")// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
Updates 方法支持 struct 和 map[string]interface{} 参数。当使用 struct 更新时,默认情况下,GORM 只会更新非零值的字段,可以使用Select来更新指定指定字段。
// 根据 `struct` 更新属性,只会更新非零值的字段// 只会更新Model指定的主键值对应的记录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;// 根据 `map` 更新属性result := db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})// UPDATE users SET name='hello', age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;result.RowsAffected // 更新的记录数result.Error // 更新的错误
db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"))// UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);
删除一条记录时,删除对象需要指定主键,否则会触发批量删除
// 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";
GORM 允许通过内联条件指定主键来检索对象,但只支持整型数值,因为 string 可能导致 SQL 注入。
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);
db.Transaction(func(tx *gorm.DB) error { // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db') if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil { // 返回任何错误都会回滚事务 return err } if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil { return err } // 返回 nil 提交事务 return nil})
// 开始事务tx := db.Begin()// 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')tx.Create(...)// ...// 遇到错误时回滚事务tx.Rollback()// 否则,提交事务tx.Commit()
来源
:https://blog.csdn.net/zuzhiang/article/details/122539066