这是我参与「第五届青训营 」伴学笔记创作活动的第 1天
1. 理解database/sql
database/sql包是Go标准库中提供的围绕sql(或类似sql)数据库的通用接口。sql包必须与数据库驱动程序一起使用,而这些驱动程序由第三方开发,并不在标准库中。
1.1 database/sql基本用法
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
type Todo struct {
ID int
Title string
}
func main() {
dsn := "root:19991101@tcp(127.0.0.1:3306)/bubble"
db, err := sql.Open("mysql", dsn)
if err != nil {
fmt.Println("Connect database failed ! err: ", err)
}
rows, err := db.Query("select id, title from todos where id = ?", 1)
if err != nil {
// xxx
}
defer func() {
err = rows.Close()
}()
var todos []Todo
for rows.Next() {
var todo Todo
err := rows.Scan(&todo.ID, &todo.Title)
if err != nil {
// ...
}
fmt.Printf("%#v\n", todo)
todos = append(todos, todo)
}
if rows.Err() != nil {
//..
}
}
1.2 连接池的维护
- 设置连接最大空闲时间
func (db *DB) SetConnMaxIdleTime(d time.Duration)
- 设置连接可以重复使用的最大时间
func (db *DB) SetConnMaxLifetime(d time.Duration)
- 设置空闲连接池中的最大连接数
func (db *DB) SetMaxIdleConns(n int)
- 设置数据库的最大打开连接数
func (db *DB) SetMaxOpenConns(n int)
- 返回连接池状态
func (db *DB) Stats() DBStats
连接池状态包含以下信息
type DBStats struct {
MaxOpenConnections int // Maximum number of open connections to the database.
// Pool Status
OpenConnections int // The number of established connections both in use and idle.
InUse int // The number of connections currently in use.
Idle int // The number of idle connections.
// Counters
WaitCount int64 // The total number of connections waited for.
WaitDuration time.Duration // The total time blocked waiting for a new connection.
MaxIdleClosed int64 // The total number of connections closed due to SetMaxIdleConns.
MaxIdleTimeClosed int64 // The total number of connections closed due to SetConnMaxIdleTime.
MaxLifetimeClosed int64 // The total number of connections closed due to SetConnMaxLifetime.
}
1.3 Connector接口取代dsn
直接使用基于字符串的dsn连接数据库有诸多不便,如:
- 密码中的一些字符可能会引起转义问题
- 容易忘记导入数据库驱动包
因此,database\sql包提供了另一种基于
Connector连接数据库的方式,使用示例如下:
import (
"database/sql"
"fmt"
"github.com/go-sql-driver/mysql")
func main() {
connector, _ := mysql.NewConnector(&mysql.Config{
User: "root",
Passwd: "19991101",
Net: "tcp",
Addr: "127.0.0.1:3306",
DBName: "bubble",
Collation: "utf8mb4_general_ci", // 设定字符序
AllowNativePasswords: true, // 允许使用本机密码登录
})
db := sql.OpenDB(connector)
err := db.Ping()
if err != nil {
fmt.Println("database connect failed, error", err)
}
}
Collation参数的作用是设置字符序,如果不进行设置可能会报错:unknown collationAllowNativePasswords参数的作用是运行使用本机密码进行登录,如果不进行设置,可能会报错: this user requires mysql native password authentication 此外,通过Connector连接器连接数据库不会返回err,要验证是否连接成功,需要调用DB.Ping()方法
2. GORM的基础使用
2.1 连接数据库
func main() {
dsn := "root:19991101@tcp(127.0.0.1:3306)/bubble?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
fmt.Println("failed to connect database, err: ", err)
}
}
2.2 CRUD
2.2.1 创建数据
创建单条数据
todo := Todo{Title : "事项1",
Status : false}
result := db.Create(&todo) // 通过数据的指针来创建
todo.ID 返回插入数据的主键
result.Error返回error
result.RowsAffected返回插入记录的条数
批量创建数据 可以通过传入切片批量创建数据
var todos = []Todo{{Title: "事项1"}, {Title: "事项2"}, {Title: "事项3"}}
db.Create(todos)
for _, todo := range todos {
fmt.Println(todo.ID) // 1,2,3
}
分批创建数据 CreateInBatches
// 分批创建数据1000条数据 CreateInBatches
var todos2 = []Todo{}
for i := 0; i < 1000; i++ {
str := "事项" + strconv.Itoa(i+1)
todo := Todo{Title: str}
todos2 = append(todos2, todo)
}
// 每批次数量为 100
db.CreateInBatches(todos2, 100)
2.2.2 查找数据
查询单条记录
GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误
var todo = Todo{}
result := db.First(&todo) // 查询按主键排序的第一条数据
result = db.Take(&todo) // 查询第一条数据
result = db.Last(&todo) // 查询按主键排序的最后一条数据
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)
使用find查找多条数据
使用String条件进行查询
// select * from todos
db.Find(&todos) // 查询所有数据
// select * from todos where title in ("事项1", "事项2", "事项3")
db.Where("title in ?", []string{"事项1", "事项2", "事项3"}).Find(&todos)
// select * from todos where title like '%事项1%'
db.Where("title like ?", "%事项1%").Find(&todos)
find函数也可以使用结构体和map作为查询条件
// select * from todos where title == "事项1"
db.Where(&Todo{Title: "事项1", Status: false}).Find(&todos)
db.Where(map[string]interface{}{"Title": "事项2", "Status": false}).Find(&todos)
注意:
- 使用find函数查询多条数据,如果查询不到数据不会返回err
- 使用结构体查询时,gorm只会将非0字段作为查询条件。如果需要将零值字段作为查询条件,可以使用map进行查询
2.2.3 更新数据
更新数据主要使用到Update方法和model方法, 当使用 Model 方法,并且值中有主键值时,主键将会被用于构建条件
更新单列
// update todos set title = '事项one' where title = '事项1'
db.Model(&Todo{}).Where("title = ?", "事项1").Update("title", "事项one")
更新多列
Updates方法可以使用结构体和map作为更新值
// update todos set title = '吃饭', status = true where id == 1
db.Model(&Todo{ID: 1}).Updates(&Todo{Title : "吃饭", Status: true})
db.Model(&Todo{ID: 1}).Updates(map[string]interface{}{"Title" : "吃饭", "Status" : true});
更新选定字段
可以使用 Select、Omit在更新时选定、忽略某些字段
db.Model(&Todo{ID: 1}).Select("title").Updates(&Todo{Title: "吃饭", Status: true})
db.Model(&Todo{ID: 1}).Omit("title").Updates(&Todo{Title: "吃饭", Status: true})
SQL表达式更新
// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;
db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
注意:同find方法一样,在使用结构体更新字段值时,只会更新非0值,如果需要更新零值,可以使用map
2.2.4 删除数据
删除数据主要使用delete方法
删除单条数据
//DELETE from todos where id = 1
db.Delete(&Todo{ID: 1})
批量删除
// delete form todos where id > 10
db.Where("id > ?", 10).Delete(&Todo{})
db.Delete(&Todo{}, "id > ?", 10)
软删除
如果模型中包含了 gorm.deletedat 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力。拥有软删除能力的模型调用 Delete 时,记录不会从数据库中被真正删除。但 GORM 会将 DeletedAt 置为当前时间, 并且你不能再通过普通的查询方法找到该记录。
可以使用Unscoped找到或永久删除软删除记录
db.Unscoped().Where("age = 20").Find(&users)
db.Unscoped().Delete(&order)
2.3 模型定义
一个结构体可以视为一张表,其属性可以视为各个字段,在GORM中可以使用db.AutoMigrate(&Todo{})自动建表,只需要传入空结构体指针。
自动建表时的一些约定:
- 表名为struct name的snake_cases复数格式,可以通过实现
Tabler接口中的TableName方法设置表名
func (User) TableName() string {
return "profiles"
}
- 字段名为field name的snake_case单数格式,可以通过
gorm:"column:name"标签设置字段名 - ID/id字段默认为主键,如果为数字,则为自增主键,使用标签
gorm:"primaryKey"可以将其它字段设为主键 - CreateAt字段,创建时,保存当前时间
- UpdateAt字段,创建、更新时,保存当前时间
- gorm.DeletedAt字段,默认开启soft delete模式
引用参考
Standard library - Go Packages
GORM 指南 | GORM - The fantastic ORM library for Golang, aims to be developer friendly.