一、数据库
import "github.com/jinzhu/gorm"import _ "github.com/jinzhu/gorm/dialects/mysql"// import _ "github.com/jinzhu/gorm/dialects/postgres"// import _ "github.com/jinzhu/gorm/dialects/sqlite"// import _ "github.com/jinzhu/gorm/dialects/mssql"
1.1. mysql
db, err := gorm.Open("mysql", "user:password@/dbname?charset=utf8&parseTime=True&loc=Local") //parseTime不设置会导致查询参数时报错,loc不设置会导致时区变为0时区defer db.Close()
1.2. PostgreSQL
db, err := gorm.Open("postgres", "host=myhost user=gorm dbname=gorm sslmode=disable password=mypassword")defer db.Close()
1.3. Sqlite3
db, err := gorm.Open("sqlite3", "/tmp/gorm.db")defer db.Close()
二、模型
基本模型定义gorm.Model
// 基本模型的定义type Model struct {ID uint `gorm:"primary_key"`CreatedAt time.TimeUpdatedAt time.TimeDeletedAt *time.Time}
// 你可以将它嵌入到你自己的 Model 中,也可以完全使用自己的 Model。type User struct {gorm.ModelName string}// 不使用gorm.Model定义模型type User struct {ID intName string}
1.1 支持的结构体标记(Struct tags)
| 结构体标记(Tag) | 描述 |
|---|---|
| Column | 指定列名 |
| Type | 指定列数据类型 |
| Size | 指定列大小, 默认值255 |
| PRIMARY_KEY | 将列指定为主键 |
| UNIQUE | 将列指定为唯一 |
| DEFAULT | 指定列默认值 |
| PRECISION | 指定列精度 |
| NOT NULL | 将列指定为非 NULL |
| AUTO_INCREMENT | 指定列是否为自增类型 |
| INDEX | 创建具有或不带名称的索引, 如果多个索引同名则创建复合索引 |
| UNIQUE_INDEX | 和 INDEX 类似,只不过创建的是唯一索引 |
| EMBEDDED | 将结构设置为嵌入 |
| EMBEDDED_PREFIX | 设置嵌入结构的前缀 |
| - | 忽略此字段 |
1.2 关联 struct 的标记(tags)
| 结构体标记(Tag) | 描述 |
|---|---|
| MANY2MANY | 指定连接表 |
| FOREIGNKEY | 设置外键 |
| ASSOCIATION_FOREIGNKEY | 设置关联外键 |
| POLYMORPHIC | 指定多态类型 |
| POLYMORPHIC_VALUE | 指定多态值 |
| JOINTABLE_FOREIGNKEY | 指定连接表的外键 |
| ASSOCIATION_JOINTABLE_FOREIGNKEY | 指定连接表的关联外键 |
| SAVE_ASSOCIATIONS | 是否自动完成 save 的相关操作 |
| ASSOCIATION_AUTOUPDATE | 是否自动完成 update 的相关操作 |
| ASSOCIATION_AUTOCREATE | 是否自动完成 create 的相关操作 |
| ASSOCIATION_SAVE_REFERENCE | 是否自动完成引用的 save 的相关操作 |
| PRELOAD | 是否自动完成预加载的相关操作 |
实例
type User struct {gorm.ModelName string `gorm:"primary_key"` // 设置Name为主键Age sql.NullInt64Birthday *time.TimeEmail string `gorm:"type:varchar(100);unique_index"`Role string `gorm:"size:255"` // 设置字段大小为255MemberNumber *string `gorm:"unique;not null"` // 设置会员号(member number)唯一并且不为空Num int `gorm:"AUTO_INCREMENT"` // 设置 num 为自增类型Address string `gorm:"index:addr"` // 给address字段创建名为addr的索引IgnoreMe int `gorm:"-"` // 忽略本字段}
2.1 默认表名
type User struct {} // 默认表名是`users`// 设置User的表名为`profiles`func (User) TableName() string {return "profiles"}// 全局禁用表名复数db.SingularTable(true) // 如果设置为true,`User`的默认表名为`user`,使用`TableName`设置的表名不受影响//自定义表名设置函数gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string {return "prefix_" + defaultTableName;}// 使用User结构体创建名为`deleted_users`的表db.Table("profiles").CreateTable(&User{})
2.2 列名
type User struct {ID uint `gorm:"column:_id"` // 设置列名为 `_id`Name string // 默认列名为 `name`DeletedAt time.Time // 默认列名为 `deleted_at`CreatedAt time.Time // 默认列名为 `created_at`}
注意:
在创建数据时,将会自动设置CreatedAt为当前时间.
在删除数据时,将会自动设置DeletedAt为当前时间,而不是直接将记录从数据库中删除
3.1 外键
type Todo struct {Id uint `gorm:"primary_key;unique;not null"`Records []Record `gorm:"ForeignKey:TodoId"`...}type Record struct {TodoId uint...}使用`gorm:"ForeignKey:TodoId"`将TodoID设置为外键,并于Todo表的Id(主键)关联。`gorm:"ForeignKey:TodoId;AssociationForeignKey:Id"`AssociationForeignKey可以设置非主键字段关联外联
设置关联之后,使用 Create创建记录Todo时,可以自动创建todos和records表的记录
查询Todo表时,需要使用联表查询,否则Records字段为空
var todo internal.Todoerr := GetDBClient().Preload("Records").First(&todo, id).Errorif err != nil {return internal.Todo{}, err}
关联
// 根据Todo表中的主键查询record表中相关联的数据GetDBClient().Model(&todo).Association("Records").Find(&records)// 添加关联GetDBClient().Model(&todo).Association("Records").Append(&records{Name: "DE"})// 删除关联GetDBClient().Model(&todo).Association("Records").Delete(&records{Name: "DE"})// 替换关联GetDBClient().Model(&todo).Association("Records").Replace(&records{Name: "DE"})// 关联计数GetDBClient().Model(&todo).Association("Records").Count()// 删除关联GetDBClient().Model(&todo).Association("Records").Clear()
三、操作
3.1. 自动迁移
自动迁移模式将保持更新到最新。 :::tips 修改User{}之后,自动迁移仅仅会创建表,创建缺少列和索引,并且不会改变现有列的类型或删除未使用的列以保护数据。 :::
db.AutoMigrate(&User{})db.AutoMigrate(&User{}, &Product{}, &Order{})// 创建表时添加表后缀db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
3.2. 检查表是否存在
// 检查模型`User`表是否存在db.HasTable(&User{})// 检查表`users`是否存在db.HasTable("users")
3.3. 创建表
// 为模型`User`创建表db.CreateTable(&User{})// 创建表`users'时将“ENGINE = InnoDB”附加到SQL语句db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&User{})
3.4. 删除表
// 删除模型`User`的表db.DropTable(&User{})// 删除表`users`db.DropTable("users")// 删除模型`User`的表和表`products`db.DropTableIfExists(&User{}, "products")
3.5. 修改列
修改列的类型为给定值
// 修改模型`User`的description列的数据类型为`text`db.Model(&User{}).ModifyColumn("description", "text")
3.6. 删除列
// 删除模型`User`的description列db.Model(&User{}).DropColumn("description")
3.7. 添加外键
// 添加主键// 1st param : 外键字段// 2nd param : 外键表(字段)// 3rd param : ONDELETE// 4th param : ONUPDATEdb.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")
3.8. 索引
// 为`name`列添加索引`idx_user_name`db.Model(&User{}).AddIndex("idx_user_name", "name")// 为`name`, `age`列添加索引`idx_user_name_age`db.Model(&User{}).AddIndex("idx_user_name_age", "name", "age")// 添加唯一索引db.Model(&User{}).AddUniqueIndex("idx_user_name", "name")// 为多列添加唯一索引db.Model(&User{}).AddUniqueIndex("idx_user_name_age", "name", "age")// 删除索引db.Model(&User{}).RemoveIndex("idx_user_name")
四、CRUD
4.1 创建
4.1.1 创建记录
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}db.NewRecord(user) // 判断主键是否为空db.Create(&user) // 插入数据,注意:这里必须使用&
4.1.2 默认值
type Animal struct {ID int64Name string `gorm:"default:'galeone'"`Age int64}
注意 所有字段的零值, 比如 0, ‘’, false 或者其它 零值,都不会保存到数据库内,但会使用他们的默认值。 如果你想避免这种情况,可以考虑使用指针或实现 Scanner/Valuer 接口,比如:
// 使用指针type User struct {gorm.ModelName stringAge *int `gorm:"default:18"`}// 使用 Scanner/Valuertype User struct {gorm.ModelName stringAge sql.NullInt64 `gorm:"default:18"` // sql.NullInt64 实现了Scanner/Valuer接口}
4.2 查询
// 根据主键查询第一条记录db.First(&user)//// SELECT * FROM users ORDER BY id LIMIT 1;// 查询指定的某条记录(仅当主键为整型时可用)db.First(&user, 10)//// SELECT * FROM users WHERE id = 10;// 随机获取一条记录db.Take(&user)//// SELECT * FROM users LIMIT 1;// 根据主键查询最后一条记录db.Last(&user)//// SELECT * FROM users ORDER BY id DESC LIMIT 1;// 查询所有的记录db.Find(&users)//// SELECT * FROM users;db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)//// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
4.2.1 Where
// 获取所有匹配的记录db.Where("name = ?", "jinzhu").Find(&users)//// SELECT * FROM users WHERE name = 'jinzhu';// INdb.Where("name in (?)", []string{"jinzhu", "jinzhu 2"}).Find(&users)// LIKEdb.Where("name LIKE ?", "%jin%").Find(&users)// ANDdb.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)// Timedb.Where("updated_at > ?", lastWeek).Find(&users)db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)// 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 或者其他 零值时,将不会被用于构建查询条件
例
var guest []Guesterr := db.Where("username=?", "123").Find(&guest).Errorif err != nil {panic(err)}fmt.Println(guest)
4.2.2 Not条件查询
db.Not("name", "jinzhu").First(&user)//// SELECT * FROM users WHERE name <> "jinzhu" LIMIT 1;// Not Indb.Not("name", []string{"jinzhu", "jinzhu 2"}).Find(&users)//// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
4.2.3 Or条件查询
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)//// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
4.2.4 select
db.Select("name, age").Find(&users)//// SELECT name, age FROM users;
4.2.5 order
db.Order("age desc, name").Find(&users)//// SELECT * FROM users ORDER BY age desc, name;// Multiple ordersdb.Order("age desc").Order("name").Find(&users)//// SELECT * FROM users ORDER BY age desc, name;
4.2.6 Limit
db.Limit(3).Find(&users)//// SELECT * FROM users LIMIT 3;
4.2.7 offset
db.Offset(3).Find(&users)//// SELECT * FROM users OFFSET 3;
4.2.8 count
var count intdb.Find(&users).Count(&count)
4.2.9 join
rows, 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)
FirstOrInit不存在就生成struct
获取第一个匹配的记录,或者使用给定的条件初始化一个新的记录(仅适用于struct,map条件)
// Unfounddb.FirstOrInit(&user, User{Name: "non_existing"})//// user -> User{Name: "non_existing"}// Founddb.Where(User{Name: "Jinzhu"}).FirstOrInit(&user)//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}db.FirstOrInit(&user, map[string]interface{}{"name": "jinzhu"})//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
Attrs
如果未找到记录,则使用参数初始化结构
// Unfounddb.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrInit(&user)//// SELECT * FROM USERS WHERE name = 'non_existing';//// user -> User{Name: "non_existing", Age: 20}db.Where(User{Name: "non_existing"}).Attrs("age", 20).FirstOrInit(&user)//// SELECT * FROM USERS WHERE name = 'non_existing';//// user -> User{Name: "non_existing", Age: 20}// Founddb.Where(User{Name: "Jinzhu"}).Attrs(User{Age: 30}).FirstOrInit(&user)//// SELECT * FROM USERS WHERE name = jinzhu';//// user -> User{Id: 111, Name: "Jinzhu", Age: 20}
Assign
将参数分配给结果,不管它是否被找到
// Unfounddb.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrInit(&user)//// user -> User{Name: "non_existing", Age: 20}// Founddb.Where(User{Name: "Jinzhu"}).Assign(User{Age: 30}).FirstOrInit(&user)//// SELECT * FROM USERS WHERE name = jinzhu';//// user -> User{Id: 111, Name: "Jinzhu", Age: 30}
FirstOrCreate不存在就创建
获取第一个匹配的记录,或创建一个具有给定条件的新记录(仅适用于struct, map条件)
// Unfounddb.FirstOrCreate(&user, User{Name: "non_existing"})//// INSERT INTO "users" (name) VALUES ("non_existing");//// user -> User{Id: 112, Name: "non_existing"}// Founddb.Where(User{Name: "Jinzhu"}).FirstOrCreate(&user)//// user -> User{Id: 111, Name: "Jinzhu"}
Attrs
如果未找到记录,则为参数分配结构
// Unfounddb.Where(User{Name: "non_existing"}).Attrs(User{Age: 20}).FirstOrCreate(&user)//// SELECT * FROM users WHERE name = 'non_existing';//// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);//// user -> User{Id: 112, Name: "non_existing", Age: 20}// Founddb.Where(User{Name: "jinzhu"}).Attrs(User{Age: 30}).FirstOrCreate(&user)//// SELECT * FROM users WHERE name = 'jinzhu';//// user -> User{Id: 111, Name: "jinzhu", Age: 20}
Assign
将其分配给记录,而不管它是否被找到,并保存回数据库。
// Unfounddb.Where(User{Name: "non_existing"}).Assign(User{Age: 20}).FirstOrCreate(&user)//// SELECT * FROM users WHERE name = 'non_existing';//// INSERT INTO "users" (name, age) VALUES ("non_existing", 20);//// user -> User{Id: 112, Name: "non_existing", Age: 20}// Founddb.Where(User{Name: "jinzhu"}).Assign(User{Age: 30}).FirstOrCreate(&user)//// SELECT * FROM users WHERE name = 'jinzhu';//// UPDATE users SET age=30 WHERE id = 111;//// user -> User{Id: 111, Name: "jinzhu", Age: 30}
4.3 更新
4.3.1 更新全部字段
db.First(&user)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;
4.3.2 更新部分字段
注意: user作为查询字段,查询时只会使用user的主键进行查询,若主键为空值,则修改所有字段。
更新单个字段Update
// 更新单个属性(如果更改)db.Model(&user).Update("name", "hello")//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;// 使用组合条件更新单个属性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;
更新多个字段Updates
// 使用`map`更新多个属性,只会更新这些更改的字段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;// 警告:当使用struct更新时,FORM将仅更新具有非空值的字段// 对于下面的更新,什么都不会更新为"",0,false是其类型的空白值db.Model(&user).Updates(User{Name: "", Age: 0, Actived: false})
4.3.3 批量更新
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);// 使用struct更新仅适用于非零值,或使用map[string]interface{}db.Model(User{}).Where("password = ?", 234).Updates(User{Name: "hello", Age: 18})//// UPDATE users SET name='hello', age=18;
4.4 删除
警告 删除记录时,需要确保其主要字段具有值,GORM将使用主键删除记录,如果主要字段为空,GORM将删除模型的所有记录
如果模型有DeletedAt字段,它将自动获得软删除功能! 那么在调用Delete时不会从数据库中永久删除,而是只将字段DeletedAt的值设置为当前时间。但查询数据时,不会查询到已经删除的数据。
4.4.1 单个删除
// 删除存在的记录db.Delete(&email)//// DELETE from emails where id=10;
4.4.2 批量删除
db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})//// DELETE from emails where email LIKE "%jinhu%";db.Delete(Email{}, "email LIKE ?", "%jinzhu%")//// DELETE from emails where email LIKE "%jinhu%";
4.4.3 永久删除
// 使用Unscoped查找软删除的记录db.Unscoped().Where("age = 20").Find(&users)//// SELECT * FROM users WHERE age = 20;// 使用Unscoped永久删除记录db.Unscoped().Delete(&order)//// DELETE FROM orders WHERE id=10;
五、回调
5.1. 创建对象
创建过程中可用的回调
// begin transaction 开始事物BeforeSaveBeforeCreate// save before associations 保存前关联// update timestamp `CreatedAt`, `UpdatedAt` 更新`CreatedAt`, `UpdatedAt`时间戳// save self 保存自己// reload fields that have default value and its value is blank 重新加载具有默认值且其值为空的字段// save after associations 保存后关联AfterCreateAfterSave// commit or rollback transaction 提交或回滚事务
5.2. 更新对象
更新过程中可用的回调
// begin transaction 开始事物BeforeSaveBeforeUpdate// save before associations 保存前关联// update timestamp `UpdatedAt` 更新`UpdatedAt`时间戳// save self 保存自己// save after associations 保存后关联AfterUpdateAfterSave// commit or rollback transaction 提交或回滚事务
5.3. 删除对象
删除过程中可用的回调
// begin transaction 开始事物BeforeDelete// delete self 删除自己AfterDelete// commit or rollback transaction 提交或回滚事务
5.4. 查询对象
查询过程中可用的回调
// load data from database 从数据库加载数据// Preloading (edger loading) 预加载(加载)AfterFind
5.5. 回调示例
func (u *User) BeforeUpdate() (err error) {if u.readonly() {err = errors.New("read only user")}return}// 如果用户ID大于1000,则回滚插入func (u *User) AfterCreate() (err error) {if (u.Id > 1000) {err = errors.New("user id is already greater than 1000")}return}
gorm中的保存/删除操作正在事务中运行,因此在该事务中所做的更改不可见,除非提交。 如果要在回调中使用这些更改,则需要在同一事务中运行SQL。 所以你需要传递当前事务到回调,像这样:
func (u *User) AfterCreate(tx *gorm.DB) (err error) {tx.Model(u).Update("role", "admin")return}
func (u *User) AfterCreate(scope *gorm.Scope) (err error) {scope.DB().Model(u).Update("role", "admin")return}
六、其余用法
1.1. 错误处理
执行任何操作后,如果发生任何错误,GORM将其设置为*DB的Error字段
if err := db.Where("name = ?", "jinzhu").First(&user).Error; err != nil {// 错误处理...}// 如果有多个错误发生,用`GetErrors`获取所有的错误,它返回`[]error`db.First(&user).Limit(10).Find(&users).GetErrors()// 检查是否返回RecordNotFound错误db.Where("name = ?", "hello world").First(&user).RecordNotFound()if db.Model(&user).Related(&credit_card).RecordNotFound() {// 没有信用卡被发现处理...}
1.2. 事物
要在事务中执行一组操作,一般流程如下。
// 开始事务tx := db.Begin()// 在事务中做一些数据库操作(从这一点使用'tx',而不是'db')tx.Create(...)// ...// 发生错误时回滚事务tx.Rollback()// 或提交事务tx.Commit()
1.2.1. 一个具体的例子
func CreateAnimals(db *gorm.DB) err {tx := db.Begin()// 注意,一旦你在一个事务中,使用tx作为数据库句柄if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {tx.Rollback()return err}if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {tx.Rollback()return err}tx.Commit()return nil}
1.3. SQL构建
1.3.1. 执行原生SQL
db.Exec("DROP TABLE users;")db.Exec("UPDATE orders SET shipped_at=? WHERE id IN (?)", time.Now, []int64{11,22,33})// Scantype Result struct {Name stringAge int}var result Resultdb.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)
1.3.2. sql.Row & sql.Rows
获取查询结果为*sql.Row或*sql.Rows
row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row)row.Scan(&name, &age)rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)defer rows.Close()for rows.Next() {...rows.Scan(&name, &age, &email)...}// Raw SQLrows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows() // (*sql.Rows, error)defer rows.Close()for rows.Next() {...rows.Scan(&name, &age, &email)...}
1.3.3. 迭代中使用sql.Rows的Scan
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)defer rows.Close()for rows.Next() {var user Userdb.ScanRows(rows, &user)// do something}
1.4. 通用数据库接口sql.DB
从*gorm.DB连接获取通用数据库接口*sql.DB
// 获取通用数据库对象`*sql.DB`以使用其函数db.DB()// Pingdb.DB().Ping()
1.4.1. 连接池
db.DB().SetMaxIdleConns(10)db.DB().SetMaxOpenConns(100)
1.5. 复合主键
将多个字段设置为主键以启用复合主键
type Product struct {ID string `gorm:"primary_key"`LanguageCode string `gorm:"primary_key"`}
1.6. 日志
Gorm有内置的日志记录器支持,默认情况下,它会打印发生的错误
// 启用Logger,显示详细日志db.LogMode(true)// 禁用日志记录器,不显示任何日志db.LogMode(false)// 调试单个操作,显示此操作的详细日志db.Debug().Where("name = ?", "jinzhu").First(&User{})
1.6.1. 自定义日志
参考GORM的默认记录器如何自定义它https://github.com/jinzhu/gorm/blob/master/logger.go
db.SetLogger(gorm.Logger{revel.TRACE})db.SetLogger(log.New(os.Stdout, "\r\n", 0))
