mysql
go-zero
提供更易于操作的 mysql
API。
[!TIP] 但是
stores/mysql
定位不是一个orm
框架,如果你需要通过sql/scheme
->model/struct
逆向生成model
层代码,可以使用「goctl model」,这个是极好的功能。
Feature
- 相比原生,提供对开发者更友好的 API
- 完成
queryField -> struct
的自动赋值 - 批量插入「bulkinserter」
- 自带熔断
- API 经过若干个服务的不断考验
- 提供
partial assignment
特性,不强制struct
的严格赋值
Connection
下面用一个例子简单说明一下如何创建一个 mysql
连接的 model:
// 1. 快速连接一个 mysql
// datasource: mysql dsn
heraMysql := sqlx.NewMysql(datasource)
// 2. 在 servicecontext 中调用,懂model上层的logic层调用
model.NewMysqlModel(heraMysql, tablename),
// 3. model层 mysql operation
func NewMysqlModel(conn sqlx.SqlConn, table string) *MysqlModel {
defer func() {
recover()
}()
// 4. 创建一个批量insert的 [mysql executor]
// conn: mysql connection; insertsql: mysql insert sql
bulkInserter , err := sqlx.NewBulkInserter(conn, insertsql)
if err != nil {
logx.Error("Init bulkInsert Faild")
panic("Init bulkInsert Faild")
return nil
}
return &MysqlModel{conn: conn, table: table, Bulk: bulkInserter}
}
CRUD
准备一个 User model
var userBuilderQueryRows = strings.Join(builderx.FieldNames(&User{}), ",")
type User struct {
Avatar string `db:"avatar"` // 头像
UserName string `db:"user_name"` // 姓名
Sex int `db:"sex"` // 1男,2女
MobilePhone string `db:"mobile_phone"` // 手机号
}
其中 userBuilderQueryRows
: go-zero
中提供 struct -> [field...]
的转化,开发者可以将此当成模版直接使用。
insert
// 一个实际的insert model层操作
func (um *UserModel) Insert(user *User) (int64, error) {
const insertsql = `insert into `+um.table+` (`+userBuilderQueryRows+`) values(?, ?, ?)`
// insert op
res, err := um.conn.Exec(insertsql, user.Avatar, user.UserName, user.Sex, user.MobilePhone)
if err != nil {
logx.Errorf("insert User Position Model Model err, err=%v", err)
return -1, err
}
id, err := res.LastInsertId()
if err != nil {
logx.Errorf("insert User Model to Id parse id err,err=%v", err)
return -1, err
}
return id, nil
}
- 拼接
insertsql
- 将
insertsql
以及占位符对应的struct field
传入 ->con.Exex(insertsql, field...)
[!WARNING]
conn.Exec(sql, args...)
:args...
需对应sql
中的占位符。不然会出现赋值异常的问题。
go-zero
将涉及 mysql
修改的操作统一抽象为 Exec()
。所以 insert/update/delete
操作本质上是一致的。其余两个操作,开发者按照上述 insert
流程尝试即可。
query
只需要传入 querysql
和 model
结构体,就可以获取到被赋值好的 model
。无需开发者手动赋值。
func (um *UserModel) FindOne(uid int64) (*User, error) {
var user User
const querysql = `select `+userBuilderQueryRows+` from `+um.table+` where id=? limit 1`
err := um.conn.QueryRow(&user, querysql, uid)
if err != nil {
logx.Errorf("userId.findOne error, id=%d, err=%s", uid, err.Error())
if err == sqlx.ErrNotFound {
return nil, ErrNotFound
}
return nil, err
}
return &user, nil
}
- 声明
model struct
,拼接querysql
conn.QueryRow(&model, querysql, args...)
:args...
与querysql
中的占位符对应。
[!WARNING]
QueryRow()
中第一个参数需要传入Ptr
「底层需要反射对struct
进行赋值」
上述是查询一条记录,如果需要查询多条记录时,可以使用 conn.QueryRows()
func (um *UserModel) FindOne(sex int) ([]*User, error) {
users := make([]*User, 0)
const querysql = `select `+userBuilderQueryRows+` from `+um.table+` where sex=?`
err := um.conn.QueryRows(&users, querysql, sex)
if err != nil {
logx.Errorf("usersSex.findOne error, sex=%d, err=%s", uid, err.Error())
if err == sqlx.ErrNotFound {
return nil, ErrNotFound
}
return nil, err
}
return users, nil
}
与 QueryRow()
不同的地方在于: model
需要设置成 Slice
,因为是查询多行,需要对多个 model
赋值。但同时需要注意️:第一个参数需要传入 Ptr
querypartial
从使用上,与上述的 QueryRow()
无异「这正体现了 go-zero
高度的抽象设计」。
区别:
QueryRow()
:len(querysql fields) == len(struct)
,且一一对应QueryRowPartial()
:len(querysql fields) <= len(struct)
numA:数据库字段数;numB:定义的 struct
属性数。
如果 numA < numB
,但是你恰恰又需要统一多处的查询时「定义了多个 struct
返回不同的用途,恰恰都可以使用相同的 querysql
」,就可以使用 QueryRowPartial()
事务
要在事务中执行一系列操作,一般流程如下:
var insertsql = `insert into User(uid, username, mobilephone) values (?, ?, ?)`
err := usermodel.conn.Transact(func(session sqlx.Session) error {
stmt, err := session.Prepare(insertsql)
if err != nil {
return err
}
defer stmt.Close()
// 返回任何错误都会回滚事务
if _, err := stmt.Exec(uid, username, mobilephone); err != nil {
logx.Errorf("insert userinfo stmt exec: %s", err)
return err
}
// 还可以继续执行 insert/update/delete 相关操作
return nil
})
如同上述例子,开发者只需将 事务 中的操作都包装在一个函数 func(session sqlx.Session) error {}
中即可,如果事务中的操作返回任何错误, Transact()
都会自动回滚事务。