这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记
01. 理解database/sql
基本用法
_ "github.com/go-sql-driver/mysql",这个我们必须加,没有编译检查,但是运行就会报错了 为什么要加这个呢,因为我们可以用不上里面的方法,但是我们可能需要用到里面的初始化方法,所以这个是不可或缺的。
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
type User struct {
Id int64
Name string
}
func main() {
db, err := sql.Open("mysql","username:password@tcp(127.0.0.1:3306)/数据库名")
if err != nil {
//....
}
rows, err := db.Query("select * from `user`")
if err != nil {
//....
}
defer func() {
err := rows.Close()
if err != nil {
//....
}
}()
var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.Id,&user.Name)
if err != nil {
//....
}
users = append(users,user)
}
if rows.Err() != nil {
//....
}
for _, user := range users {
fmt.Println(user)
}
}
defer func() {
err := rows.Close()
if err != nil {
//。。。。
}
}()
这个关闭可以会出现错误,要处理
if rows.Err() != nil {
//.....
}
这个异常也要记得处理
设计原理
应用程序 操作接口 database/sql (连接池) 连接接口、操作接口 数据库
连接池
type DB struct {
// Atomic access only. At top of struct to prevent mis-alignment
// on 32-bit platforms. Of type time.Duration.
waitDuration int64 // Total time waited for new connections.
connector driver.Connector
// numClosed is an atomic counter which represents a total number of
// closed connections. Stmt.openStmt checks it before cleaning closed
// connections in Stmt.css.
numClosed uint64
mu sync.Mutex // protects following fields
freeConn []*driverConn // free connections ordered by returnedAt oldest to newest
connRequests map[uint64]chan connRequest
nextRequest uint64 // Next key to use in connRequests.
numOpen int // number of opened and pending open connections
// Used to signal the need for new connections
// a goroutine running connectionOpener() reads on this chan and
// maybeOpenNewConnections sends on the chan (one send per needed connection)
// It is closed during db.Close(). The close tells the connectionOpener
// goroutine to exit.
openerCh chan struct{}
closed bool
dep map[finalCloser]depSet
lastPut map[*driverConn]string // stacktrace of last conn's put; debug only
maxIdleCount int // zero means defaultMaxIdleConns; negative means 0
maxOpen int // <= 0 means unlimited
maxLifetime time.Duration // maximum amount of time a connection may be reused
maxIdleTime time.Duration // maximum amount of time a connection may be idle before being closed
cleanerCh chan struct{}
waitCount int64 // Total number of connections waited for.
maxIdleClosed int64 // Total number of connections closed due to idle count.
maxIdleTimeClosed int64 // Total number of connections closed due to idle time.
maxLifetimeClosed int64 // Total number of connections closed due to max connection lifetime limit.
stop func() // stop cancels the connection opener.
}
重点了解一下下面的就行了
// 设置连接可以被重用的最大时间量。
func (db *DB) SetConnMaxLifetime(d time.Duration)
//设置连接空闲的最大时间
func (db *DB) SetConnMaxIdleTime(d time.Duration)
//设置空闲连接池中的最大连接数。
func (db *DB) SetMaxIdleConns(n int)
//设置打开到数据库的最大连接数。
func (db *DB) SetMaxOpenConns(n int)
func (db *DB) Stats() DBStats
sql执行的一个过程
- 这个重点说了
下面: maxBadConnRetries 等于2的,driver.ErrBadConn失败会再尝试操作一次
for i := 0; i < maxBadConnRetries; i++ {
//从连接池获取连接或通过driver新建连接
dc, err := db.conn(ctx,strategy)
//将连接放回连接池
defer dc.db.putConn(dc,err,true)
//连接实现
if err == nil {
err = dc.ci.Query(sql,args...)
}
isBadConn = errors.Is(err,driver.ErrBadConn)
if !isBadConn {
break
}
}
这里会让我们数据库出现“重复插入的情况”。我们出现了一些网络波动,导致连接失败,我们再次重试就再插入一次了,就出问题了。
driver
- 提供了一个接口,这样就可以适配不同的数据库了
- 通过Register全局注册:drivers[name] = driver
设计缺点:
import没有编译检查
DSN通过字符串传会受限
- 提到了sqlserver的账号密码
然后就是这个名字,别的导入起名啥的不能和它重名
后来也解决了上面的问题,只是出来的太晚了,很多时候大家也没介绍这个
DB连接的几种类型
DB连接的几种类型:直接连接/Conn 预编译/Stmt 事务/Tx
这个预编译生成PrepareStatement(PrepareStatementId获取),当出现相同的查询语句的时候我们不需要重新生成PrepareStatement,传一个id就行了,这样可以减少网络传输的时间,减少数据库解析同样sql的时间。
返回数据的几种方式
处理返回数据的几种方式: Exec/ExecContext => Result Query/Querytext -> Rows(Columns) QueryRow/QueryRowContext -> Row(Rows简化)
这个Rows简化我们不需要关闭。
02. GORM使用简介
背景知识
"设计简洁、功能强大、自由扩展的全功能ORM" 设计原则:API简洁、测试优先、最小惊讶、灵活扩展、无依赖、可信赖
功能完善:关联、事务、多数据源、读写分离。。。。
关联中的多态:www.cnblogs.com/mingxuanton…
基本使用
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
type User struct {
Id int64
Name string
}
// TableName 通过这个指定表名 *User和User绑定都行
func (*User) TableName() string {
return "user"
}
func main() {
db, err := gorm.Open(
mysql.Open("username:password@tcp(127.0.0.1:3306)/datasourceName"),
)
if err != nil {
fmt.Println(err)
return
}
var users []User
err = db.Select("*").Find(&users,).Error
if err != nil {
fmt.Println(err)
return
}
fmt.Println(users)
}
CRUD
db.AutoMigrate(&User{})
- 文章参考://davidchan0519.github.io/2019/05/06/…
- 比如我们User新增了字段,我们使用这个进行刷新,但是我们删除了一个字段,刷新之后删除的字段还在
db.Migrator().CreateTable(&User{
})
创建表格,表格字段和user里面的字段一致
db.Create(&user)//新增,如果id是自增的,我们插入数据之后会返回id到我们的user里面
fmt.Println(user.Id)
fmt.Println(res.Error)//返回error
fmt.Println(res.RowsAffected)//返回影响的行数
db.Create(&users)//传切片的地址和直接传切片都行的
db.CreateInBatches(users,len(users))//第二个参数是插入的数量
批量创建 CreateInBatches:CreateInBatches会根据batchSize来分配进行create,但是他们是在同一个事务的,其rowsAffected是每个批次的rowsAffected的累加
查
var user User
db.First(&user,1)//查询id为1的user
fmt.Println(user)
user.Id = 0
db.First(&user,"name = ?","1111")
fmt.Println(user)
var users []User
res := db.Find(&users,[]int{1,2,3})
fmt.Println(res.RowsAffected)//返回找s的记录数
is := errors.Is(res.Error,gorm.ErrRecordNotFound) //First,Last,Take查找不到数据
fmt.Println(is)
fmt.Println(users)
更新
//更新某个字段
res := db.Model(&User{}).Where("name","1111").Update("name", "随风")
fmt.Println(res.RowsAffected)
//Where要在UpdateColumn前面
res := db.Model(&User{}).Where("name","222").UpdateColumn("name","哈哈哈")
还有批量更新等等
删除
模型定义
约定优于配置
-
表名复数
- 例子:type User struct {},默认表名:
users - 我们可以给User绑定func (User) TableName() string,返回自定义表名
- 例子:type User struct {},默认表名:
-
字段名单数
- 例子:name string,对应数据库字段:name
-
ID/Id字段为主键,如果为数字则为自增主键
- 我们也可以自定义一下:UUID string
gorm:"primaryKey"
- 我们也可以自定义一下:UUID string
-
CreatedAt字段,创建时保存当前时间
-
UpdatedAt字段,创建和更新时,保存当前时间
-
gorm.DeletedAt字段,默认开启soft delete模式
- DeletedAt gorm.DeletedAt
gorm:"index"
- DeletedAt gorm.DeletedAt
惯例约定
gorm.Model里面包含的东西: ID uint
gorm:"primarykey"CreatedAt time.Time UpdatedAt time.Time DeletedAt DeletedAtgorm:"index"
关联操作
- gorm.io/zh_CN/docs/…
- 我们平时需要进行开发的时候,忘记了一些知识点可以直接到这个官方文档里面进行查看。
介绍
本质就是提供一组函数来帮助我们快速拼接 sql 语句,尽量减少 sql 的编写,关联操作也可以减少我们繁琐的sql拼接。 通过关联操作,我们可以快速进行表间的关联操作。
CRUD
Preload/Joins预加载
preload提升了资源加载的优先级,使得它提前开始加载(预加载),在需要用的时候能够更快的使用上。
需要注意的是:Joins只能用于一对一关联中,即HasOne和BelongTo关联
联级删除
方法1:使用数据库约束自动删除
放法2:使用Select实现级联删除,不依赖数据库约束和逻辑删除
03. GORM设计原理
应用程序 => GORM =操作接口=> database/sql =连接接口/操作接口=> 数据库
PS:上这课的时候完全没接触过GORM,所以这个设计原理是真的听不懂。
04. GORM最佳实践
1、数据序列化与SQL表达式
2、批量数据操作
3、代码复用、分库分表
4、混沌工程/压测
5、Logger/Trace
6、Migrator
7、Gen代码生成/Raw SQL
- 仓库地址:gitee.com/gorm/gen
8、安全
安全要求我们防止SQL注入,要怎么做到这一点呢。我们使用GORM提供的函数的时候以参数形式传入(比如将参数绑定到实体对象里面再传入),不要直接使用类似fmt.Sprintf这种函数进行SQL拼接
随记,要是想详细学习GORM可以参照官方文档进行学习