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. // 1. 快速连接一个 mysql
  2. // datasource: mysql dsn
  3. heraMysql := sqlx.NewMysql(datasource)
  4. // 2. 在 servicecontext 中调用,懂model上层的logic层调用
  5. model.NewMysqlModel(heraMysql, tablename),
  6. // 3. model层 mysql operation
  7. func NewMysqlModel(conn sqlx.SqlConn, table string) *MysqlModel {
  8. defer func() {
  9. recover()
  10. }()
  11. // 4. 创建一个批量insert的 [mysql executor]
  12. // conn: mysql connection; insertsql: mysql insert sql
  13. bulkInserter , err := sqlx.NewBulkInserter(conn, insertsql)
  14. if err != nil {
  15. logx.Error("Init bulkInsert Faild")
  16. panic("Init bulkInsert Faild")
  17. return nil
  18. }
  19. return &MysqlModel{conn: conn, table: table, Bulk: bulkInserter}
  20. }

CRUD

准备一个 User model

  1. var userBuilderQueryRows = strings.Join(builderx.FieldNames(&User{}), ",")
  2. type User struct {
  3. Avatar string `db:"avatar"` // 头像
  4. UserName string `db:"user_name"` // 姓名
  5. Sex int `db:"sex"` // 1男,2女
  6. MobilePhone string `db:"mobile_phone"` // 手机号
  7. }

其中 userBuilderQueryRowsgo-zero 中提供 struct -> [field...] 的转化,开发者可以将此当成模版直接使用。

insert

  1. // 一个实际的insert model层操作
  2. func (um *UserModel) Insert(user *User) (int64, error) {
  3. const insertsql = `insert into `+um.table+` (`+userBuilderQueryRows+`) values(?, ?, ?)`
  4. // insert op
  5. res, err := um.conn.Exec(insertsql, user.Avatar, user.UserName, user.Sex, user.MobilePhone)
  6. if err != nil {
  7. logx.Errorf("insert User Position Model Model err, err=%v", err)
  8. return -1, err
  9. }
  10. id, err := res.LastInsertId()
  11. if err != nil {
  12. logx.Errorf("insert User Model to Id parse id err,err=%v", err)
  13. return -1, err
  14. }
  15. return id, nil
  16. }
  • 拼接 insertsql
  • insertsql 以及 占位符对应的 struct field 传入 -> con.Exex(insertsql, field...)

[!WARNING] conn.Exec(sql, args...)args... 需对应 sql 中占位符。不然会出现赋值异常的问题。

go-zero 将涉及 mysql 修改的操作统一抽象为 Exec() 。所以 insert/update/delete 操作本质上一致的。其余两个操作,开发者按照上述 insert 流程尝试即可。

query

只需要传入 querysqlmodel 结构体,就可以获取到被赋值好的 model 。无需开发者手动赋值。

  1. func (um *UserModel) FindOne(uid int64) (*User, error) {
  2. var user User
  3. const querysql = `select `+userBuilderQueryRows+` from `+um.table+` where id=? limit 1`
  4. err := um.conn.QueryRow(&user, querysql, uid)
  5. if err != nil {
  6. logx.Errorf("userId.findOne error, id=%d, err=%s", uid, err.Error())
  7. if err == sqlx.ErrNotFound {
  8. return nil, ErrNotFound
  9. }
  10. return nil, err
  11. }
  12. return &user, nil
  13. }
  • 声明 model struct ,拼接 querysql
  • conn.QueryRow(&model, querysql, args...)args...querysql 中的占位符对应。

[!WARNING] QueryRow() 中第一个参数需要传入 Ptr 「底层需要反射对 struct 进行赋值」

上述是查询一条记录,如果需要查询多条记录时,可以使用 conn.QueryRows()

  1. func (um *UserModel) FindOne(sex int) ([]*User, error) {
  2. users := make([]*User, 0)
  3. const querysql = `select `+userBuilderQueryRows+` from `+um.table+` where sex=?`
  4. err := um.conn.QueryRows(&users, querysql, sex)
  5. if err != nil {
  6. logx.Errorf("usersSex.findOne error, sex=%d, err=%s", uid, err.Error())
  7. if err == sqlx.ErrNotFound {
  8. return nil, ErrNotFound
  9. }
  10. return nil, err
  11. }
  12. return users, nil
  13. }

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()

事务

要在事务中执行一系列操作,一般流程如下:

  1. var insertsql = `insert into User(uid, username, mobilephone) values (?, ?, ?)`
  2. err := usermodel.conn.Transact(func(session sqlx.Session) error {
  3. stmt, err := session.Prepare(insertsql)
  4. if err != nil {
  5. return err
  6. }
  7. defer stmt.Close()
  8. // 返回任何错误都会回滚事务
  9. if _, err := stmt.Exec(uid, username, mobilephone); err != nil {
  10. logx.Errorf("insert userinfo stmt exec: %s", err)
  11. return err
  12. }
  13. // 还可以继续执行 insert/update/delete 相关操作
  14. return nil
  15. })

如同上述例子,开发者只需将 事务 中的操作都包装在一个函数 func(session sqlx.Session) error {} 中即可,如果事务中的操作返回任何错误, Transact() 都会自动回滚事务。