一、GKH三件套介绍
二、GORM的基本使用
package main
import (
"gorm.io/driver/mysql" // 驱动层
"gorm.io/gorm" // 访问层
// 使用database/sql库、gorm库访问mysql的驱动
/* 驱动层才是直接帮我们去访问的数据库的地方,而gorm库和database/sql这两个库只是在驱动层之上再封装一层,提供统一访问层,这样之后如果我们想更换数据库,比如从mysql切换到sqlserver,只需要改下引入的驱动库,而不是修改任何操作数据库的代码*/
)
// 定义gorm model
type Product struct {
Code string
Price uint
}
// 为model定义表名
func (p Product) Tablename() string {
return "prouduct"
}
func main() {
// 连接数据库
db, err := gorm.Open(
mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),
&gorm.Config{})
if err != nil {
panic("faliled to connect database")
}
// 创建数据
db.Create(&Product{Code: "D42", Price: 100})
// 查询数据
var product Product
db.First(&product, 1) // 根据整型主键查找
db.First(&product, "code = ?", "D42") // 查找code字段值为D42的记录
// 更新数据,更新多个字段
db.Model(&product).Update("Price", 200)
db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零字段
db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
// 删除数据, 删除product
db.Delete(&product, 1)
}
struct结构体对应数据库中的一个表
- 结构体中的每一个字段(Code、Price)对应数据库中的每一个字段
定义TableName()函数作为接口
-
如果没有定义表名的话,那就使用结构体的蛇形复数作为表名。
-
蛇形命名法就是这种user_info,还有驼峰命名法,比如userInfo(小驼峰)
gorm.Open()
- gorm.Open通过调用不同的驱动来支持不同的数据库,在这里的话,是通过调用mysql驱动调用的Open方法,然后去打开一个mysql的数据库链接,Open方法里面第一个参数需要传递dsn,第二个参数就是gorm.Config,我们可以通过它去传递一些自定义配置
- dsn的全称是数据源名称(Data Source Name)
它是一个表示 ODBC 连接的符号名称。 连接到 ODBC 时,它会存储连接详细信息,例如数据库名称、目录、数据库驱动程序、UserID、密码等。 - dsn的格式:name + password@tcp(数据库的地址)/数据库的名称、用问号分隔符去传递一些基本的参数,比如说它的编码和是否解析时间以及时区。
- 通过gorm.Open去初始化一个数据库的链接,当我们输入错误的时候,也就会出现错误,如果err != nil的话,就panic一下,panic里面可以加一些错误的信息,这样的话,我们就初始化了一个gorm的对象,用它来帮助我们去做一些数据库的基本操作。
- 小结一下,所以说db是一个gorm对象,这个对象是通过gorm.Open方法得来的。调用这个方法,能够得到一个gorm对象(也就是db),这个gorm对象能够帮助我们去做一些数据库的基本操作。
创建数据
- gorm提供了我们一个db.Create的方法,它支持传递一条数据和多条数据,区别是创建一条数据的时候,我们传递的是一个对象,当我们创建多条数据的时候,传递的是一个切片。
- 创建一个Product对象,对象里面的Code赋值为D42,Price赋值为100。
查询数据
-
db.First(&product, 1),注意:这里传的要是指针,因为gorm要把查询到的字段,反写回结构体里面,如果没有传指针的话,是写不回去的,第二个参数是会默认传递一个条件,如果传递的是整型的话,那就用整型主键去查找。
-
第二个参数如果是整型,就表示的是主键;如果是条件表达式,那就根据条件去查询
-
"code = ?","D42"是查询条件
-
First只能查询单条记录,查询多条记录要用Model。
更新数据
- Update的第一参数是column,第二参数是value,column是指字段名,value是指字段对应的值
- Updates里面放的就是结构体了
- gorm仅更新零值,零值是指:如果是整型,那么就是0;如果是字符串,那就是”“空字符串;
- 使用map的话,就算要更新零值,也可以去更新
三、GORM创建数据
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 定义gorm model
type Product struct {
ID uint `gorm:"primarykey"`
Code string `gorm:"column: code"`
Price uint `gorm:"column: user_id"`
}
// 为model定义表名
func (p Product) Tablename() string {
return "prouduct"
}
func main() {
// 连接数据库
db, err := gorm.Open(
mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),
&gorm.Config{})
if err != nil {
panic("faliled to connect database")
}
// 创建一条数据
p := &Product{Code: "D42"} // 创建一个结构体对象
res := db.Create(p) // 创建一条数据,返回的是一个gorm对象
fmt.Println(res.Error) // 获取err,由于gorm是链式调用,所以这里是通过res来获取err
fmt.Println(p.ID) // 返回插入数据的主键
// 创建多条
products := []*Product{{Code: "D41"}, {Code: "D42"}, {Code: "D43"}} // 定义list结构体
res = db.Create(products) // 通过Create批量创建数据
fmt.Println(res.Error) // 链式调用,通过res获取err
for _, p := range products {
fmt.Println(p.ID)
}
}
如何使用Upsert?
- 创建的时候,如果遇到一些唯一索引冲突,我们应该如何去处理?
使用Upsert,gorm提供了Upsert的支持,也就是clause.OnConfilict
里面的Donothing也就是什么都不做 - 当我们去调用.Create、.Update、.Updates、.Delete的时候,其实这些都是Finish API,前面的API都是些组合的API,拼SQL的;而动词的API就是真正的需要执行SQL了,如果在Create后面加一个.where的话,是不生效的,因为.Create的时候,SQL已经执行了。
四、GORM查询数据
// 连接数据库
db, err := gorm.Open(
mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),
&gorm.Config{})
if err != nil {
panic("failed to connect database")
}
// 获取第一条记录(主键升序),查询不到数据则返回 ErrRecordNotFound
u := &User{}
db.First(u) // SELECT * FROM users ORDER BY id LIMIT 1;
// 查询多条数据
users := make([]*User, 0)
result := db.Where("age > 10").Find(&users) // SELECT * FROM users where age > 10;
fmt.Println(result.RowsAffected) //返回找到的记录数,相当于 len(users)
fmt.Println(result.Error) // returns error
// IN SELECT * FROM users WHERE name IN ('jinzhu', 'jinzhu 2');
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// LIKE SELECT * FROM users WHERE name LIKE '%jin%';
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
- 链式调用获取到的结果赋值给result,这样我们才能够获得到它的RowsAffected、Error。
五、GORM更新数据
// 条件更新单个列
// UPDATE users SET name='hello', updated_at='2013-11-17 21: 34 : 10' WHERE age > 18;
db.Model(&User{ID:111}).Where("age > ?", age: 18).Update("name", "hello")
// 更新多个列
// 根据'struct'更新属性,只会更新非零值的字段
// UPDATE users SET names='hello', age=18, updated_at = '2014-11-17 21:34:10' WHERE id = 111
db.Model(&User{ID: 111}).Updates(User{Name: "hello", Age: 18})
// 更新多个列
// 根据'map'更新属性
// UPATAE users SET name='hello', age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
db.Model(&User{ID: 111}).Updates(map[string]interface{"name": "hello", "age": 18, "actived": false})
// 更新选定字段
// UPDATE users SET name='hello' WHERE id = 111;
db.Model(&User{ID: 111}).Select("name".).Updates(map[string]interface{}{"name":"hello", "age":18, "actived":false})
// SQL 表达式更新
// UPDATE "products" SET "price" = price * 2 ÷ 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
db.Model(&User{ID: 111}).Update("age", gorm.Expr("age * ? ÷ ?", 2, 100))
- 因为Update只能是去更新单个列,它并没有接口,并没有位置让它去传递表名,所以当使用Update这个单个列的API接口的时候,我们需要使用.Model和.where去设置表名,否则它就会报一个关于没有表名的错误
- 在这里Select选定了"name",所以就只更新name字段
六、GORM删除数据
七、GORM事务
- db.Begin()会返回一个gorm对象,但是我们要注意,使用db.Begin()的时候一定要返回得到一个tx,我们要先固化一个链接,因为golang底层的话使用的是一个连接池,并且去执行开启SQL的语句,所以当执行完这两个操作之后,会返回一个新的db对象,这个对象是已经执行完开启事务的命令,并且它的链接是固化的,就再也不会使用连接池里面的链接了
- 有时候可能分支过多,会忘记写Roolback,或者Commit,从而导致内存泄漏,把数据库打挂等问题,所以Gorm也提供了Tansaction方法用于自动提交事务。
八、GORM Hook
- BeforeCreate用于参数校验