model 指令

goctl model 为go-zero下的工具模块中的组件之一,目前支持识别mysql ddl进行model层代码生成,通过命令行或者idea插件(即将支持)可以有选择地生成带redis cache或者不带redis cache的代码逻辑。

快速开始#

  • 通过ddl生成

    $ goctl model mysql ddl -src="./*.sql" -dir="./sql/model" -c

    执行上述命令后即可快速生成CURD代码。

    model
    ├── usermodel.go
    ├── usermodel_gen.go
    └── vars.go
  • 通过datasource生成

    $ goctl model mysql datasource -url="user:password@tcp(127.0.0.1:3306)/database" -table="*" -dir="./model"

usermodel_gen.go

// Code generated by goctl. DO NOT EDIT!
package model
import (
"context"
"database/sql"
"fmt"
"strings"
"time"
"github.com/zeromicro/go-zero/core/stores/builder"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlc"
"github.com/zeromicro/go-zero/core/stores/sqlx"
"github.com/zeromicro/go-zero/core/stringx"
)
var (
userFieldNames = builder.RawFieldNames(&User{})
userRows = strings.Join(userFieldNames, ",")
userRowsExpectAutoSet = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`"), ",")
userRowsWithPlaceHolder = strings.Join(stringx.Remove(userFieldNames, "`id`", "`create_time`", "`update_time`"), "=?,") + "=?"
cacheUserIdPrefix = "cache:user:id:"
cacheUserNumberPrefix = "cache:user:number:"
)
type (
userModel interface {
Insert(ctx context.Context, data *User) (sql.Result, error)
FindOne(ctx context.Context, id int64) (*User, error)
FindOneByNumber(ctx context.Context, number string) (*User, error)
Update(ctx context.Context, data *User) error
Delete(ctx context.Context, id int64) error
}
defaultUserModel struct {
sqlc.CachedConn
table string
}
User struct {
Id int64 `db:"id"`
Number string `db:"number"`
Name string `db:"name"`
Password string `db:"password"`
Gender string `db:"gender"`
CreateTime time.Time `db:"create_time"`
UpdateTime time.Time `db:"update_time"`
}
)
func newUserModel(conn sqlx.SqlConn, c cache.CacheConf) *defaultUserModel {
return &defaultUserModel{
CachedConn: sqlc.NewConn(conn, c),
table: "`user`",
}
}
func (m *defaultUserModel) Insert(ctx context.Context, data *User) (sql.Result, error) {
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
userNumberKey := fmt.Sprintf("%s%v", cacheUserNumberPrefix, data.Number)
ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("insert into %s (%s) values (?, ?, ?, ?)", m.table, userRowsExpectAutoSet)
return conn.ExecCtx(ctx, query, data.Number, data.Name, data.Password, data.Gender)
}, userIdKey, userNumberKey)
return ret, err
}
func (m *defaultUserModel) FindOne(ctx context.Context, id int64) (*User, error) {
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
var resp User
err := m.QueryRowCtx(ctx, &resp, userIdKey, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userRows, m.table)
return conn.QueryRowCtx(ctx, v, query, id)
})
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserModel) FindOneByNumber(ctx context.Context, number string) (*User, error) {
userNumberKey := fmt.Sprintf("%s%v", cacheUserNumberPrefix, number)
var resp User
err := m.QueryRowIndexCtx(ctx, &resp, userNumberKey, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
query := fmt.Sprintf("select %s from %s where `number` = ? limit 1", userRows, m.table)
if err := conn.QueryRowCtx(ctx, &resp, query, number); err != nil {
return nil, err
}
return resp.Id, nil
}, m.queryPrimary)
switch err {
case nil:
return &resp, nil
case sqlc.ErrNotFound:
return nil, ErrNotFound
default:
return nil, err
}
}
func (m *defaultUserModel) Update(ctx context.Context, data *User) error {
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, data.Id)
userNumberKey := fmt.Sprintf("%s%v", cacheUserNumberPrefix, data.Number)
_, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("update %s set %s where `id` = ?", m.table, userRowsWithPlaceHolder)
return conn.ExecCtx(ctx, query, data.Number, data.Name, data.Password, data.Gender, data.Id)
}, userIdKey, userNumberKey)
return err
}
func (m *defaultUserModel) Delete(ctx context.Context, id int64) error {
data, err := m.FindOne(ctx, id)
if err != nil {
return err
}
userIdKey := fmt.Sprintf("%s%v", cacheUserIdPrefix, id)
userNumberKey := fmt.Sprintf("%s%v", cacheUserNumberPrefix, data.Number)
_, err = m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
query := fmt.Sprintf("delete from %s where `id` = ?", m.table)
return conn.ExecCtx(ctx, query, id)
}, userIdKey, userNumberKey)
return err
}
func (m *defaultUserModel) formatPrimary(primary interface{}) string {
return fmt.Sprintf("%s%v", cacheUserIdPrefix, primary)
}
func (m *defaultUserModel) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error {
query := fmt.Sprintf("select %s from %s where `id` = ? limit 1", userRows, m.table)
return conn.QueryRowCtx(ctx, v, query, primary)
}
func (m *defaultUserModel) tableName() string {
return m.table
}

usermodel.go

package model
import (
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/sqlx"
)
var _ UserModel = (*customUserModel)(nil)
type (
// UserModel is an interface to be customized, add more methods here,
// and implement the added methods in customUserModel.
UserModel interface {
userModel
}
customUserModel struct {
*defaultUserModel
}
)
// NewUserModel returns a model for the database table.
func NewUserModel(conn sqlx.SqlConn, c cache.CacheConf) UserModel {
return &customUserModel{
defaultUserModel: newUserModel(conn, c),
}
}

用法#

$ goctl model mysql -h
NAME:
goctl model mysql - generate mysql model
USAGE:
goctl model mysql command [command options] [arguments...]
COMMANDS:
ddl generate mysql model from ddl
datasource generate model from datasource
OPTIONS:
--help, -h show help

生成规则#

  • 默认规则

    我们默认用户在建表时会创建createTime、updateTime字段(忽略大小写、下划线命名风格)且默认值均为CURRENT_TIMESTAMP,而updateTime支持ON UPDATE CURRENT_TIMESTAMP,对于这两个字段生成insertupdate时会被移除,不在赋值范畴内,当然,如果你不需要这两个字段那也无大碍。

  • ddl

    $ goctl model mysql ddl -h
    NAME:
    goctl model mysql ddl - generate mysql model from ddl
    USAGE:
    goctl model mysql ddl [command options] [arguments...]
    OPTIONS:
    --src value, -s value the path or path globbing patterns of the ddl
    --dir value, -d value the target dir
    --style value the file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]
    --cache, -c generate code with cache [optional]
    --idea for idea plugin [optional]
    --database value, --db value the name of database [optional]
    --home value the goctl home path of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
    --remote value the remote git repo of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
    The git repo directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure
    --branch value the branch of the remote repo, it does work with --remote
  • datasource

    $ goctl model mysql datasource -h
    NAME:
    goctl model mysql datasource - generate model from datasource
    USAGE:
    goctl model mysql datasource [command options] [arguments...]
    OPTIONS:
    --url value the data source of database,like "root:password@tcp(127.0.0.1:3306)/database"
    --table value, -t value the table or table globbing patterns in the database
    --cache, -c generate code with cache [optional]
    --dir value, -d value the target dir
    --style value the file naming format, see [https://github.com/zeromicro/go-zero/tree/master/tools/goctl/config/readme.md]
    --idea for idea plugin [optional]
    --home value the goctl home path of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
    --remote value the remote git repo of the template, --home and --remote cannot be set at the same time, if they are, --remote has higher priority
    The git repo directory must be consistent with the https://github.com/zeromicro/go-zero-template directory structure
    --branch value the branch of the remote repo, it does work with --remote

生成代码仅基本的CURD结构。

缓存#

对于缓存这一块我选择用一问一答的形式进行罗列。我想这样能够更清晰的描述model中缓存的功能。

  • 缓存会缓存哪些信息?

    对于主键字段缓存,会缓存整个结构体信息,而对于单索引字段(除全文索引)则缓存主键字段值。

  • 数据有更新(update)操作会清空缓存吗?

    会,但仅清空主键缓存的信息,why?这里就不做详细赘述了。

  • 为什么不按照单索引字段生成updateByXxxdeleteByXxx的代码?

    理论上是没任何问题,但是我们认为,对于model层的数据操作均是以整个结构体为单位,包括查询,我不建议只查询某部分字段(不反对),否则我们的缓存就没有意义了。

  • 为什么不支持findPageLimitfindAll这种模式代码生成?

    目前,我认为除了基本的CURD外,其他的代码均属于业务型代码,这个我觉得开发人员根据业务需要进行编写更好。

类型转换规则#

mysql dataTypegolang dataTypegolang dataType(if null&&default null)
boolint64sql.NullInt64
booleanint64sql.NullInt64
tinyintint64sql.NullInt64
smallintint64sql.NullInt64
mediumintint64sql.NullInt64
intint64sql.NullInt64
integerint64sql.NullInt64
bigintint64sql.NullInt64
floatfloat64sql.NullFloat64
doublefloat64sql.NullFloat64
decimalfloat64sql.NullFloat64
datetime.Timesql.NullTime
datetimetime.Timesql.NullTime
timestamptime.Timesql.NullTime
timestringsql.NullString
yeartime.Timesql.NullInt64
charstringsql.NullString
varcharstringsql.NullString
binarystringsql.NullString
varbinarystringsql.NullString
tinytextstringsql.NullString
textstringsql.NullString
mediumtextstringsql.NullString
longtextstringsql.NullString
enumstringsql.NullString
setstringsql.NullString
jsonstringsql.NullString
最后更新 日期: