字节青训营第五课之设计模式之Database SQL与GORM实践的笔记与总结

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 的基本数据类型、实现了 ScannerValuer 接口的自定义类型及其指针或别名组成
在这里插入图片描述

惯例约定

GORM 倾向于约定,而不是配置,当然实际是可以自定义配置的。默认情况下,GORM 使用 ID 作为主键,使用结构体名的 snake_cases 复数格式作为表名,字段名的 snake_case单数格式作为列名,并使用 CreatedAtUpdatedAtDeletedAt 字段追踪创建、更新时间、删除模式。如图:
在这里插入图片描述

关联

//保存用户及其关联
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语句包括selectfromwhereordered bylimitfor等从句组成。GORM语句通过Chain方法和Finisher方法组成,前者是前面说的sql从句,后者是最终决定类型和执行,两者会拼接成最终sql语句,GORM仿照sql语句,因此有很好的扩展性。
在这里插入图片描述
Chain方法底层实现:
在这里插入图片描述
Finisher方法底层实现:
在这里插入图片描述
这样设计的原因:

  1. 自定义Clause Builder
    在这里插入图片描述
  2. 方便扩展Clause

在这里插入图片描述

  1. 自由选择Clauses

在这里插入图片描述

插件是怎么工作的

前面Finisher方法提到的决定语句类型,并执行Callbacks并生成SQL并执行的就是插件系统,主要有6种类型:
在这里插入图片描述
先注册回调函数,后面找到对应类型所注册的所有方法,并一一调用
在这里插入图片描述
callback的操作
在这里插入图片描述
为何这样设计?总的来说就是达到”灵活定制自由扩展”的目的

  1. 多租户
    在这里插入图片描述
  2. 多数据库、读写分离
    在这里插入图片描述
  3. 加解密、混沌工程等…

ConnPool是什么

连接池,是GORM和数据库的中间层,而且通常会将对数据库的操作分成读和写两种类型,然后调用不同连接池进行操作
在这里插入图片描述
预编译语句有两种模式:全局模式、会话模式
在这里插入图片描述
一行语句提升性能,关闭interpolateParams即可省略2个语句

在这里插入图片描述

Dialector

字节内部提供bytedgorm,即默认最佳参数配置,使用户不需看文档就能达到最佳配置,通过Dialector实现
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里演讲者有很多设计的经验分享,可惜没听懂。。。后面再补充

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值