# mysql

`go-zero` 提供更易于操作 `mysql` API。


> 但是  `stores/mysql` 定位不是一个 `orm` 框架，如果你需要通过 `sql/scheme` -> `model/struct` 逆向生成 `model` 层代码，开发者可以使用「[goctl model](https://www.yuque.com/tal-tech/go-zero/nkg20f)」，这个是极好的功能。



## Feature


- 相比原生，提供开发者更友好的API
- 完成 `queryField -> struct` 的自动赋值
- 批量插入「bulkinserter」
- 自带熔断
- API 经过若干个服务的不断考验
- 提供 `partial assignment` 特性，不强制 `struct` 的严格赋值



## Connection
下面用一个例子简单说明一下如何创建一个 `mysql` 连接的model：
```go
// 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` 
```go
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
```go
// 一个实际的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...)` 



需要 ⚠️  `conn.Exec(sql, args...)` ： `args...` 需对应 `sql` 中占位符。不然会出现赋值异常的问题。


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


### query


只需要传入 `querysql` 和 `model` 结构体，就可以获取到被赋值好的 `model` 。无需开发者手动赋值。
```go
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` 中的占位符对应。



需要 ⚠️ 的是： `QueryRow()` 中第一个参数需要传入 `Ptr` 「底层需要反射对 `struct` 进行赋值」

上述是查询一条记录，如果需要查询多条记录时，可以使用 `conn.QueryRows()` 
```go
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()`


## 事务


要在事务中执行一系列操作，一般流程如下：
```go
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()` 都会自动回滚事务。


<Vssue title="storemysql" />
