基本说明
在go-zero框架进行开发时,常使用goctl工具通过xxx.sql文件生成golang代码,且生成的代码对缓存支持性良好。但是生成的方法较为简单,无法应对复杂查询的情况。
以user.sql表为例,生成的四个方法为:
type userModel interface {
Insert(ctx context.Context, data *User) error
FindOne(ctx context.Context, id string) (*User, error)
Update(ctx context.Context, data *User) error
Delete(ctx context.Context, id string) error
}
注意:update接口的实现,传入的数据均会更新(不会同Gorm的结构体更新方法一样忽略零值),在使用时应该注意
目前暂无对批量插入的方法,官方只给了异步插入接口,用于日志等不需要实时返回插入结果的场景。
批量插入官方文档地址: https://go-zero.dev/docs/tutorials/mysql/bulk/insert
goctl数据库模型代码生成官方文档地址:go-zero.dev/docs/tutori…
自定义model方法
自定义model方法,需要在goctl生成的user_model.go文件中编写代码。如下:
1、在UserModel interface 中添加自定义方法接口
2、在下方customUsersModel 结构体方法中实现该自定义接口
type (
// UsersModel is an interface to be customized, add more methods here,
// and implement the added methods in customUsersModel.
UsersModel interface {
usersModel
//添加的自定义方法
FindListByUid(ctx context.Context, ids []int64) (list []*Users, error)
customUsersModel struct {
*defaultUsersModel
}
)
func (m *defaultUsersModel) FindListByUid(ctx context.Context, ids []int64) (list []*Users, error) {
//业务逻辑
}
自定义方法的使用,主要依赖于go-zero框架封装的sqlc、sqlx两个包。
对应的包名:
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
sqlc包含对sqlx和cache的封装。
sqlc封装接口方法如下:
//缓存操作方法
//实际是对go-redis v8 的set、get操作方法的封装
SetCache(key string, val interface{}) error
SetCacheCtx(ctx context.Context, key string, val interface{}) error
DelCache(keys ...string) error
DelCacheCtx(ctx context.Context, keys ...string) error
GetCache(key string, v interface{}) error
GetCacheCtx(ctx context.Context, key string, v interface{}) error
//sql操作方法
QueryRow(v interface{}, key string, query QueryFn) error
QueryRowCtx(ctx context.Context, v interface{}, key string, query QueryCtxFn) error
QueryRowIndex(v interface{}, key string, keyer func(primary interface{}) string, indexQuery IndexQueryFn, primaryQuery PrimaryQueryFn) error
QueryRowIndexCtx(ctx context.Context, v interface{}, key string, keyer func(primary interface{}) string, indexQuery IndexQueryCtxFn, primaryQuery PrimaryQueryCtxFn) error
QueryRowNoCache(v interface{}, q string, args ...interface{}) error
QueryRowNoCacheCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error
QueryRowsNoCache(v interface{}, q string, args ...interface{}) error
QueryRowsNoCacheCtx(ctx context.Context, v interface{}, q string, args ...interface{}) error
//事务操作:
Transact(fn func(sqlx.Session) error) error
TransactCtx(ctx context.Context, fn func(context.Context, sqlx.Session) error) error
//支持自定义sql执行操作:
Exec(exec ExecFn, keys ...string) (sql.Result, error)
ExecCtx(ctx context.Context, exec ExecCtxFn, keys ...string) (sql.Result, error)
ExecNoCache(q string, args ...interface{}) (sql.Result, error)
ExecNoCacheCtx(ctx context.Context, q string, args ...interface{}) (sql.Result, error)
sqlx基础方法
Exec(args ...interface{}) (sql.Result, error)
ExecCtx(ctx context.Context, args ...interface{}) (sql.Result, error)
QueryRow(v interface{}, args ...interface{}) error
QueryRowCtx(ctx context.Context, v interface{}, args ...interface{}) error
QueryRowPartial(v interface{}, args ...interface{}) error
QueryRowPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error
QueryRows(v interface{}, args ...interface{}) error
QueryRowsCtx(ctx context.Context, v interface{}, args ...interface{}) error
QueryRowsPartial(v interface{}, args ...interface{}) error
QueryRowsPartialCtx(ctx context.Context, v interface{}, args ...interface{}) error
QueryRowPartial和QueryRow的区别:
QueryRow接收数据的结构体必须包含查询出来的所有字段,否则会报错。而QueryRowPartial可只接收某个字段。
注意:在sqlc的query操作中,均是对sqlx中QueryRow的操作,在使用结构体接收查询出来的内容时,列数与结构体的字段需要全匹配,否则会报错。
sql生成神器-squirrel
很多时候我们需要使用自定义sql语句进行查询,在使用自定义sql语句查询时,为了避免出现sql注入等安全问题,在sql中使用?进行占位,然后具体参数用args传入。类似以下这样:
SELECT id, name, age FROM `user` WHERE id IN (?,?,?) AND name = ?
为了操作方便,常用squirrel包进行sql语句生成。
Squirrel包地址:github.com/Masterminds…
基本用法:
这个库的基本用法与Gorm相似,但squirrel用于生成sql语句及对应的参数,不是ORM框架,无法操作数据库,且不支持生成批量插入语句。
//Select:
selectBuilder1 := sq.Select("id", "name", "age").From("`user`").Where("id = ?", 1)
sqlStr,args,err := selectBuilder1.ToSql()
//sqlStr==>SELECT id, name, age FROM `user` WHERE id = ?
//Args==>[1]
selectBuilder2 := sq.Select("id", "name", "age").From("`user`").Where(sq.Eq{
"id": []int{1, 2, 3},
"name": "jack",
})
sqlStr,args,err := selectBuilder2.ToSql()
//sqlStr==>SELECT id, name, age FROM `user` WHERE id IN (?,?,?) AND name = ?
//Args==>[1 2 3 jack]
//此外还支持外联查询、左右连接、临时表等查询方法。
//Inset:
insertBuilder1 := sq.Insert("`user`").Columns("id", "name", "age").Values(1, "jack", 15)
sqlStr,args,err := insertBuilder1 .ToSql()
//sqlStr ==> INSERT INTO `user` (id,name,age) VALUES (?,?,?)
//Args ==>[1 jack 15]
insertBuilder2 := sq.Insert("`user`").SetMap(map[string]interface{}{
"id": 1,
"name": "jack",
"age": 18,
})
sqlStr,args,err := insertBuilder2 .ToSql()
//sqlStr ==> INSERT INTO `user` (age,id,name) VALUES (?,?,?)
//Args ==> [18 1 jack]
注意: 条件生成: sq.where()可写对应的查询代码,但是不支持直接写IN的操作,需要使用sq.EQ{ } 作为map传入,key为条件字段,当条件内容为[]切片时,自动转换为key IN()的操作 此外,还有AND GtOrEq 等针对性生成对应字段查询条件的语句。 由于是map[string]interface{},所以生成sql代码的列名称顺序是不确定的。
综上,FindListByUid接口可以有如下的实现
package model
import (
"github.com/Masterminds/squirrel"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
/*
省略其他代码
*/
func (m *defaultUsersModel) FindListByUid(ctx context.Context, ids []int64) (list []*User, error) {
var list []*User
sqlBuilder := squirrel.Select("*").From(m.table)
sqlBuilder = sqlBuilder.Where(squirrel.Eq{
"id":ids,
})
sqlStr,args,err := sqlBuilder.ToSql()
if err != nil {
logx.Error("generate sqlStr failed",err)
return nil,err
}
err = m.QueryRowsNoCacheCtx(ctx, &list, sqlStr, args...)
if err != nil {
logx.Error("query failed",err)
return nil,err
}
return list,nil
}