ORM概念介绍 #
虽然Go的原生SQL包database/sql提供了最大的灵活性和控制力,但在实际项目中,我们经常需要一种更便捷、更安全、更贴近业务逻辑的方式来操作数据库。这就是ORM(对象关系映射)技术的用武之地。
什么是ORM #
ORM(Object-Relational Mapping,对象关系映射)是一种编程技术,它建立了编程语言中的对象与关系型数据库中表的映射关系,使开发者能够使用面向对象的方式来操作数据库,而无需直接编写SQL语句。
- 数据表映射为结构体
- 表中的字段映射为结构体的字段
- 表中的记录映射为结构体的实例
- SQL操作映射为对象上的方法调用
ORM的优势 #
- 生产力提升:减少重复的CRUD(创建、读取、更新、删除)操作代码
- 面向对象的编程方式:使用对象和方法代替SQL语句
- 数据库抽象:减少与特定数据库系统的耦合
- 类型安全:编译时检查替代运行时SQL字符串拼接错误
- 安全性:减少SQL注入风险
- 自动处理关联关系:方便处理一对一、一对多、多对多等关系
- 内置迁移工具:简化数据库结构变更管理
ORM的劣势 #
- 性能开销:在简单查询上可能比原生SQL慢
- 学习曲线:需要学习ORM框架的特定API
- 复杂查询限制:有些复杂SQL查询可能难以用ORM表达
- 抽象泄漏:在某些情况下,隐藏的数据库细节可能会影响应用行为
- 过度使用可能导致非最优SQL:自动生成的SQL可能不如手写的优化
Go语言中的主要ORM库 #
- GORM:目前最流行的Go语言ORM库,功能全面且活跃维护
- XORM:另一个成熟的ORM库,具有良好的性能
- Ent:Facebook开发的实体框架,使用代码生成的方式
- SQLBoiler:以生成代码为主的ORM,专注于类型安全
- SQLx:介于原生SQL和ORM之间的库,提供便捷的查询构建器
GORM #
GORM是Go语言中最受欢迎的ORM库,提供了友好的API,并支持各种数据库,包括MySQL、PostgreSQL、SQLite和SQL Server等。
官网:https://gorm.io/
安装 #
**注意:**gorm 使用的驱动和database/sql包的驱动不一样
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql # MySQL驱动
# 或其他数据库驱动
go get -u gorm.io/driver/postgres # PostgreSQL驱动
go get -u gorm.io/driver/sqlite # SQLite驱动
go get -u gorm.io/driver/sqlserver # SQL Server驱动
连接数据库 #
GORM使用的数据库连接池由底层的*sql.DB实例管理,我们可以通过db.DB()方法获取这个实例并设置连接池参数。
package database
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"time"
)
type MySQLConfig struct {
DSN string
MaxIdleConns int
MaxOpenConns int
ConnMaxLifetime int
}
// InitDB 创建数据库连接
func InitDB(config *MySQLConfig) *gorm.DB {
db, err := gorm.Open(mysql.Open(config.DSN), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 设置日志级别
})
if err != nil {
log.Fatalf("数据库连接失败,%v", err)
return nil
}
// 获取底层SQL连接以设置连接池参数
sqlDB, err := db.DB()
if err != nil {
log.Fatalf("无法获取数据库连接: %v", err)
}
// 设置连接池参数
sqlDB.SetMaxIdleConns(config.MaxIdleConns) // 设置空闲连接池中连接的最大数量
sqlDB.SetMaxOpenConns(config.MaxOpenConns) // 设置打开数据库连接的最大数量
sqlDB.SetConnMaxLifetime(time.Duration(config.ConnMaxLifetime) * time.Second) // 设置了连接可复用的最大时间
log.Println("成功连接到数据库!")
return db
}
package main
import "ygang.top/zhiyingkuaichuang/internal/database"
func main() {
db := database.InitDB(&database.MySQLConfig{
DSN: "root:123456@tcp(localhost:3306)/mcp_demo?charset=utf8&loc=Local",
ConnMaxLifetime: 60,
MaxOpenConns: 10,
MaxIdleConns: 5,
})
}
定义模型 #
在GORM中,模型是与数据库表映射的结构体。我们通过定义Go结构体来创建数据库模型:
type User struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time `gorm:"autoCreateTime"`
UpdatedAt time.Time `gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string `gorm:"size:255;not null"`
Email string `gorm:"size:255;uniqueIndex;not null"`
Age uint8 `gorm:"default:18"`
Address string `gorm:"type:varchar(500)"`
IsActive bool `gorm:"default:true"`
}
约定 #
- 主键:GORM 使用一个名为
ID的字段作为每个模型的默认主键。 - 表名:默认情况下,GORM 将结构体名称
SnakeCase转换为snake_case并为表名加上复数形式。 例如,一个User结构体在数据库中的表名变为users。 - 列名:GORM 自动将结构体字段名称
SnakeCase转换为snake_case作为数据库中的列名。 - 时间戳字段:GORM使用字段
CreatedAt和UpdatedAt来自动跟踪记录的创建和更新时间,后面的autoCreateTime和autoUpdateTime必须要带上。 - 软删除:
DeletedAt用于软删除(将记录标记为已删除,而实际上并未从数据库中删除)。
Struct Tag #
在声明模型时,标签是可选的,GORM支持以下标签:标签不区分大小写,但首选camelCase。如果使用多个标记,则应使用分号;分隔。对解析器具有特殊意义的字符可以用反斜杠\转义,使其可以用作参数值。
| 标签名 | 说明 |
|---|---|
column |
指定 db 列名 |
type |
列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null、size, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT |
serializer |
指定将数据序列化或反序列化到数据库中的序列化器, 例如: serializer:json/gob/unixtime |
size |
定义列数据类型的大小或长度,例如 size: 256 |
primaryKey |
将列定义为主键 |
unique |
将列定义为唯一键 |
default |
定义列的默认值 |
precision |
指定列的精度 |
scale |
指定列大小 |
not null |
指定列为 NOT NULL |
autoIncrement |
指定列为自动增长 |
autoIncrementIncrement |
自动步长,控制连续记录之间的间隔 |
embedded |
嵌套字段 |
embeddedPrefix |
嵌入字段的列名前缀 |
autoCreateTime |
创建时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano |
autoUpdateTime |
创建/更新时追踪当前时间,对于 int 字段,它会追踪时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoUpdateTime:milli |
index |
根据参数创建索引,多个字段使用相同的名称则创建复合索引。 |
uniqueIndex |
与 index 相同,但创建的是唯一索引 |
check |
创建检查约束,例如 check:age > 13。 |
<- |
设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限 |
-> |
设置字段读的权限,->:false 无读权限 |
- |
忽略该字段,- 表示无读写,-:migration 表示无迁移权限,-:all 表示无读写迁移权限 |
comment |
迁移时为字段添加注释 |
自定义表名 #
默认情况下,GORM会使用结构体名称的蛇形复数作为表名。例如,User结构体对应的表名是users。如果需要自定义表名,可以实现TableName方法:
// TableName 指定用户模型的表名
func (User) TableName() string {
return "my_users"
}
或者全局设置表名:
db.Config.NamingStrategy = schema.NamingStrategy{
TablePrefix: "t_", // 表名前缀
SingularTable: true, // 使用单数表名
}
自动迁移 #
GORM提供了自动迁移功能,它会自动创建表、缺失的外键、约束、列和索引,并且会更改现有列的类型(如果大小、精度、是否为空等发生变化)。但它不会删除未使用的列,以保护数据。
// 自动迁移
err := db.AutoMigrate(&User{}, &Product{}, &Order{})
if err != nil {
log.Fatalf("自动迁移失败: %v", err)
}
注意:自动迁移只应在开发环境中使用。在生产环境中,应该使用数据库迁移工具,如golang-migrate或Atlas。
数据库操作 #
基本 CRUD 操作 #
新增 #
// 创建一条记录
user := User{Name: "张三", Email: "zhangsan@example.com", Age: 25}
result := db.Create(&user)
if result.Error != nil {
log.Fatalf("创建用户失败: %v", result.Error)
}
fmt.Printf("创建用户成功,ID: %d, 受影响的行数: %d\n", user.ID, result.RowsAffected)
// 批量创建
users := []User{
{Name: "李四", Email: "lisi@example.com", Age: 30},
{Name: "王五", Email: "wangwu@example.com", Age: 28},
}
result = db.Create(&users)
if result.Error != nil {
log.Fatalf("批量创建用户失败: %v", result.Error)
}
fmt.Printf("批量创建用户成功,受影响的行数: %d\n", result.RowsAffected)
查询 #
// 查询单条记录
var firstUser User
result := db.First(&firstUser) // 获取第一条记录(按主键排序)
if result.Error != nil {
log.Fatalf("查询失败: %v", result.Error)
}
fmt.Printf("第一条用户记录: %+v\n", firstUser)
// 根据主键查询
var user User
result = db.First(&user, 10) // 查找ID为10的用户
// 或者
result = db.First(&user, "id = ?", 10)
// 查询多条记录
var users []User
result = db.Find(&users) // 查询所有用户
fmt.Printf("总共查询到 %d 个用户\n", len(users))
// 条件查询
var activeUsers []User
result = db.Where("age > ? AND is_active = ?", 20, true).Find(&activeUsers)
// 或者使用结构体
result = db.Where(&User{IsActive: true}).Where("age > ?", 20).Find(&activeUsers)
// 或者使用map
result = db.Where(map[string]interface{}{"is_active": true}).Find(&activeUsers)
// 排序
var orderedUsers []User
db.Order("age desc, name").Limit(10).Find(&orderedUsers)
// 分页
var pageUsers []User
var totalCount int64
db.Model(&User{}).Count(&totalCount)
db.Offset(10).Limit(10).Find(&pageUsers) // 第二页,每页10条
// 选择特定字段
var partialUsers []User
db.Select("name", "email").Find(&partialUsers)
更新 #
// 保存所有字段
var user User
db.First(&user, 1)
user.Name = "新名字"
user.Age = 32
db.Save(&user) // 更新所有字段
// 更新单个字段
db.Model(&user).Update("Name", "更新的名字")
// 更新多个字段
db.Model(&user).Updates(User{Name: "新名字", Age: 35}) // 只会更新非零值字段
// 或者使用map更新任意值,包括零值
db.Model(&user).Updates(map[string]interface{}{"name": "新名字", "age": 0, "is_active": false})
// 批量更新
db.Model(&User{}).Where("age > ?", 30).Update("is_active", false)
删除 #
// 删除记录
var user User
db.First(&user, 1) // 删除 id 为 1 的记录
db.Delete(&user) // 软删除,会设置DeletedAt
// 根据主键删除
db.Delete(&User{}, 10)
// 或者
db.Delete(&User{}, []int{1, 2, 3})
// 批量删除
db.Where("age < ?", 18).Delete(&User{})
// 永久删除
db.Unscoped().Delete(&user) // 永久删除,不使用软删除
条件查询 #
// 基本条件
db.Where("name = ?", "张三").First(&user)
// NOT条件
db.Not("name = ?", "张三").Find(&users)
// OR条件
db.Where("name = ?", "张三").Or("name = ?", "李四").Find(&users)
// AND条件
db.Where("name = ? AND age >= ?", "张三", 20).Find(&users)
// IN条件
db.Where("name IN ?", []string{"张三", "李四", "王五"}).Find(&users)
// LIKE条件
db.Where("name LIKE ?", "%张%").Find(&users)
// 时间范围
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// 原始SQL
db.Raw("SELECT * FROM users WHERE name = ?", "张三").Scan(&users)
事务 #
// 手动事务
tx := db.Begin()
defer func() {
if r := recover(); r != nil {
tx.Rollback() // 发生panic时回滚
}
}()
if err := tx.Create(&User{Name: "事务1"}).Error; err != nil {
tx.Rollback()
return
}
if err := tx.Create(&User{Name: "事务2"}).Error; err != nil {
tx.Rollback()
return
}
// 提交事务
if err := tx.Commit().Error; err != nil {
log.Fatalf("提交事务失败: %v", err)
}
// 使用事务闭包
err := db.Transaction(func(tx *gorm.DB) error {
if err := tx.Create(&User{Name: "事务3"}).Error; err != nil {
return err // 返回任何错误都会回滚事务
}
if err := tx.Create(&User{Name: "事务4"}).Error; err != nil {
return err
}
// 返回nil将提交事务
return nil
})
钩子(Hooks) #
GORM允许在特定操作之前或之后运行代码,这些钩子方法可以用来设置值或处理错误:
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
// 创建记录前的钩子
u.UpdatedAt = time.Now()
if u.Name == "" {
err = errors.New("用户名不能为空")
}
return
}
func (u *User) AfterCreate(tx *gorm.DB) (err error) {
// 创建记录后的钩子
fmt.Printf("用户 %s 已创建\n", u.Name)
return
}
GORM提供了以下钩子点:
- 创建:
BeforeSave,BeforeCreate,AfterCreate,AfterSave - 更新:
BeforeSave,BeforeUpdate,AfterUpdate,AfterSave - 删除:
BeforeDelete,AfterDelete - 查询:
AfterFind
关联(Associations) #
GORM提供了多种关联关系的支持,包括:
- 一对一(has one, belongs to)
- 一对多(has many)
- 多对多(many to many)
一对一关系 #
一对一关系意味着一个记录只与另一个表中的一条记录相关联。在GORM中,一对一关系可以通过has one或belongs to表示。
Has One(拥有一个) #
// 用户有一个信用卡
type User struct {
ID uint
Name string
CreditCard CreditCard // has one 关系
}
type CreditCard struct {
ID uint
Number string
UserID uint // 外键
}
// 查询用户的信用卡
var user User
db.First(&user, 1)
db.Model(&user).Association("CreditCard").Find(&creditCard)
// 预加载关系
var userWithCard User
db.Preload("CreditCard").First(&userWithCard, 1)
Belongs To(属于) #
belongs to关系表示一个模型属于另一个模型。例如,每个信用卡属于一个用户:
// 信用卡属于一个用户
type CreditCard struct {
ID uint
Number string
UserID uint // 外键
User User // belongs to 关系
}
type User struct {
ID uint
Name string
}
// 查询信用卡所属的用户
var card CreditCard
db.First(&card, 1)
db.Model(&card).Association("User").Find(&user)
// 预加载关系
var cardWithUser CreditCard
db.Preload("User").First(&cardWithUser, 1)
一对多关系 #
一对多关系表示一个模型可以有多个其他模型的实例。例如,一个用户可以有多篇文章:
// 用户有多篇文章
type User struct {
ID uint
Name string
Articles []Article // has many 关系
}
type Article struct {
ID uint
Title string
Content string
UserID uint // 外键
}
// 查询用户的所有文章
var user User
db.First(&user, 1)
var articles []Article
db.Model(&user).Association("Articles").Find(&articles)
// 添加关联
newArticle := Article{Title: "新文章", Content: "文章内容..."}
db.Model(&user).Association("Articles").Append(&newArticle)
// 替换关联
db.Model(&user).Association("Articles").Replace(&newArticles)
// 删除关联
db.Model(&user).Association("Articles").Delete(&articlesToDelete)
// 清空关联
db.Model(&user).Association("Articles").Clear()
// 计数
count := db.Model(&user).Association("Articles").Count()
// 预加载关系
var userWithArticles User
db.Preload("Articles").First(&userWithArticles, 1)
多对多关系 #
多对多关系表示两个模型之间互相拥有多个实例。例如,一个用户可以有多个角色,一个角色也可以被多个用户拥有:
// 用户和角色是多对多关系
type User struct {
ID uint
Name string
Roles []Role `gorm:"many2many:user_roles;"` // 多对多关系
}
type Role struct {
ID uint
Name string
Users []User `gorm:"many2many:user_roles;"` // 多对多关系
}
// 多对多关系会自动创建连接表
// user_roles表会包含user_id和role_id两个外键
// 查询用户的所有角色
var user User
db.First(&user, 1)
var roles []Role
db.Model(&user).Association("Roles").Find(&roles)
// 为用户添加角色
var role Role
db.First(&role, "name = ?", "管理员")
db.Model(&user).Association("Roles").Append(&role)
// 预加载关系
var userWithRoles User
db.Preload("Roles").First(&userWithRoles, 1)
关联预加载 #
预加载是避免N+1查询问题的重要技术。N+1问题指的是,当需要获取一个模型及其关联时,首先执行一次查询获取主模型,然后针对每个主模型实例再执行额外的查询来获取关联数据。
GORM提供了Preload方法来一次性加载关联:
// 预加载单个关联
var users []User
db.Preload("CreditCard").Find(&users)
// 预加载多个关联
db.Preload("CreditCard").Preload("Articles").Find(&users)
// 预加载嵌套关联
db.Preload("Articles.Comments").Find(&users)
// 预加载带条件的关联
db.Preload("Articles", "published = ?", true).Find(&users)
延迟加载与急切加载 #
默认情况下,GORM使用延迟加载(lazy loading)模式,即只有在需要时才加载关联数据。但是这可能导致N+1查询问题。
为了避免此问题,我们可以使用急切加载(eager loading):
// 急切加载 - 一次查询获取所有需要的数据
var users []User
db.Preload("CreditCard").Preload("Articles").Find(&users)
// 对比延迟加载 - 可能导致N+1查询
var users []User
db.Find(&users)
for _, user := range users {
var card CreditCard
db.Model(&user).Association("CreditCard").Find(&card)
var articles []Article
db.Model(&user).Association("Articles").Find(&articles)
}
高级GORM功能 #
批量操作 #
对于需要处理大量数据的场景,GORM提供了批量操作功能:
// 批量插入
var users = []User{
{Name: "用户1", Age: 18},
{Name: "用户2", Age: 20},
// 更多用户...
}
// 使用Create批量插入
db.Create(&users)
// 使用CreateInBatches控制每批次插入的数量
db.CreateInBatches(&users, 100) // 每批次100条
// 批量更新
db.Model(&User{}).Where("age < ?", 20).Updates(map[string]interface{}{"is_adult": false})
// 批量删除
db.Where("created_at < ?", lastMonth).Delete(&User{})
自定义数据类型 #
GORM允许我们定义自定义数据类型,以满足特定的业务需求:
type Status string
const (
StatusPending Status = "pending"
StatusProcessing Status = "processing"
StatusCompleted Status = "completed"
StatusFailed Status = "failed"
)
// 继承Scanner和Valuer接口以便与数据库交互
func (s *Status) Scan(value interface{}) error {
*s = Status(value.(string))
return nil
}
func (s Status) Value() (driver.Value, error) {
return string(s), nil
}
type Order struct {
ID uint
UserID uint
Amount float64
Status Status `gorm:"type:string;default:'pending'"`
}
原生SQL与复杂查询 #
对于复杂查询,GORM允许我们使用原生SQL:
// 原生SQL查询
var users []User
db.Raw("SELECT * FROM users WHERE age > ? AND is_active = ?", 18, true).Scan(&users)
// 执行原生SQL
db.Exec("UPDATE users SET is_active = ? WHERE last_login < ?", false, lastMonth)
// 使用子查询
subQuery := db.Model(&User{}).Select("id").Where("age > ?", 18)
db.Where("id IN (?)", subQuery).Find(&users)
// 使用命名参数
db.Where("name = @name AND age = @age", map[string]interface{}{"name": "张三", "age": 18}).Find(&users)
软删除与硬删除 #
GORM的模型如果包含gorm.DeletedAt字段,默认会启用软删除功能:
// 软删除(默认行为)
db.Delete(&user)
// 查询时会自动排除软删除的记录
db.Find(&users) // 仅返回未删除的记录
// 包括已删除记录
db.Unscoped().Find(&users) // 返回所有记录,包括已删除的
// 永久删除(硬删除)
db.Unscoped().Delete(&user) // 永久删除记录
GORM会话模式 #
GORM v2引入了会话模式,可以在不同的操作中重用设置:
// 创建一个新会话
session := db.Session(&gorm.Session{
PrepareStmt: true, // 预编译语句
Logger: logger.Default.LogMode(logger.Info),
})
// 在会话中执行多个操作
var user User
session.First(&user, 1)
session.Model(&user).Update("is_active", true)
// 创建一个只读会话
readSession := db.Session(&gorm.Session{
PrepareStmt: true,
QueryFields: true, // 列出所有字段
DryRun: true, // 生成SQL但不执行
})
// 查看生成的SQL
stmt := readSession.Find(&users).Statement
fmt.Println(stmt.SQL.String())
使用事务处理关联 #
在处理关联关系时,使用事务确保数据一致性非常重要:
err := db.Transaction(func(tx *gorm.DB) error {
// 创建用户
user := User{Name: "新用户"}
if err := tx.Create(&user).Error; err != nil {
return err
}
// 为用户创建信用卡
card := CreditCard{Number: "1234-5678-9012-3456", UserID: user.ID}
if err := tx.Create(&card).Error; err != nil {
return err
}
// 为用户创建文章
article := Article{Title: "我的第一篇文章", UserID: user.ID}
if err := tx.Create(&article).Error; err != nil {
return err
}
return nil
})
if err != nil {
log.Fatalf("事务失败: %v", err)
}
使用中间件和插件 #
GORM允许我们通过实现接口来创建插件,扩展其功能:
type GormPlugin struct{}
func (p *GormPlugin) Name() string {
return "MyPlugin"
}
func (p *GormPlugin) Initialize(db *gorm.DB) error {
// 注册回调
db.Callback().Create().Before("gorm:create").Register("my_plugin:before_create", func(db *gorm.DB) {
// 在创建前执行逻辑
fmt.Println("Before Create Hook!")
})
return nil
}
// 使用插件
db.Use(&GormPlugin{})
Gen #
GEN 是一个基于 GORM 的安全 ORM 框架, 由字节跳动无恒实验室与 GORM 作者联合研发,主要功能说白了就是帮助生成数据表对应的模型文件和更安全方便地执行SQL。
安装 #
# 安装Gorm和mysql驱动
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
# 安装Gen
go get -u gorm.io/gen
生成 #
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
// 更新会覆盖原有文件,所以通过 g.GenerateModel("oss", fieldOpts...) 指定需要更新的表,不要全部覆盖
const DBDSN = "root:123456@(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
func main() {
cfg := gen.Config{
OutPath: "./internal/dao", // 生成查询方法的目录
ModelPkgPath: "./internal/model", // 结构体生成目录
Mode: gen.WithoutContext | gen.WithDefaultQuery | gen.WithQueryInterface,
// 表字段可为 null 值时, 对应结体字段使用指针类型
FieldNullable: true,
// 表字段默认值与模型结构体字段零值不一致的字段, 在插入数据时需要赋值该字段值为零值的, 结构体字段须是指针类型才能成功, 即`FieldCoverable:true`配置下生成的结构体字段.
// 因为在插入时遇到字段为零值的会被GORM赋予默认值. 如字段`age`表默认值为10, 即使你显式设置为0最后也会被GORM设为10提交.
// 如果该字段没有上面提到的插入时赋零值的特殊需要, 则字段为非指针类型使用起来会比较方便.
FieldCoverable: false,
// 模型结构体字段的数字类型的符号表示是否与表字段的一致, `false`指示都用有符号类型
FieldSignable: false,
// 生成 gorm 标签的字段索引属性
FieldWithIndexTag: false,
// 生成 gorm 标签的字段类型属性
FieldWithTypeTag: true, // generate with gorm column type tag
}
// 处理表名
cfg.WithTableNameStrategy(func(tableName string) (targetTableName string) {
// 需要忽略的表
//if strings.EqualFold(tableName, "pay_credit") ||
// strings.EqualFold(tableName, "pay_log") ||
// strings.EqualFold(tableName, "space") ||
// strings.EqualFold(tableName, "space_account") {
// return ""
//}
return tableName
})
// 处理 model名
cfg.WithModelNameStrategy(func(tableName string) (targetTableName string) {
s := tableName
//if strings.HasPrefix(tableName, "conf_") {
// s = strings.TrimPrefix(tableName, "conf_")
//}
ns := schema.NamingStrategy{SingularTable: true}
return ns.SchemaName(s)
return tableName
})
// 处理文件名
cfg.WithFileNameStrategy(func(tableName string) (targetTableName string) {
//if strings.HasPrefix(tableName, "conf_") {
// return strings.TrimPrefix(tableName, "conf_")
//}
return tableName
})
g := gen.NewGenerator(cfg)
gormdb, _ := gorm.Open(mysql.Open(DBDSN))
g.UseDB(gormdb) // reuse your gorm db
// 自定义字段的数据类型
dataMap := map[string]func(detailType gorm.ColumnType) (dataType string){
"tinyint": func(detailType gorm.ColumnType) (dataType string) { return "bool" },
"smallint": func(detailType gorm.ColumnType) (dataType string) { return "int" },
"mediumint": func(detailType gorm.ColumnType) (dataType string) { return "int" },
"bigint": func(detailType gorm.ColumnType) (dataType string) { return "int" },
"int": func(detailType gorm.ColumnType) (dataType string) { return "int" },
}
// 要先于`ApplyBasic`执行
g.WithDataTypeMap(dataMap)
// 自定义模型结体字段的标签
// 将特定字段名的 json 标签加上`string`属性,即 MarshalJSON 时该字段由数字类型转成字符串类型
//jsonField := gen.FieldJSONTagWithNS(func(columnName string) (tagContent string) {
// toStringField := `balance, `
// if strings.Contains(toStringField, columnName) {
// return columnName + ",string"
// }
// return columnName
//})
//delField := gen.FieldType("deleted_at", "time.Time") // 不生成 默认的类型
//sizeField := gen.FieldType("size", "uint64") // 不生成 默认的类型
// 将非默认字段名的字段定义为自动时间戳和软删除字段;
// 自动时间戳默认字段名为:`updated_at`、`created_at, 表字段数据类型为: INT 或 DATETIME
// 软删除默认字段名为:`deleted_at`, 表字段数据类型为: DATETIME
autoUpdateTimeField := gen.FieldGORMTag("update_time", func(tag field.GormTag) field.GormTag {
return tag.Set("autoUpdateTime")
})
autoCreateTimeField := gen.FieldGORMTag("create_time", func(tag field.GormTag) field.GormTag {
return tag.Set("autoCreateTime")
})
//softDeleteField := gen.FieldType("delete_time", "soft_delete.DeletedAt")
// 模型自定义选项组
fieldOpts := []gen.ModelOpt{autoUpdateTimeField, autoCreateTimeField}
// 创建模型的结构体,生成文件在 model 目录; 先创建的结果会被后面创建的覆盖
// 这里创建个别模型仅仅是为了拿到`*generate.QueryStructMeta`类型对象用于后面的模型关联操作中
// Address := g.GenerateModel("address")
// 创建 全部模型文件 , 并覆盖前面创建的同名模型
allModel := g.GenerateAllTable(fieldOpts...)
// 指定特定的表名
//models := []interface{}{
// g.GenerateModel("workspace_subscribe_log", fieldOpts...),
// g.GenerateModel("workspace_bill_log", fieldOpts...),
// g.GenerateModel("workspace_version", fieldOpts...),
// g.GenerateModel("storage_usage", fieldOpts...),
// g.GenerateModel("inbox", fieldOpts...),
//}
// 创建模型的方法,生成文件在 query 目录; 先创建结果不会被后创建的覆盖
g.ApplyBasic(allModel...)
g.Execute()
}
只生成 Model #
如果只想生成 Model
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gen"
"gorm.io/gen/field"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
const DBDSN = "root:123456@(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
func main() {
cfg := gen.Config{
ModelPkgPath: "./internal/model", // 结构体生成目录
// 表字段可为 null 值时, 对应结体字段使用指针类型
FieldNullable: false,
// 表字段默认值与模型结构体字段零值不一致的字段, 在插入数据时需要赋值该字段值为零值的, 结构体字段须是指针类型才能成功, 即`FieldCoverable:true`配置下生成的结构体字段.
// 因为在插入时遇到字段为零值的会被GORM赋予默认值. 如字段`age`表默认值为10, 即使你显式设置为0最后也会被GORM设为10提交.
// 如果该字段没有上面提到的插入时赋零值的特殊需要, 则字段为非指针类型使用起来会比较方便.
FieldCoverable: false,
// 模型结构体字段的数字类型的符号表示是否与表字段的一致, `false`指示都用有符号类型
FieldSignable: false,
// 生成 gorm 标签的字段索引属性
FieldWithIndexTag: false,
// 生成 gorm 标签的字段类型属性
FieldWithTypeTag: true, // generate with gorm column type tag
}
// 处理表名
cfg.WithTableNameStrategy(func(tableName string) (targetTableName string) {
// 需要忽略的表
//if strings.EqualFold(tableName, "pay_credit") ||
// strings.EqualFold(tableName, "pay_log") ||
// strings.EqualFold(tableName, "space") ||
// strings.EqualFold(tableName, "space_account") {
// return ""
//}
return tableName
})
// 处理 model名
cfg.WithModelNameStrategy(func(tableName string) (targetTableName string) {
s := tableName
//if strings.HasPrefix(tableName, "conf_") {
// s = strings.TrimPrefix(tableName, "conf_")
//}
ns := schema.NamingStrategy{SingularTable: true}
return ns.SchemaName(s)
return tableName
})
// 处理文件名
cfg.WithFileNameStrategy(func(tableName string) (targetTableName string) {
//if strings.HasPrefix(tableName, "conf_") {
// return strings.TrimPrefix(tableName, "conf_")
//}
return tableName
})
g := gen.NewGenerator(cfg)
gormdb, _ := gorm.Open(mysql.Open(DBDSN))
g.UseDB(gormdb) // reuse your gorm db
// 自定义字段的数据类型
dataMap := map[string]func(detailType gorm.ColumnType) (dataType string){
"tinyint": func(detailType gorm.ColumnType) (dataType string) { return "bool" },
"smallint": func(detailType gorm.ColumnType) (dataType string) { return "int" },
"mediumint": func(detailType gorm.ColumnType) (dataType string) { return "int" },
"bigint": func(detailType gorm.ColumnType) (dataType string) { return "int" },
"int": func(detailType gorm.ColumnType) (dataType string) { return "int" },
// 这里将数据库中的`datetime`映射为`config.Time`,方便我们序列化和反序列化
"datetime": func(detailType gorm.ColumnType) (dataType string) { return "config.Time" },
}
// 要先于`ApplyBasic`执行
g.WithDataTypeMap(dataMap)
// fields to their `datatypes.Null[T]` types
gen.WithDataTypesNullType(true)
// 将非默认字段名的字段定义为自动时间戳和软删除字段;
autoUpdateTimeField := gen.FieldGORMTag("update_time", func(tag field.GormTag) field.GormTag {
return tag.Set("autoUpdateTime")
})
autoCreateTimeField := gen.FieldGORMTag("create_time", func(tag field.GormTag) field.GormTag {
return tag.Set("autoCreateTime")
})
// 模型自定义选项组
fieldOpts := []gen.ModelOpt{autoUpdateTimeField, autoCreateTimeField}
// 创建 全部模型文件 , 并覆盖前面创建的同名模型
g.GenerateAllTable(fieldOpts...)
g.Execute()
}