文章目录
- 教程-基本操作
- 1、链式操作
- 1.1、链式操作
- 1.2、创建方法
- 1.3、Scopes方法
- 1.4、多个创建方法
- 1.5、线程安全
- 2、错误处理
- 2.1、错误处理
- 2.2、错误
- 2.3、RecordNotFound错误
- 3、钩子
- 3.1、对象的生命周期
- 3.2、钩子
- 3.3、更新一个对象
- 3.4、删除一个对象
- 3.5、查询一个对象
- 4、事务
- 4.1、事务
- 4.2、具体例子
- 5、数据库迁移
- 5.1、自动迁移
- 5.2、其他数据库迁移宫剧
- 5.3、表结构的方法
- 6、原生SQL和SQL生成器
- 6.1、运行原生SQL
- 7、通用数据接口
- 8、复合主键
- 9、自定义Logger
- 9.1、Logger
- 9.2、自定义Logger
教程-基本操作
1、链式操作
1.1、链式操作
Gorm继承了链式操作接口,可以像拼接似的进行代码的书写,如下:
db, err := gorm.Open("postgres", "user=gorm dbname gomr sslmode=disable")
// 创建一个新的关系
tx := db.Where("name = ?", "zhihui")
// 新增更多的筛选条件
if someCondition {
tx = tx.Where("age = ?", 22)
} else {
tx = tx.Where("age = ?", 18)
}
if yetAnotherCondition {
tx = tx.Where("active = ?", 1)
}
这样的写法,直到调用立即方法之前都不会产生查询,在某些场景下会十分方便实用,就像我们可以封装一个包来处理常见的逻辑一样!
1.2、创建方法
创建方法就是那些会产生SQL查询并且发送到数据库,通常就是一些CRUD方法,就像:Create、First、Find、Take、Save、UpdateXXX、Delete、Scan、Row、Rows...
在下方,我们通过创建方法的举一个例子:
tx.Find(&user) // 相当于生成: select * from user where name = "zhihui" and age = 18 and active = 1;
1.3、Scopes方法
Scope方法是基于链式操作理论创建的,使用其可以提取一些通用逻辑,创建一些实用性更强的库。
func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}
func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}
func PaidWithCod(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}
func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
return func (db *gorm.DB) *gorm.DB {
return db.Scopes(AmountGreaterThan1000).Where("status in (?)", status)
}
}
// ----------------------具体实操-------------------------- //
db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// 查找所有大于1000的信用卡订单和金额
db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// 查找所有大于1000的 COD 订单和金额
db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// 查找大于1000的所有付费和运单
1.4、多个创建方法
当使用Gorm的创建方法,后面创建的方法将复用前面的创建方法的搜索条件(不包含内联条件)
db.Where("name like ?", "zhihui%").Find(&user, "id in (?)", []int{1, 2, 3}).Count(&count)
相当于执行如下的SQL操作:
select * from users where name like "zhihui%" and id in (1, 2, 3)
select count(*) from users where name like "zhihui%"
1.5、线程安全
所有的链式操作都将会克隆并创建一个新的数据库对象(共享一个连接池),Gorm对于多个goroutines的并发使用是安全的。
2、错误处理
在Go语言中,错误处理是尤为重要的,鼓励人们在任何创建方法之后去检查错误!
2.1、错误处理
由于Gorm的链式API,GORM中的错误处理与惯用的Go代码不同,但它仍然相当容易 => 如果发生任何错误,Gorm会将其设置为*
的Error
字段,你可以在这样检查:
if err := db.Where("name = ?", "zhihui").First(&user).Error; err != nil {
// error handing...
}
或者
if result := db.Where("name = ?", "zhihui").First(&user); result.Error != nil {
// error handing...
}
2.2、错误
在处理数据期间,发生几个错误很普遍,Gorm提供了一个API来讲所有发生的错误作为切片返回!
// 多个错误产生,GetErrors 返回一个 []errors 切片
db.First(&user).Limit(10).Find(&users).GetErrors()
fmt.Println(len(errors))
for _, err := range errors {
fmt.Println(err)
}
2.3、RecordNotFound错误
Gorm提供了一个处理RecordNotFound错误的快捷方法,如果发生错误,它将检查每个错误,如果他们中的任何一个是RecordNotFound错误。
// 检查是否返回RecordNotFound错误
db.Where("name = ?", "hello world").First(&user).RecordNotFound()
if db.Model(&user).Related(&credit_card).RecordNotFount() {
// 数据没有找到
}
if err := db.Where("name = ?", "zhihui").First(&user).Error; gorm.IsRecordNotFoundError(err) {
// 数据没有找到
}
在 GORM 中,如果你使用 First 或 Find 等方法查询记录,当没有找到对应记录时,GORM 将返回一个 RecordNotFound 错误。
为了处理这种情况,我们可以使用 RecordNotFound 方法来检查是否返回了这个错误。
在以上代码片段中:
1、第一个示例演示了如何使用 RecordNotFound 方法来检查是否返回了 RecordNotFound 错误。
2、第二个示例中,我们使用 Related 方法来检索关联记录,并使用 RecordNotFound 方法检查是否找到了记录。
3、最后一个示例展示了如何使用 gorm.IsRecordNotFoundError 函数来检查 First 或 Find 方法是否返回了 RecordNotFound 错误。
3、钩子
3.1、对象的生命周期
钩子是一个在 插入/查询/更新/删除 之前或之后被调用的方法。
如果在一个模型中定义了特殊的方法,它将会在插入、查询、更新、删除的时候被自动调用,如果任何的回调抛出错误,Gorm将会停止将要的执行的操作并且回滚当前的改变。
3.2、钩子
1、创建一个对象
// 开启事务
BeforeSave
BeforeCreate
// 连表前的创建
// 更新时间戳 CreateAt、UpdateAt
// 保存自己本身
// 重载那些有默认值和空的字段
// 连表后的操作
AfterCreate
AfterSave
// 提交或回滚事务
2、例子
func (u *User) BeforeSave() (err error) {
if u.IsVaild() {
err = error.New("can't save invalid date")
}
return
}
func (u *User) AfterCrete(scope *gorm.Scope) (er error) {
if u.ID == 1 {
scope.DB.Model(u).Update("role", "admin")
}
return
}
这是两个 GORM 钩子函数的例子。
1、BeforeSave 是一个 GORM 钩子函数,它在保存之前对数据进行处理。在这个例子中,它检查了用户的数据是否有效,如果无效就返回错误。
2、AfterCreate 是另一个 GORM 钩子函数,它在创建完数据之后对数据进行处理。在这个例子中,它检查了用户是否为第一个创建的用户,如果是的话就将其角色设置为 "admin"。
这两个钩子函数都是在模型方法中定义的,并且它们的名称必须符合 GORM 的命名规则,以便 GORM 可以正确地调用它们。
3、注意事项
在Gorm中 保存/删除
操作会默认进行事务处理,所有在事务中,所有的改变都是无效的,直到它被提交为止!
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
tx.Model(u).Update("role", "admin")
return
}
这段代码是在 GORM 中使用事务处理的一个示例。在 GORM 中,当进行保存/删除操作时,默认会启动事务处理。因此,在使用钩子函数进行操作时,需要将 gorm.DB 对象作为参数传递给钩子函数,以便在事务中进行操作。
在上面的示例中,AfterCreate 钩子函数会在保存 User 对象后被调用,并且会将保存的对象作为参数传递给它。在这个钩子函数中,我们使用了传递的 tx 对象来更新 User 对象的角色为 "admin",并且没有返回任何错误。在事务中的任何操作都需要使用传递的 tx 对象进行操作。
需要注意的是,在钩子函数中进行操作时,一定要小心不要引起死锁或无限循环的问题。此外,如果在事务中操作失败,应该回滚事务并返回相应的错误。
3.3、更新一个对象
// 开启事务
BeforeSave
BeforeUpdate
// 连表前的保存
// 更新时间戳UpdateAt
// 保存自己本身
// 连表后的操作
AfterUpdate
AfterSave
// 提交或回滚事
1、示例:
func (u *User) BeforeUpdate() (err error) {
if u.readonly() {
err = error.New("read only user")
}
return
}
// 在事务结束后,更新数据
func (u *User) AfterUpdate(tx *gorm.DB) (err error) {
if u.Confirmed {
tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("verfied", true)
}
return
}
1、这段代码实现了在 GORM 中使用钩子函数控制 更新 操作。在 BeforeUpdate 钩子函数中,会检查是否是只读用户,如果是,则会返回一个错误,导致更新操作失败。
2、在 AfterUpdate 钩子函数中,会检查用户是否已经被确认,如果是,则会将与该用户相关联的地址信息标记为已验证。
需要注意的是,在 AfterUpdate 钩子函数中使用了 tx 参数来执行数据更新操作,这是因为在钩子函数中无法直接访问数据库连接对象,需要通过参数传递进来。
3.4、删除一个对象
// 开启事务
BeforeDelete
// 删除自身
AfterDelete
// 提交或回滚事务
1、示例:
// 在事务结束后进行更新操作
func (u *User) AfterDelete(tx *gorm.DB) (err error) {
if u.Confirmed {
tx.Model(&Address{}).Where("user_id = ?", u.ID).Update("invaild", false)
}
return
}
3.5、查询一个对象
// 从数据库中读取数据
// 加载之前
AfterFind
1、示例
func (u *User) AfterFind() (err error) {
if u.MemberShip == "" {
u.MemberShip = "user"
}
return
}
4、事务
Gorm中默认在事务中执行单个create、update、delete操作,以确保数据库完整性,如果想把多个create、update、delete当成一个原子性操作,那么trnsaction
就是为了这个而创建的!
4.1、事务
要在事务中执行一组操作,正常的流程如下所示:
// 开启事务
tx := db.Begin()
// 在事务中执行一些数据库操作(从这里开始使用‘tx’,而不是‘db’)
tx.Create(...)
// ...
// 发生错误回滚事务
tx.Rollback()
// 或者提交这个事务
tx.Commit()
4.2、具体例子
func CreateAnimals(db *gorm.DB) err {
// 注意在事务中要使用 tx 作为数据库句柄
tx := db.Begin()
defer func() {
if r := recover(), r != nil {
tx.Rollback()
}
}()
if tx.Error != nil {
return err
}
if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
tx.Rollback()
return err
}
if err := tx.Create(&Animals{Name:"Lion"}).Error; err != nil {
tx.Rollback()
return err
}
}
以上代码是一个创建动物的函数,采用了 GORM 中的事务机制,确保在创建动物时,要么全部创建成功,要么全部失败。
这个函数中采用了 db.Begin() 方法创建事务,如果在函数执行过程中出现了 panic 异常,会调用 defer 语句中的 tx.Rollback() 方法,撤销当前事务。如果在函数执行过程中没有出现异常,tx.Commit() 方法会提交事务。
在函数的主体中,使用了 tx.Create() 方法创建动物,如果出现错误,tx.Rollback() 方法会撤销当前事务。
5、数据库迁移
5.1、自动迁移
使用migrate来维持表结构一直处于最新状态(migrate仅支持创建表、添加表中没有的字段和索引),为了保护数据,它不支持改变已有的字段类型或删除未被使用的字段!
db.AutoMigrate(&User{})
db.AutoMigrate(&User{}, &Product{}, &Order{})
// 创建表的时候,添加表的后缀
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
这些代码是用于在 GORM 中执行数据库迁移的。GORM 支持通过代码自动创建表格并同步数据库模型。其中:
1、db.AutoMigrate(&User{}) 创建名为 "users" 的表格,该表格包含模型 "User" 中定义的所有字段,如果表格不存在,则创建表格。如果模型中有新增的字段,则自动同步到表格中。
2、db.AutoMigrate(&User{}, &Product{}, &Order{}) 与上面的语句类似,但是在一个语句中可以创建多个表格。
3、db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{}) 在创建表格时可以设置表格选项。在这个例子中,表格选项是 "ENGINE=InnoDB",意思是使用 InnoDB 存储引擎来创建表格。
这些代码主要用于将 GORM 模型映射到数据库表格中。通过这些语句,我们可以自动创建数据库表格,并将它们与我们的 GORM 模型同步。这样,我们就可以方便地使用 GORM 进行数据库操作。
5.2、其他数据库迁移宫剧
Gorm的数据库迁移工具能够支持主要的数据库,但如果要寻找更多的迁移工具,Gorm会提供数据库结构,可能会有所帮助。
// 返回 *
db.DB()
5.3、表结构的方法
1、Has Table
// 检查模型中 User 表是否存在
db.HasTable(&user{})
// 检查users表是否存在
db.HasTable("users")
2、Create Table
// 通过模型 User 创建表
db.CreateTable(&user{})
// 在创建users表的时候,会在SQL语句中拼接上`ENGINE=InnoDB`
db.Set("gorm:table_options", "ENGINE=InnoDB").CreateTable(&User{})
3、Drop table
// 删除模型 User 表
(&User{})
// 删除 users 表
("users")
// 删除模型 User 表和 products 表
(&User{}, "products")
4、ModifyColumn
以给定的值来定义字段类型
// User 模型,改变 description 字段的数据类型为 `text`
(&User{}).ModifyColumn("description", "text")
5、DropColumn
// User 模型,删除 description 字段
(&User{}).DropColumn("description")
6、Add Indexes
// 为 `name` 字段建立一个名叫 `idx_user_name` 的索引
(&User{}).AddIndex("idx_user_name", "name")
// 为 `name`, `age` 字段建立一个名叫 `idx_user_name_age` 的索引
(&User{}).AddIndex("idx_user_name_age", "name", "age")
// 添加一条唯一索引
(&User{}).AddUniqueIndex("idx_user_name", "name")
// 为多个字段添加唯一索引
(&User{}).AddUniqueIndex("idx_user_name_age", "name", "age")
7、Remove Index
// 移除索引
(&User{}).RemoveIndex("idx_user_name")
8、Add Foreign Key
// 添加主键
// 第一个参数 : 主键的字段
// 第二个参数 : 目标表的 ID
// 第三个参数 : ONDELETE
// 第四个参数 : ONUPDATE
(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")
9、Remove ForeignKey
(&User{}).RemoveForeignKey("city_id", "cities(id)")
6、原生SQL和SQL生成器
6.1、运行原生SQL
执行原生SQL的时候不能通过链式调用其他方法:
db.Exec("drop table users;")
db.Exec("update orders set shipped_at = ? where id in (?)", time.Now(), []int64{11, 22, 33})
//--------------------分割--------------------//
// Scan
type Result struct {
Name string
Age int
}
var result Result
db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)
这段代码使用了 GORM 的 Raw 方法,可以直接执行 SQL 命令。Scan 方法将查询结果扫描到指定的变量中,以便后续使用。
具体来说,上面的代码执行了一个查询语句,查询名字为 3 的用户的姓名和年龄。查询结果扫描到了 Result 结构体变量 result 中。Result 结构体定义了两个字段 Name 和 Age,对应查询结果中的两个字段。
需要注意的是,Scan 方法的参数是一个指针,指向需要扫描的结果变量。如果查询结果不为空,Scan 方法会将结果扫描到这个变量中。如果查询结果为空,则不会进行任何操作。
1、 和
使用* 或者 *获得查询结果
row := db.Table("users").Where("name = ?", "zhihui").Select("name, age").Row() // *
row.Scan(&name, &age)
rows, err := db.Model(&User{}).Where("name = ?", "zhihui").Select("name, age, email").Rows() // (*, error)
defer rows.Close()
for rows.Next() {
...
rows.Scan(&name, &age, &email)
...
}
// 原生SQL
rows, err := db.Raw("select name, age, email from users where name = ?", "zhihui").Rows() // (*, error)
defer rows.Close()
for rows.Next() {
...
rows.Scan(&name, &age, &email)
...
}
这段代码演示了如何使用Gorm查询数据,并使用Scan方法将查询结果映射到结构体中,或使用原生SQL查询数据。
首先,我们定义了一个Result结构体来存储查询结果,它包括Name和Age两个字段。`接着,使用Raw方法来执行原生SQL查询,将查询结果使用Scan方法映射到Result结构体中。`
此外,还演示了如何使用Model方法和Rows方法来查询数据。Model方法指定要查询的模型,Where方法指定查询条件,Select方法指定要查询的字段,并使用Rows方法执行查询并返回一个结果集。
无论是使用Rows方法还是使用Raw方法,都需要在查询完毕后关闭结果集。在Rows方法中,我们使用defer语句来确保在函数结束前关闭结果集。在使用Raw方法时,也应该在处理完查询结果后及时关闭结果集。
总之,这些演示了Gorm中常用的查询方法,包括使用结构体映射查询结果和使用原生SQL查询数据。这些方法可以大大简化数据库操作,提高开发效率。
2、扫描数据到模型
rows, err := db.Model(&User{}).Where("name = ?", "zhihui").Select("name, age, email").Rows() // (*, error)
defer rows.Close()
for rows.Next() {
var user User
// ScanRows 扫描一行到 user 模型
db.ScanRows(rows, &user)
// do something
}
7、通用数据接口
Gorm提供了当前连接中返回通用的数据库接口方法
DB
(如果底层数据库连接不是*,就像在事务中,他将返回nil)
// 获取数据库对象 来使用他的 () 方法
// ping
db.DB().Ping()
1、连接池
// SetMaxIdleConns 设置空闲连接池中的最大连接数。
db.DB().SetMaxIdleConns(10)
// SetMaxOpenConns 设置数据库连接最大打开数。
db.DB().SetMaxOpenConns(100)
// SetConnMaxLifetime 设置可重用连接的最长时间
db.DB().SetConnMaxLifetime(time.Hour)
8、复合主键
通过设置多个字段为主键,开启复合主键功能。
type Product struct {
ID string gorm:"primary_key"
LanguageCode string gorm:"primary_key"
Code string
Name string
}
9、自定义Logger
9.1、Logger
Gorm建立了对Logger的支持,默认模式只会在错误发生的时候打印日志。
// 开启Logger,以展示详细的日志
db.Logger(ture)
// 关闭Logger
db.Logger(false)
// 对某个操作展示详细的日志,用来排查该操作的问题
db.Debug().Where("name = ?", "zhihui").First(&User{})
9.2、自定义Logger
例如使用Revel的Logger作为Gorm的输出
db.SetLogger(gorm.Logger{revel.TRACE})
使用作为输出
db.SetLogger(log.New(os.Stdout, "\r\n", 0))
至此,我们完成了Gorm部分的学习,能够大致明白,如何通过Gorm来实现对数据库的操作,简化我们开发,提高开发效率。当然需要我们进一步在实际开发过程中去实践,才能更好的将这些知识转变为自己的东西!