一对多关系
在gorm中,官方文档是把一对多关系分为了两类,
Belongs To 属于谁
Has Many 我拥有的
以用户和文章为例
一个用户可以发布多篇文章,一篇文章属于一个用户
type User struct {
ID uint `gorm:"size:4"`
Name string `gorm:"size:8"`
Articles []Article // 用户拥有的文章列表
}
type Article struct {
ID uint `gorm:"size:4"`
Title string `gorm:"size:16"`
UserID uint // 属于 这里的类型要和引用的外键类型一致,包括大小
User User // 属于
}
关于外键命名,外键名称就是关联表名+ID
,类型是uint
重写外键关联
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
// 用户表
type User struct {
ID uint `gorm:"size:4"`
Name string `gorm:"size:8"`
Articles []Article // 用户拥有的文章列表
}
// 文章表 一篇文章属于一个用户,一个用户能有多篇文章
type Article struct {
ID uint `gorm:"size:4"`
Title string `gorm:"size:16"`
UserID uint // 属于 这里的类型要和引用的外键类型一致,包括大小 外键名称就是`关联表名+ID`
User User // 属于
}
//type User struct {
// ID uint `gorm:"size:4"`
// Name string `gorm:"size:8"`
// Articles []Article `gorm:"foreignKey:UID"` // 用户拥有的文章列表
//}
//
//type Article struct {
// ID uint `gorm:"size:4"`
// Title string `gorm:"size:16"`
// UID uint // 属于 改了Article 的外键,将UID作为了外键,那么User这个外键关系就要指向UID 与此同时,User所拥有的Articles也得更改外键,改为UID
// User User `gorm:"foreignKey:UID"` // 属于
//}
var DB *gorm.DB
var mysqlLogger logger.Interface
func init() {
username := "root1" //账号
password := "rootroot" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DBname := "GoORM" //数据库名
timeout := "10s" //连接超时,10秒
mysqlLogger = logger.Default.LogMode(logger.Info)
// root:root@tcp(127.0.0.1:3306)/gorm?
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, DBname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
//跳过默认事故,它默认是false,在初始化时禁用它,这样可以获得60%的性能提升
//SkipDefaultTransaction: true,
//如果不想自动以蛇形复数那些默认形式生成表,就写在这里面
NamingStrategy: schema.NamingStrategy{
SingularTable: false, // 是否单数表名
NoLowerCase: true, // 是否关闭小写转换
},
})
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
DB = db
// 连接成功
}
func main() {
DB.AutoMigrate(&User{}, &Article{})
}
一对多添加
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
//// 用户表
//type User struct {
// ID uint `gorm:"size:4"`
// Name string `gorm:"size:8"`
// Articles []Article // 用户拥有的文章列表
//}
//
//// 文章表 一篇文章属于一个用户,一个用户能有多篇文章
//type Article struct {
// ID uint `gorm:"size:4"`
// Title string `gorm:"size:16"`
// UserID uint // 属于 这里的类型要和引用的外键类型一致,包括大小 外键名称就是`关联表名+ID`
// User User // 属于
//}
//重写外键关联
//type User struct {
// ID uint `gorm:"size:4"`
// Name string `gorm:"size:8"`
// Articles []Article `gorm:"foreignKey:UID"` // 用户拥有的文章列表
//}
//
//type Article struct {
// ID uint `gorm:"size:4"`
// Title string `gorm:"size:16"`
// UID uint // 属于 改了Article 的外键,将UID作为了外键,那么User这个外键关系就要指向UID 与此同时,User所拥有的Articles也得更改外键,改为UID
// User User `gorm:"foreignKey:UID"` // 属于
//}
// 重写外键引用 (但这样不适合实战应用)
type User struct {
ID uint `gorm:"size:4"`
Name string `gorm:"size:8"`
Articles []Article `gorm:"foreignKey:UserName;references:Name"` // 用户拥有的文章列表 外键是UserName,主键是Name
}
type Article struct {
ID uint `gorm:"size:4"`
Title string `gorm:"size:16"`
UserName string
User User `gorm:"references:Name"` // 属于
}
var DB *gorm.DB
var mysqlLogger logger.Interface
func init() {
username := "root1" //账号
password := "rootroot" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DBname := "GoORM" //数据库名
timeout := "10s" //连接超时,10秒
mysqlLogger = logger.Default.LogMode(logger.Info)
// root:root@tcp(127.0.0.1:3306)/gorm?
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, DBname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
//跳过默认事故,它默认是false,在初始化时禁用它,这样可以获得60%的性能提升
//SkipDefaultTransaction: true,
//如果不想自动以蛇形复数那些默认形式生成表,就写在这里面
NamingStrategy: schema.NamingStrategy{
SingularTable: false, // 是否单数表名
NoLowerCase: true, // 是否关闭小写转换
},
})
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
DB = db
// 连接成功
}
func main() {
//日志显示
DB = DB.Session(&gorm.Session{
Logger: mysqlLogger,
})
//DB.AutoMigrate(&User{}, &Article{})
//创建用户,带上文章
//DB.Create(&User{
// Name: "RoLingG",
// Articles: []Article{
// {
// Title: "Python",
// },
// {
// Title: "Golang",
// },
// },
//})
//创建文章,关联已有用户
//DB.Create(&Article{
// Title: "C++狗屎",
// 一般来说主键是ID,我这主键是Name,就当关联ID进行关联操作就好了
// UserName: "RoLingG",
//})
//DB.Create(&Article{
// Title: "Golang天下第一",
// User: User{
// Name: "Clouwer",
// },
//})
//以现有对象进行添加操作
//var user User
//DB.Take(&user, 2)
//DB.Create(&Article{
// Title: "Spring也挺好",
// User: user,
//})
}
外键添加
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
// 重写外键引用 (但这样不适合实战应用)
type User struct {
ID uint `gorm:"size:4"`
Name string `gorm:"size:8"`
Articles []Article `gorm:"foreignKey:UserName;references:Name"` // 用户拥有的文章列表 外键是UserName,主键是Name
}
type Article struct {
ID uint `gorm:"size:4"`
Title string `gorm:"size:16"`
UserName string
User User `gorm:"references:Name"` // 属于
}
var DB *gorm.DB
var mysqlLogger logger.Interface
func init() {
username := "root1" //账号
password := "rootroot" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DBname := "GoORM" //数据库名
timeout := "10s" //连接超时,10秒
mysqlLogger = logger.Default.LogMode(logger.Info)
// root:root@tcp(127.0.0.1:3306)/gorm?
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, DBname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
//跳过默认事故,它默认是false,在初始化时禁用它,这样可以获得60%的性能提升
//SkipDefaultTransaction: true,
//如果不想自动以蛇形复数那些默认形式生成表,就写在这里面
NamingStrategy: schema.NamingStrategy{
SingularTable: false, // 是否单数表名
NoLowerCase: true, // 是否关闭小写转换
},
})
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
DB = db
// 连接成功
}
func main() {
//日志显示
DB = DB.Session(&gorm.Session{
Logger: mysqlLogger,
})
//给已有用户绑定文章
//方法一:Save方式
//将原本为Clouwer的文章变成了RoLingG的
//var user User
//DB.Take(&user, 1)
//
//var article Article
//DB.Take(&article, 5)
//
//user.Articles = []Article{article}
//DB.Save(&user)
//方法二:Append方式
//var user User
//DB.Take(&user, 2)
//var article Article
//DB.Take(&article, 5)
//Articles是指type User里面的
//DB.Model(&user).Association("Articles").Append(&article)
//给文章绑定用户
//同上:方法一
//var article Article
//DB.Take(&article, 5)
//article.UserName = "克劳德"
//DB.Save(&article)
//方法二:
var user User
DB.Take(&user, 1)
var article Article
DB.Take(&article, 5)
//User是指type Articles里面的
DB.Model(&article).Association("User").Append(&user)
}
查询
查询用户,显示用户的文章列表
所以这里就要用到预加载
预加载
var user User
DB.Preload("Articles").Take(&user, 1)
fmt.Println(user)
预加载的名字就是外键关联的属性名
查询文章,显示文章用户的信息
同样的,使用预加载
var article Article
DB.Preload("User").Take(&article, 1)
fmt.Println(article)
嵌套预加载
查询文章,显示用户,并且显示用户关联的所有文章,这就得用到嵌套预加载了
//嵌套预加载:即查用户又查用户的文章列表
var article Article
DB.Preload("User.Articles").Take(&article, 1)
//甚至可以套娃
//DB.Preload("User.Articles.User.Articles.User.Articles.User.Articles").Take(&article, 1)
fmt.Println(article)
带条件的预加载
//带条件的预加载
var user User
DB.Preload("Articles", "id = ?", 1).Take(&user, 1)
fmt.Println(user)
自定义预加载
//自定义预加载
var user User
DB.Preload("Articles", func(db *gorm.DB) *gorm.DB {
return db.Where("id > ?", 2)
}).Take(&user)
fmt.Println(user)
删除
清除外键关系
删除用户,与将与用户关联的文章,外键设置为null
//删除
var user User
//为了Articles预加载
DB.Preload("Articles").Take(&user, 2)
//fmt.Println(user.Articles)
//删除文章上的所属用户
//DB.Model(&user).Association("Articles").Delete(user.Articles)
//删除对应用户
DB.Delete(&user)
级联删除
删除用户,与用户关联的文章也会删除,外键不是设置为null
//级联删除
//连人带所属文章一起删完
var user User
DB.Take(&user, 2)
DB.Select("Articles").Delete(&user)
一对一关系
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
)
type User struct {
ID uint
Name string
Age int
Gender bool
UserInfo UserInfo // 通过UserInfo可以拿到用户详情信息
}
//type UserInfo struct {
// UserID uint // 外键
// ID uint
// Addr string
// Like string
//}
type UserInfo struct {
User *User //记得加指针,放置嵌套
UserID uint // 外键
ID uint
Addr string
Like string
}
var DB *gorm.DB
var mysqlLogger logger.Interface
func init() {
username := "root1" //账号
password := "rootroot" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DBname := "GoORM" //数据库名
timeout := "10s" //连接超时,10秒
mysqlLogger = logger.Default.LogMode(logger.Info)
// root:root@tcp(127.0.0.1:3306)/gorm?
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, DBname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
//跳过默认事故,它默认是false,在初始化时禁用它,这样可以获得60%的性能提升
//SkipDefaultTransaction: true,
//如果不想自动以蛇形复数那些默认形式生成表,就写在这里面
NamingStrategy: schema.NamingStrategy{
SingularTable: false, // 是否单数表名
NoLowerCase: true, // 是否关闭小写转换
},
})
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
DB = db
// 连接成功
}
func main() {
//日志显示
DB = DB.Session(&gorm.Session{
Logger: mysqlLogger,
})
////创建表
//DB.AutoMigrate(&User{}, &UserInfo{})
//
////创建数据
//DB.Create(&User{
// Name: "RoLingG",
// Age: 21,
// Gender: true,
// UserInfo: UserInfo{
// Addr: "广东",
// Like: "写代码",
// },
//})
//DB.Create(&User{
// Name: "Clouwer",
// Age: 23,
// Gender: true,
// UserInfo: UserInfo{
// Addr: "广东",
// Like: "玩游戏",
// },
//})
//根据用户查用户详情
//var user User
//DB.Preload("UserInfo").Take(&user)
//fmt.Println(&user)
////根据用户详情查用户
////要给UserInfo加上User *User
//var userinfo UserInfo
////能查到了,但是是以进制码的形式,给User的UserInfo 加上指针*就能看了
//DB.Preload("User").Take(&userinfo)
//fmt.Println(userinfo)
////硬要不加也可以看,直接查某个属性即可,但这样UserInfo的内容就看不到了
//fmt.Println(userinfo.User)
////删除 和一对多一样
//var user User
////能删,但只能删一个user表中的数据
//DB.Select("UserInfo").Delete(&user, 3)
////DB.Preload("UserInfo").Take(&user)
}
多对多关系
多对多的时候要在表属性那里添加tag:many2many
例如:
type Tag struct {
ID uint
Name string
Articles []Article `gorm:"many2many:article_tags;"` // 用于反向引用
}
type Article struct {
ID uint
Title string
Tags []Tag `gorm:"many2many:article_tags;"` //many2many对应第三个表的名字,也就会用第三个表去存储前两个表之间的关系
}
添加
//添加文章,并创建标签
//DB.Create(&Article{
// Title: "GOLANG对对对",
// Tags: []Tag{
// { Name: "GOLANG"},
// { Name: "后端"},
// },
//})
//使用上面已有tag给予新建文章
var tags []Tag
//take也可以,但是take貌似只能最多拿一个数据,有点忘了
DB.Find(&tags, "name = ?", "后端")
DB.Create(&Article{
Title: "Python",
Tags: tags,
})
查询
//查询文章,显示标签列表
var article Article
DB.Preload("Tags").Take(&article)
fmt.Println(&article)
//查询标签,显示文章列表
var tag Tag
DB.Preload("Articles").Take(&tag, 2)
fmt.Println(tag)
更新
//更新
//方法一:删除原有的tags,再添加新的
//移除文章的标签
var article Article
DB.Preload("Tags").Take(&article, 1) //找文章
DB.Model(&article).Association("Tags").Delete(article.Tags) //关联Tags,删除文章对应Tags
fmt.Println(article) //先没tag
//添加新的tag
var tag Tag
DB.Take(&tag, 1)
DB.Model(&article).Association("Tags").Append(tag)
fmt.Println(article) //再有新的tag
//方法二:Gorm原生方法
var article Article
var tags []Tag
DB.Find(&tags, []int{2, 6, 7})
DB.Preload("Tags").Take(&article, 2) //找到文章2的tag
DB.Model(&article).Association("Tags").Replace(tags) //用replace进行替换
fmt.Println(article)
自定义连接表(重要)
包括了添加,更改
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"gorm.io/gorm/schema"
"time"
)
type Article struct {
ID uint
Title string
Tags []Tag `gorm:"many2many:article_tags"`
}
type Tag struct {
ID uint
Name string
//Article []Article `gorm:"many2many:article_tags"` //反向引用,但对自定义链接表有影响
}
type ArticleTag struct {
ArticleID uint `gorm:"primaryKey"`
TagID uint `gorm:"primaryKey"`
CreatedAt time.Time
}
var DB *gorm.DB
var mysqlLogger logger.Interface
func init() {
username := "root1" //账号
password := "rootroot" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DBname := "GoORM" //数据库名
timeout := "10s" //连接超时,10秒
mysqlLogger = logger.Default.LogMode(logger.Info)
// root:root@tcp(127.0.0.1:3306)/gorm?
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, DBname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
//跳过默认事故,它默认是false,在初始化时禁用它,这样可以获得60%的性能提升
//SkipDefaultTransaction: true,
//如果不想自动以蛇形复数那些默认形式生成表,就写在这里面
NamingStrategy: schema.NamingStrategy{
SingularTable: false, // 是否单数表名
NoLowerCase: true, // 是否关闭小写转换
},
})
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
DB = db
// 连接成功
}
func main() {
//日志显示
DB = DB.Session(&gorm.Session{Logger: mysqlLogger})
//自定义设置链接表
DB.SetupJoinTable(&Article{}, "Tags", &ArticleTag{}) //不能注释掉,要一直放开
// 如果tag要反向应用Article,那么也得加上(但是这种情况实战少用)
//DB.SetupJoinTable(&Tag{}, "Articles", &ArticleTag{})
//err := DB.AutoMigrate(&Article{}, &Tag{}, &ArticleTag{})
//fmt.Println(err)
//添加外键关系,直接操作第三张表,效率最高
//DB.Create(&ArticleTag{
// ArticleID: 3,
// TagID: 3,
// CreatedAt: time.Time{},
//})
//之前的方式
//var article Article
//DB.Preload("Tags").Take(&article, 2)
//fmt.Println(article)
//添加文章并添加标签,并自动关联
//DB.SetupJoinTable(&Article{}, "Tags", &ArticleTag{}) // 要设置这个,才能走到我们自定义的连接表
//DB.Create(&Article{
// Title: "flask零基础入门",
// Tags: []Tag{
// {Name: "前端"},
// {Name: "后端"},
// {Name: "web"},
// },
//})
// CreatedAt time.Time 由于我们设置的是CreatedAt,gorm会自动填充当前时间,
// 如果是其他的字段,需要使用到ArticleTag 的添加钩子 BeforeCreate
//添加文章,关联已有tag
//var tags []Tag
//DB.Find(&tags, []int{4, 5})
//DB.Create(&Article{
// Title: "普通基础",
// Tags: tags,
//})
//给以有文章添加tag
//以防没有文章先创建文章
//article := Article{
// Title: "django基础",
//}
//DB.Create(&article)
////开始添加
//var at Article
//var tags []Tag
//DB.Find(&tags, "name in ?", []string{"python", "web"})
//DB.Take(&at, article.ID).Association("Tags").Append(tags)
//给以有文章替换标签
var articleToReplace Article
DB.Preload("Tags").Take(&articleToReplace, "title = ?", "django基础")
//DB.Preload("Tags").Take(&articleToReplace, 7) //多种展示
var tags []Tag
DB.Find(&tags, []int{1, 7})
//DB.Find(&tags, "name in ?", []string{"python", "web"}) //多种展示
DB.Model(&articleToReplace).Association("Tags").Replace(tags)
////查询文章列表,显示标签
//var articles []Article
//DB.Preload("Tags").Find(&articles)
//fmt.Println(articles)
}
自定义连接表主键
这个功能还是很有用的,例如你的文章表 可能叫ArticleModel
,你的标签表可能叫TagModel
那么按照gorm默认的主键名,那就分别是ArticleModelID
,TagModelID
,太长了,根本就不实用
主要是要修改这两项
joinForeignKey
:连接的主键id
joinReferences
:关联的主键id
例子:(这次没有手动改表结构,db,err := gorm.Open(mysql.Open(dsn))
用的是gorm默认结构)
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"time"
)
type ArticleModel struct {
ID uint
Title string
Tags []TagModel `gorm:"many2many:article_tags;joinForeignKey:ArticleID;JoinReferences:TagID"` //joinForeignKey:ArticleID对应的是ArticleModel的主键ID,也就是ArticleTagModel里的ArticleID
}
type TagModel struct {
ID uint
Name string
Articles []ArticleModel `gorm:"many2many:article_tags;joinForeignKey:TagID;JoinReferences:ArticleID"`
}
type ArticleTagModel struct {
ArticleID uint `gorm:"primaryKey"` // article_id,如果不加JoinReferences:ArticleID,那么这个本身就是ArticleModelID
TagID uint `gorm:"primaryKey"` // tag_id 同理
CreatedAt time.Time
}
var DB *gorm.DB
var mysqlLogger logger.Interface
func init() {
username := "root1" //账号
password := "rootroot" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DBname := "GoORM" //数据库名
timeout := "10s" //连接超时,10秒
mysqlLogger = logger.Default.LogMode(logger.Info)
// root:root@tcp(127.0.0.1:3306)/gorm?
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, DBname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
DB = db
// 连接成功
}
func main() {
//日志显示
DB = DB.Session(&gorm.Session{Logger: mysqlLogger})
DB.SetupJoinTable(&ArticleModel{}, "Tags", &ArticleTagModel{})
DB.SetupJoinTable(&TagModel{}, "Articles", &ArticleTagModel{})
//err := DB.AutoMigrate(&ArticleModel{}, &TagModel{}, &ArticleTagModel{})
//fmt.Println(err)
//添加内容
DB.Create(&ArticleModel{
Title: "flask零基础入门",
Tags: []TagModel{
{Name: "前端"},
{Name: "后端"},
{Name: "web"},
},
})
}
操作多对多连接表
查询
如果通过一张表去操作连接表,这样会比较麻烦
比如查询某篇文章关联了哪些标签
或者是举个更通用的例子,用户和文章,某个用户在什么时候收藏了哪篇文章
无论是通过用户关联文章,还是文章关联用户都不太好查
最简单的就是直接查连接表
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"time"
)
type UserModel struct {
ID uint
Name string
ACollects []ArticleModel `gorm:"many2many:user_collect_models;joinForeignKey:UserID;JoinReferences:ArticleID"`
}
type ArticleModel struct {
ID uint
Title string
//UCollects []UserModel `gorm:"many2many:user_collect_models;joinForeignKey:ArticleID;JoinReferences:UserID"`
// 这里也可以反向引用,根据文章查哪些用户收藏了
}
// UserCollectModel 用户收藏文章表
type UserCollectModel struct {
UserID uint `gorm:"primaryKey"` // article_id
ArticleID uint `gorm:"primaryKey"` // tag_id
CreatedAt time.Time
}
var DB *gorm.DB
var mysqlLogger logger.Interface
func init() {
username := "root1" //账号
password := "rootroot" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DBname := "GoORM" //数据库名
timeout := "10s" //连接超时,10秒
mysqlLogger = logger.Default.LogMode(logger.Info)
// root:root@tcp(127.0.0.1:3306)/gorm?
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, DBname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
DB = db
// 连接成功
}
func main() {
//日志显示
DB = DB.Session(&gorm.Session{Logger: mysqlLogger})
DB.SetupJoinTable(&UserModel{}, "ACollects", &UserCollectModel{})
err := DB.AutoMigrate(&UserModel{}, &ArticleModel{}, &UserCollectModel{})
fmt.Println(err)
}
常用的操作就是根据用户查收藏的文章列表
var user UserModel
DB.Preload("ACollects").Take(&user, "name = ?", "RoLingG")
fmt.Println(user)
但是这样不太好做分页,并且也拿不到收藏文章的时间
var collects []UserCollectModel
DB.Find(&collects, "user_id = ?", 2)
fmt.Println(collects)
//结果:[{2 2 2023-11-14 15:38:01 +0800 CST}]
这样虽然可以查到用户id,文章id,收藏的时间,但是搜索只能根据用户id搜,返回也拿不到用户名,文章标题等
我们需要改一下表结构,不需要重新迁移,加一些字段
type UserModel struct {
ID uint
Name string
Collects []ArticleModel `gorm:"many2many:user_collect_models;joinForeignKey:UserID;JoinReferences:ArticleID"`
}
type ArticleModel struct {
ID uint
Title string
}
// UserCollectModel 用户收藏文章表
type UserCollectModel struct {
UserID uint `gorm:"primaryKey"` // article_id
UserModel UserModel `gorm:"foreignKey:UserID"`
ArticleID uint `gorm:"primaryKey"` // tag_id
ArticleModel ArticleModel `gorm:"foreignKey:ArticleID"`
CreatedAt time.Time
}
func main() {
//日志显示
DB = DB.Session(&gorm.Session{Logger: mysqlLogger})
DB.SetupJoinTable(&UserModel{}, "ACollects", &UserCollectModel{})
var collects []UserCollectModel
var user UserModel
DB.Take(&user, "name = ?", "RoLingG")
// 这里用map的原因是如果没查到,那就会查0值,如果是struct,则会忽略零值,全部查询
DB.Debug().Preload("UserModel").Preload("ArticleModel").Where(map[string]any{"user_id": user.ID}).Find(&collects)
for _, collect := range collects {
fmt.Println(collect)
}
}
//结果:
//{1 {1 RoLingG []} 1 {1 Golang基础} 2023-11-14 15:49:10 +0800 CST}
//{1 {1 RoLingG []} 2 {2 Python基础} 2023-11-14 15:49:18 +0800 CST}
自定义数据类型
很多情况下我们存储到数据库中的数据是多变的
例如我需要存储json或者是数组
然后很多数据库并不能直接存储这些数据类型,我们就需要自定义数据类型
自定义的数据类型必须实现 Scanner
和 Valuer
接口(因为算高级数据类型 ),以便让 GORM 知道如何将该类型接收、保存到数据库
gorm中自定义数据类型无外乎就两个方法
在数据入库的时候要转换为什么数据,已经出库的时候数据变成什么样子
存储json
存储json可能是经常使用到的
我们需要定义一个结构体,在入库的时候,把它转换为[]byte类型,查询的时候把它转换为结构体
package main
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type Info struct {
Status string `json:"status"`
Addr string `json:"addr"`
Age int `json:"age"`
}
// Scan 从数据库中读取出来 从这往下基本是通用写法,不通用的就是结构体
func (i *Info) Scan(value interface{}) error {
bytes, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
}
err := json.Unmarshal(bytes, i)
return err
}
// Value 存入数据库
func (i Info) Value() (driver.Value, error) {
return json.Marshal(i)
}
type AuthModel struct {
ID uint
Name string
Info Info `gorm:"type:string"`
}
var DB *gorm.DB
var mysqlLogger logger.Interface
func init() {
username := "root1" //账号
password := "rootroot" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DBname := "GoORM" //数据库名
timeout := "10s" //连接超时,10秒
mysqlLogger = logger.Default.LogMode(logger.Info)
// root:root@tcp(127.0.0.1:3306)/gorm?
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, DBname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
DB = db
// 连接成功
}
func main() {
DB.AutoMigrate(&AuthModel{})
}
插入数据
//添加信息
DB.Create(&AuthModel{
Name: "RoLingG",
Info: Info{
Status: "success",
Addr: "广东惠州",
Age: 21,
},
})
查询信息
//查询信息
var auth AuthModel
DB.Take(&auth, "name = ?", "RoLingG")
fmt.Println(auth)
存储数组
很多时候存储数组也是很常见的
最简单的方式就是存json
(偷懒了,在上面的代码基础上做了点变化,就介样了)
package main
import (
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type Array []string
type HostModel struct {
ID uint `json:"id"`
IP string `json:"ip"`
Ports Array `gorm:"type:string" json:"ports"`
}
// Scan 从数据库中读取出来
func (arr *Array) Scan(value interface{}) error {
bytes, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
}
err := json.Unmarshal(bytes, arr)
return err
}
// Value 存入数据库
func (arr Array) Value() (driver.Value, error) {
return json.Marshal(arr)
}
var DB *gorm.DB
var mysqlLogger logger.Interface
func init() {
username := "root1" //账号
password := "rootroot" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DBname := "GoORM" //数据库名
timeout := "10s" //连接超时,10秒
mysqlLogger = logger.Default.LogMode(logger.Info)
// root:root@tcp(127.0.0.1:3306)/gorm?
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s", username, password, host, port, DBname, timeout)
//连接MYSQL, 获得DB类型实例,用于后面的数据库读写操作。
db, err := gorm.Open(mysql.Open(dsn))
if err != nil {
panic("连接数据库失败, error=" + err.Error())
}
DB = db
// 连接成功
}
func main() {
//日志显示
DB = DB.Session(&gorm.Session{Logger: mysqlLogger})
//创建数据库
//DB.AutoMigrate(&HostModel{})
//添加信息
DB.Create(&HostModel{
IP: "192.168.1.1",
Ports: []string{"80", "8080"},
})
////查询信息
var host HostModel
DB.Take(&host, 1)
fmt.Println(host)
}
当然,也可以用字符串拼接,例如 |
, =
, ,
, ;
(,和=分隔要注意,url里面也有这些字符,可能会有问题)
type Array []string
// Scan 从数据库中读取出来
func (arr *Array) Scan(value interface{}) error {
data, ok := value.([]byte)
if !ok {
return errors.New(fmt.Sprintf("解析失败: %v %T", value, value))
}
*arr = strings.Split(string(data), "|")
return nil
}
// Value 存入数据库
func (arr Array) Value() (driver.Value, error) {
return strings.Join(arr, "|"), nil
}
注意:拼接的字符串不能是输入字符串中存在的
评论(0)