database/sql基础使用
Quick Start
导入的database/sql包,它是Go原生的数据库操作的包,定义连接和操作数据库的接口,导入的mysql包则是数据库驱动包,它实现database/sql包中接口定义的方法,用户无需关注底层实现,只需调用接口定义方法,就可使用api和mysql进程进行通信
连接数据库
导入sql和driver包,通过sql.Open()
传入driver和dsn初始化连接,注意:Open方法只会校验参数语法而非真正建立连接,其次DB引用是并发安全的,并维护空闲连接池,因此Open函数只需调用一次,很少需关闭DB。因此上面的例子将db.Close()
注释掉
DB结构体维护所连接的数据对象,持有连接池维护很多连接,其结构体中成员包括等待连接时间、关闭连接数、最大连接数、最大空闲连接数等,可查看sql的源代码,如图:
查询数据库
分为单行和多行查询,前者通过db.QueryRow()
传入查询条件的参数查询,后者先通过db.Query()
查询,提前通过defer rows.Close()
关闭rows释放资源,然后通过row.Next()
循环读取结果集中数据,代码如图:
rows, err := db.Query("select username,passwd from user where username=?", "ylliao")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.Username, &user.Passwd)
if err != nil {
log.Fatal(err)
}
fmt.Println(user.Username, user.Passwd)
users = append(users, user)
}
插入、更新、删除
插入、更新、删除使用的API是同一个:db.Exec()
,传入参数,组装成不同sql语句,实现不同操作,如下,只需将参数拼装成不同sql语句,再调用Exec
即可
func insertUser(user *User) error {
sqlStr := "insert into user(username, passwd) values (?,?)"
ret, err := db.Exec(sqlStr, user.Username, user.Passwd)
if err != nil {
return err
}
theID, err := ret.LastInsertId() // 新插入数据的id
if err != nil {
return err
}
fmt.Printf("insert success, the id is %d.\n", theID)
return nil
}
func updateUser(user *User) error {
sqlStr := "update user set passwd=? where username = ?"
ret, err := db.Exec(sqlStr, user.Passwd, user.Username)
if err != nil {
return err
}
n, err := ret.RowsAffected() // 操作影响的行数
if err != nil {
return err
}
fmt.Printf("update success, affected rows:%d\n", n)
return nil
}
func deleteUser(user *User) error {
sqlStr := "delete from user where username=?"
ret, err := db.Exec(sqlStr, user.Username)
if err != nil {
return err
}
n, err := ret.RowsAffected() // 操作影响的行数
if err != nil {
return err
}
fmt.Printf("update success, affected rows:%d\n", n)
return nil
}
GORM基础使用
背景知识
核心是设计简洁、功能强大、自由扩展的全功能ORM
基本用法
//操作数据库
db.AutoMigrate(&Product{})
db.Migrator().CreateTable(&Product{})
//创建
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
user.ID // 返回插入数据的主键
result.Error // 返回 error
result.RowsAffected // 返回插入记录的条数
//批量创建
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)
db.CreateInBatches(users, 100)// 数量为 100
for _, user := range users {
user.ID // 1,2,3
}
//读取
var product Product
db.First(&product, 1)//查询id为1的product
db.First(&product,"code = ?","L1212")//查询code为L1212的product
result := db.Find(&users,[]int{1,2,3})
result.RowsAffected//返回找打的记录数
errors.Is(result.Error,gorm.ErrRecordNotFound)//First,last,Take查不到数据
//更新某个字段
db.Model(&product).Update("Price",2000)
db.Model(&product).UpdateColumn("Price",2000)
//更新多个字段
db.Model(&product).Updates(Product{Price:2000,Code:"L1212"})
db.Model(&product).Updates(map[string]interface{}{"Price":2000,"Code":"L1212"})
//批量更新
db.Model(&Product{}).Where("price<?",2000).Updates(map[string]interface{}{"Price":2000})
//删除
db.Delete(&product)
模型定义
模型是标准的 struct,由 Go 的基本数据类型、实现了 Scanner 和 Valuer 接口的自定义类型及其指针或别名组成
惯例约定
GORM 倾向于约定,而不是配置,当然实际是可以自定义配置的。默认情况下,GORM 使用 ID
作为主键,使用结构体名的 snake_cases
复数格式作为表名,字段名的 snake_case
单数格式作为列名,并使用 CreatedAt
、UpdatedAt
、DeletedAt
字段追踪创建、更新时间、删除模式。如图:
关联
//保存用户及其关联
db.Save(&User{Name:'jingzhu',Languages:[]Language{{Name:"zh-CN"},{Name:"en-US"}}})
//关联模式
langAssociation:=db.Model(&user).Association("Language")
//查阅关联
langAssociation.Find(&languages)
//将汉语、英语添加到用户掌握的语言中
langAssociation.Append([]language{languageZH,languageEN})
//吧用户掌握的语言替换为汉语、德语
langAssociation.Replace([]language{languageZH,languageDE})
//删除用户掌握的两个语言
langAssociation.Delete(languageZH,languageEN)
//删除用户所有掌握的语言
langAssociation.Clear()
//用户掌握的语言数量
langAssociation.Count()
//批量模式Append、Replace
var users=[]User{user1,user2,user3}
langAssociation:=db.Model(&users).Association("Languages")
//批量Append模式,参数需和源数据长度相同,如将userA添加到user1的Team,将userB添加到user2的Team,将userA、userB、userC添加到user3的Team
db.Model(&users).Association("Team").Append(&userA,&userB,&[]User{userA,userB,userC})
预加载
type User struct {
gorm.Model
Username string
Orders []Order
}
type Order struct {
gorm.Model
UserID uint
Price float64
}
//查询用户时并找出其订单,个人信息
db.Preload("Orders").Preload("Profile").Preload("Role").Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4); // has many
// SELECT * FROM profiles WHERE user_id IN (1,2,3,4); // has one
// SELECT * FROM roles WHERE id IN (4,5,6); // belongs to
db.Joins("Company", DB.Where(&Company{Alive: true})).Find(&users)
//预加载全部关联(只加载一级关联)
db.Preload(clause.Associations).Find(&users)
//多级加载
db.Preload("Orders.OrderItems.Product").Find(&users)
//多级预加载+预加载全部一级关联
db.Preload("Orders.OrderItems.Product").Preload(clause.Associations).Find(&users)
// 带条件的预加载 Order
db.Preload("Orders", "state NOT IN (?)", "cancelled").Find(&users)
//兹定于预加载SQL
db.Preload("Orders", func(db *gorm.DB) *gorm.DB {
return db.Order("orders.amount DESC")
}).Find(&users)
// SELECT * FROM users;
// SELECT * FROM orders WHERE user_id IN (1,2,3,4) order by orders.amount DESC;
级联删除
//需使用GORM Migrate数据库迁移数据库外键才行
db.AutoMigrate(&User{})
//如果未启用软删除,删除User时自动删除其依赖
db.Delete(&User{})
// 删除 user 时,也删除 user 的
accountdb.Select("Account").Delete(&user)
// 删除 user 时,也删除 user 的 Orders、CreditCards 记录
db.Select("Orders", "CreditCards").Delete(&user)
// 删除 user 时,也删除用户所有 has one/many、many2many 记录
db.Select(clause.Associations).Delete(&user)
GORM设计原理
SQL是怎么生成的
sql语句包括select
、from
、where
、ordered by
、limit
、for
等从句组成。GORM语句通过Chain
方法和Finisher
方法组成,前者是前面说的sql从句,后者是最终决定类型和执行,两者会拼接成最终sql语句,GORM仿照sql语句,因此有很好的扩展性。
Chain方法底层实现:
Finisher方法底层实现:
这样设计的原因:
- 自定义Clause Builder
- 方便扩展Clause
- 自由选择Clauses
插件是怎么工作的
前面Finisher
方法提到的决定语句类型,并执行Callbacks并生成SQL并执行的就是插件系统,主要有6种类型:
先注册回调函数,后面找到对应类型所注册的所有方法,并一一调用
callback的操作
为何这样设计?总的来说就是达到”灵活定制、自由扩展”的目的
- 多租户
- 多数据库、读写分离
- 加解密、混沌工程等…
ConnPool是什么
连接池,是GORM和数据库的中间层,而且通常会将对数据库的操作分成读和写两种类型,然后调用不同连接池进行操作
预编译语句有两种模式:全局模式、会话模式
一行语句提升性能,关闭interpolateParams即可省略2个语句
Dialector
字节内部提供bytedgorm,即默认最佳参数配置,使用户不需看文档就能达到最佳配置,通过Dialector实现
这里演讲者有很多设计的经验分享,可惜没听懂。。。后面再补充