ITPub博客

首页 > 数据库 > 数据库开发技术 > 这开源的生成表模型工具,好用

这开源的生成表模型工具,好用

原创 数据库开发技术 作者:皇子谈技术 时间:2024-06-03 08:29:00 0 删除 编辑

生成表模型工具好用的定义是什么,方便、功能齐全。

大家好,我是皇子。

外面已经有很多工具能生成Golang gorm 的表模型了,但是在个人项目中和公司的项目中发现都不方便直接使用。

原因一:模型定义的格式不完整,排版难看,没有注释。

原因二:功能单一,只是为了生成一个模型而已,缺少关于模型的常用操作方法。

原因三:可读性和扩展性差,各种表字段满天飞,各种SQL组装,容易出错。

甚至,还有些工具是收费的。

所以针对以上的问题,我开源一个表模型工具,下面开始介绍。

GitHub 地址:https://github.com/leeprince/gormstruct

一、工具说明

这工具不仅能生成Golang gorm 的表模型,还能基于模型生成所有字段的操作方法在DAO层。


(一)关于模型

  1. 生成模型 :生成模型,包含每个字段的注释,注释统一在右边//对齐,方便全局观察字段的定义情况。
  2. 生成表的所有字段映射:使用字段自定义查询时,统一使用映射的字段,在字段的维护上更方便;同时默认查询所有字段,不使用*,不使用反射查询所有字段。
  3. 为所有字段生成SetXxx()、GetXxx()方法:用户设置/获取模型下指定字段的值

示例

(二)关于DAO层

生成操作表的基本方法,对应文件名:base_DAO.go 对应的表 DAO 层:xxx_dao.go 里面包含多种操作表数据的简单、易用、易维护方法。

❝

自动判断是否存在软删除字段,存在则默认过滤非软删除记录

  • 生成UpdateOrCreate()方法:存在则更新(只会更新非零值),否则插入(会忽略零值字段)

  • 生成Save()方法:gorm 原生的 Save 会保存所有的字段,即使字段是零值。仅会判断主键是否存在,存在则更新,不存在则创建

  • 生成Create()方法:创建数据:允许单条/批量创建,批量创建时传入切片

  • 为所有字段生成 WithXxx方法:将单个字段的值作为查询条件

  • 为所有字段生成 WithXxxs方法(多一个s):将单个字段的列表值作为查询条件

  • 生成GetByOption()方法:函数选项模式获取单条记录

  • 生成GetListByOption方法:函数选项模式获取多条记录:支持分页

  • 生成GetCountByOption()方法:函数选项模式获取多条记录:支持统计记录总数

  • 生成GetCustomeResultByOption()方法:函数选项模式获取多条记录到自定义结构体(result:务必使用指针变量):支持包含自定义聚合字段(自定义的聚合字段务必添加 gorm:"column:字段的别名;" 标签)

  • 生成UpdateByOption():更新数据

  • 生成GetFromXxx()方法:简单直接通过单个字段值获取数据(自动判断该字段是否设置唯一索引,否则自动获取单条记录,反之则是多条记录)

  • 生成GetListFromXxx()方法:简单直接通过单个字段的列表值获取多条记录

  • 生成FetchByXxx()方法:通过索引(唯一索引(主键、唯一索引、唯一复合索引)、非唯一索引(普通索引))作为查询条件获取数据(自动判断索引类型确定获取单条记录还是多条记录)

示例


type UsersDAO struct {
*_BaseDAO
}

// NewUsersDAO 初始化
func NewUsersDAO(ctx context.Context, db *gorm.DB) *UsersDAO {
if db == nil {
panic(fmt.Errorf("UsersDAO need init by db"))
}
ctx, cancel := context.WithCancel(ctx)
return &UsersDAO{
_BaseDAO: &_BaseDAO{
DB: db.Model(&Users{}),
db: db,
model: Users{},
ctx: ctx,
cancel: cancel,
timeout: -1,
columns: UsersAllColumns,
isDefaultColumns: true,
},
}
}

// GetTableName 获取表名称
func (obj *UsersDAO) GetTableName() string {
users := &Users{}
return users.TableName()
}

// UpdateOrCreate 存在则更新,否则插入,会忽略零值字段
func (obj *UsersDAO) UpdateOrCreate(users *Users) (rowsAffected int64, err error) {
if users.PrimaryKeyValue() > 0 {
return obj.UpdateByOption(users, obj.WithPrimaryKey(users.PrimaryKeyValue()))
}
return obj.Create(users)
}

// Save gorm 原生的 Save 会保存所有的字段,即使字段是零值。仅会判断主键是否存在,存在则更新,不存在则创建
func (obj *UsersDAO) Save(users *Users) (rowsAffected int64) {
return obj.db.Save(users).RowsAffected
}

// Create 创建数据:允许单条/批量创建,批量创建时传入切片
func (obj *UsersDAO) Create(users interface{}) (rowsAffected int64, err error) {
tx := obj.withContext().Create(users)
return tx.RowsAffected, tx.Error
}

// --- 表中的字段作为 option 条件 ---

// WithPrimaryKey 设置真正的主键 字段作为 option 条件
func (obj *UsersDAO) WithPrimaryKey(primaryKeyValue interface{}) Option {
return queryMapOptionFunc(func(o *options) { o.queryMap[(&Users{}).PrimaryKey()] = primaryKeyValue })
}

// WithID 设置 id(主键) 字段作为 option 条件
func (obj *UsersDAO) WithID(id int64) Option {
return queryMapOptionFunc(func(o *options) { o.queryMap[UsersColumns.ID] = id })
}

// WithIDs 设置 id(主键) 字段的切片作为 option 条件
func (obj *UsersDAO) WithIDs(ids []int64) Option {
return queryMapOptionFunc(func(o *options) { o.queryMap[UsersColumns.ID] = ids })
}


...


// --- 单个字段作为查询条件 ---

// GetFromID 通过单个 id(主键) 字段值,获取单条记录
func (obj *UsersDAO) GetFromID(id int64) (result *Users, err error) {
result, err = obj.GetByOption(obj.WithID(id))
return
}

// GetListFromID 通过多个 id(主键) 字段值,获取多条记录
func (obj *UsersDAO) GetListFromID(ids []int64) (results []*Users, err error) {
results, err = obj.GetListByOption(obj.WithIDs(ids))
return
}


...


// --- 通过索引(唯一索引(主键、唯一索引、唯一复合索引)、非唯一索引(普通索引))作为查询条件 ---

// FetchByPrimaryKey 通过 id 字段值,获取单条记录
func (obj *UsersDAO) FetchByPrimaryKey(id int64) (result *Users, err error) {
result, err = obj.GetByOption(obj.WithID(id))
return
}

// FetchUniqueByCardNo 通过 card_no 字段值,获取单条记录
func (obj *UsersDAO) FetchUniqueByCardNo(cardNo string) (result *Users, err error) {
result, err = obj.GetByOption(obj.WithCardNo(cardNo))
return
}

...
❝

所有的方法:在项目中都包含相应的测试用例test/xxx_test.go

二、快速使用

(一)使用步骤

  1. 克隆项目到本地(https://github.com/leeprince/gormstruct)
  2. 重命名./config/config.bak.yaml为./config/config.yaml
  3. 修改dbinfo为自己的数据库链接配置
# 数据库设置
dbinfo:
host: 127.0.0.1
port: 3306
username: root
password: root
database: ticket # 该参数可通过命令行设置,命令行设置会覆盖该值。如果未设置则必须在命令中指定
type: 0 # 数据库类型:0:mysql
  1. 运行指令go run main.go -t={数据库下的表名}会在./out/model下生成3个文件
  • 模型:表名xxx.go
  • DAO层:表名_DAO.go
  • 基础DAO层:base.go(只有在第一次该工具时生成)

(二)帮助命令

❝

go run main.go --help go run main.go gen --help

表名参数(-t)必填!

go run main.go -t=users [-p=model] [-s=Users]
go run main.go --table=users [--packageName=model] [--structName=Users]
-d, --database string 指定连接的数据库名【在yaml配置时可选,命令设置优先级大于配置】
-t, --table string 指定的表名【必填】
-h, --help help for gen
-p, --packageName string 生成的包名【可选,默认:model】
-s, --structName string 表名对应结构体【可选,默认:表名的大驼峰命名】

三、最近更新

(一)调整生成的 base_dao.go 文件中方法的顺序

略

(二)支持 WithWhere 多次条件并查询

WithWhere 支持原生的SQL组装。之前不考虑支持的原因是希望减少SQL组装的手动设置,防止查询条件扩散难于维护。

工作中我都会只用一个WithWhere ,并将所有需要组装的条件包含。

目前,在公司新项目中,我都是使用了这个工具,同事也很推荐,所以也向我提了反馈想要WithWhere支持多次使用。

理由是:多次使用更加灵活,更加可读。

也给了我一些建议,所以考虑后,决定更新并向前兼容。

未更新前:只能使用一次WithWhere方法,所以需要多条件一次性组装完成

userDAO := model.NewUsersDAO(context.Background(), db)
opt := []model.Option{
// 只能使用一次WithWhere方法
userDAO.WithWhere("age >= ? AND age <= ?", 18, 30)
}
users, err := userDAO.GetListByOption(opt...)

更新后:支持使用多次WithWhere方法

userDAO := model.NewUsersDAO(context.Background(), db)
opt := []model.Option{
// 支持使用多次WithWhere方法
userDAO.WithWhere("age >= ?", 18),
userDAO.WithWhere("age <= ?", 30),
}
users, err := userDAO.GetListByOption(opt...)

使用场景

接口请求中要求查询年龄区间的所有用户。

有两个参数:minAge(最小年龄)、maxAge(最大年龄)

说明:minAge(最小年龄),maxAge(最大年龄)都可选

那么未更新前:只能使用一次WithWhere方法,所以需要多条件一次性组装完成

var sql string
if req.minAge > 0 {
sql = "age >= " + req.minAge
}
if req.maxAge > 0 {
if sql == "" {
sql = "age <=" + req.maxAge
} else {
sql += " AND age <=" + req.maxAge
}
}
userDAO := model.NewUsersDAO(context.Background(), db)
opt = []model.Option{
// 只能使用一次WithWhere方法,所以前面必须组装好SQL再传入WithWhere方法中
userDAO.WithWhere(sql),
}
users, err := userDAO.GetListByOption(opt...)

而更新后:支持使用多次WithWhere方法

// 支持使用多次WithWhere方法,所以判断后直接新增查询条件即可
opt := []model.Option{}
if req.minAge > 0 {
opt = append(opt, userDAO.WithWhere("age >= ?", req.minAge))
}
if req.maxAge > 0 {
opt = append(opt, userDAO.WithWhere("age <= ?", req.maxAge))
}
userDAO := model.NewUsersDAO(context.Background(), db)
users, err := userDAO.GetListByOption(opt...)

(三)后面计划

  • [ ] 支持一次性指定多个表 - P1
  • [ ] 多条件OR查询 - P2
  • [ ] 支持指定导出模型和DAO层文件的包名和路径。不一定会做,考虑误操作

点个【赞和在看】,让开源更有更有意义。

推荐阅读

米哈游放弃了,五一先稳住

阿里面试体验,贼舒服!

腾讯三面,被拷打了

B站三面,挑战有点大

得物不错,可惜止步于二面


关于作者:一位热爱技术,并在职场与自媒体间探索的实践者,希望通过分享个人经验和见解,帮助更多人实现自我成长和价值。

如果您对我的公众号内容感兴趣,欢迎关注我。也可以在下方添加我微信和一支烟花AI社区技术群,一起交流技术、职场心得、自媒体运营经验。欢迎~

原文作者:皇子谈技术;如有侵权,请联系管理员删除。

请登录后发表评论 登录
全部评论

注册时间:2023-12-21

  • 博文量
    113
  • 访问量
    56513