gorm 多对多关系

多对多关系,需要用第三张表存储两张表的关系

连接数据库

main.go

package main

import "gorm-demo/model"

func main() {
    model.InitMysql()
}

model/dbInit.go

package model

import (
    "fmt"
    "log"
    "os"
    "time"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
    "gorm.io/gorm/schema"
)

var Db *gorm.DB

const (
    Username    = "root"
    Password    = "123456"
    Host        = "127.0.0.1"
    Port        = "3306"
    DbName      = "gorm_demo" // 库名
    Config      = "charset=utf8mb4&parseTime=True&loc=Local"
    TablePrefix = "" // 表前缀t_
    // 数据库连接池最大空闲数
    MaxIdleConns = 10
    // 数据库连接池最大打开数
    MaxOpenConns = 100
    // 数据库连接池最大存活时间
    ConnMaxLifetime = time.Hour * 1
)

// 初始化数据库并产生数据库全局变量
func InitMysql() {
    //  默认的级别,会打印find找不到模型时的sql语句。
    // Silent 就不会。
    newLogger := logger.New(
        log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
        logger.Config{
            SlowThreshold:             time.Second, // 慢 SQL 阈值
            LogLevel:                  logger.Info, // Log level 可选 Silent,Error,Warn,Info
            Colorful:                  true,        // 禁用彩色打印 true:不禁用 false:禁用
            IgnoreRecordNotFoundError: true,        // 忽略ErrRecordNotFound(记录未找到)错误
        },
    )
    //"root:123456@tcp(127.0.0.1:3306)/gorm_demo?charset=utf8mb4&parseTime=True&loc=Local",
    dns := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?%s", Username, Password, Host, Port, DbName, Config)
    db, err := gorm.Open(mysql.New(mysql.Config{
        //注意:想要正确的处理 time.Time ,您需要带上 parseTime 参数, (更多参数) 要支持完整的 UTF-8 编码,您需要将 charset=utf8 更改为 charset=utf8mb4 查看 此文章 获取详情
        DSN:                       dns,   // DSN data source name
        DefaultStringSize:         256,   // string 类型字段的默认长度 utf8mb4长应该设置为171,否则如果以字符串作为主键,会报索引超长
        DisableDatetimePrecision:  true,  // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
        DontSupportRenameIndex:    true,  // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
        DontSupportRenameColumn:   true,  // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
        SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置                                                                                      //根据版本自动配置                                                                            // 根据当前 MySQL 版本自动配置
    }), &gorm.Config{
        //CreateBatchSize: 1000,    // 批量插入的条数
        SkipDefaultTransaction:                   false, //为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它。 true:禁用 / false 不禁用
        DisableForeignKeyConstraintWhenMigrating: true,  // 外键约束 在 AutoMigrate 或 CreateTable 时,GORM 会自动创建外键约束,若要禁用该特性,可将其设置为 true
        Logger:                                   newLogger,
        NamingStrategy: schema.NamingStrategy{
            TablePrefix:   TablePrefix, // 表名前缀,`User` 的表名应该是 `t_users`
            SingularTable: true,        // 使用单数表名,启用该选项,此时,`User` 的表名应该是 `t_user`
            NoLowerCase:   false,       // 禁用自动将字段名转换为小写
        },
    })
    if err != nil {
        fmt.Println("gorm.Open err:", err)
        panic("gorm.Open err")
    } else {
        Db = db
        sqlDB, _ := Db.DB()
        // Enable Logger, show detailed log
        sqlDB.SetMaxIdleConns(MaxIdleConns)       //设置空闲连接池中连接的最大数量
        sqlDB.SetMaxOpenConns(MaxOpenConns)       //设置打开数据库连接的最大数量。
        sqlDB.SetConnMaxLifetime(ConnMaxLifetime) //设置了连接可复用的最大时间。
        fmt.Println("")
        fmt.Println("InitMysql success")
        fmt.Println("")

        autoMigrate() //迁移文件
    }
}

// 迁移文件
func autoMigrate() {

    //belongstoDemo01()
    //belongstoDemo02()
    //belongstoDemo03()
    //belongstoDemo04()

    //hasOneDemo01()
    //hasOneDemo02()
    //hasOneDemo03()
    //hasOneDemo04()
    //hasOneDemo05()

    //hasmanyDemo01()
    //hasmanyDemo02()

    manyToManyDome01()
    //manyToManyDome02()
    //manyToManyDome03()
    //    实体关联

    //association_demo01()

    //testDemo01()
}

model/manyToMany.go

package model

import (
    "fmt"
    "gorm.io/gorm"
)

type Info struct {
    gorm.Model
    Money int
    DogID uint
}
type Dog struct {
    gorm.Model
    Name     string
    Info     Info
    GirlGods []GirlGod `gorm:"many2many:dog_girl_god"` //定义中间表dog_girl_god
}
type GirlGod struct {
    gorm.Model
    Name string
    Dogs []Dog `gorm:"many2many:dog_girl_god"` //定义中间表dog_girl_god
}

func manyToManyDome01() {
    Db.AutoMigrate(&Info{}, &Dog{}, &GirlGod{})

    //create01()
    //create02()

    //getGodsDome01()
    //查找关联
    //getGodsDome02()

    //修改关联关系 update Replace Delete
    updateDome01()

    //Delete with Select
    //删除记录的同时,也删除他所关联的数据
    //selectDelete()
}

//  创建狗狗1号,创建女神1,2号,并做关系
//  狗狗1号,女神1号和女神2号都会被创建,并在关联表里创建关联关系
func create01() {
    Info01 := Info{Money: 100}
    girlGod01 := GirlGod{Name: "GirlGod01"}
    girlGod02 := GirlGod{Name: "GirlGod02"}
    //write
    d := Dog{
        Name:     "dog01",
        GirlGods: []GirlGod{girlGod01, girlGod02},
        Info:     Info01,
    }

    Db.Create(&d)
}

// 创建狗狗2号,并关联女神1号和女神2号,

func create02() {
    // 方式一,前端传入女神1号和女神2号的id
    // 如果女神1号和女神2号已经存在,就不在创建了.
    //girlGod1 := GirlGod{
    //    Model: gorm.Model{ID: 1},
    //    Name:  "女神1号",
    //}
    //girlGod2 := GirlGod{
    //    Model: gorm.Model{ID: 2},
    //    Name:  "女神2号",
    //}
    //d02 := Dog{
    //    Name: "dog02",
    //    Info: Info{Money: 20000},
    //    GirlGods: []GirlGod{girlGod1, girlGod2},
    //}
    //Db.Create(&d02)

    //方式二 从表里查出要关联的女神
    var girlGods []GirlGod
    Db.Find(&girlGods, "id in (?)", []int{1, 2})
    d02 := Dog{
        Name:     "dog02",
        Info:     Info{Money: 20000}, //创建info表信息
        GirlGods: girlGods,
    }
    Db.Create(&d02)
}

func getGodsDome01() {
    var dog Dog
    Db.Find(&dog)
    fmt.Println(dog)
}
func getGodsDome02() {
    //查dogs表 预加载GirlGods
    //var dogs []Dog
    //Db.Preload("GirlGods").Find(&dogs)
    //fmt.Println(dogs)

    //查dogs表 预加载GirlGods和dogs的Info
    //var dogs []Dog
    //Db.Preload("Info").Preload("GirlGods").Find(&dogs)
    //fmt.Println(dogs)

    //查GirlGods 预加载Dogs和Dogs.Info
    //var girl []GirlGod
    //Db.Preload("Dogs.Info").Preload("Dogs").Find(&girl)
    //fmt.Println(girl)

    //查Dog表id为1的GirlGods信息(要的结果是girlGod信息,和GirlGods下的Dogs和Dogs.Info)
    //`dog` 是源模型,它的主键不能为空
    var girlGod []GirlGod
    //查Dog表,关联GirlGods表(Association是做关联查询)
    //Db.Model(&Dog{Model:gorm.Model{ID: 1}}).Association("GirlGods").Find(&girlGod)
    //Db.Model(&Dog{Model:gorm.Model{ID: 1}}).Preload("Dogs").Association("GirlGods").Find(&girlGod)
    //Db.Model(&Dog{Model:gorm.Model{ID: 1}}).Preload("Dogs.Info").Association("GirlGods").Find(&girlGod)
    //Db.Model(&Dog{Model:gorm.Model{ID: 1}}).Preload("Dogs.Info").Preload("Dogs").Association("GirlGods").Find(&girlGod)

    // 注意:Preload关联的表查出来的列表数据不能分页,想要分页,只能查关联表,关联表左右join,然后查出来再分页


    //查找带条件的关联
    //Db.Model(&dog).Where("girl_gods.name = GirlGod01").Association("GirlGods").Find(&girlGod)
    Db.Model(&Dog{Model: gorm.Model{ID: 1}}).
        //两行下面的where和Order是相对于girl_god表的操作
        Where("name in ?", []string{"GirlGod01", "GirlGod02"}).
        Order("id desc").
        Preload("Dogs.Info", func(db *gorm.DB) *gorm.DB {
            return db.Where("money > 100") //Info表
        }).
        Preload("Dogs", func(db *gorm.DB) *gorm.DB {
            return db.Order("id DESC") //dogs列表倒序
        }).
        Association("GirlGods").Find(&girlGod)
    fmt.Println(girlGod)

}

// 修改关联关系
func updateDome01() {
    g1 := GirlGod{Model: gorm.Model{ID: 5}}
    g2 := GirlGod{Model: gorm.Model{ID: 3}}
    dog := Dog{Model: gorm.Model{ID: 3}}

    //添加g1和dog3 还有g2和dog3的关系
    //Db.Model(&dog).Association("GirlGods").Append(&g1, &g2)
    //Db.Model(&dog).Association("GirlGods").Append(&g1)
    //Db.Model(&dog).Association("GirlGods").Append(&g2)

    //替换关联 会删除所有的关系,然后再插入关系
    Db.Model(&dog).Association("GirlGods").Replace(&g1, &g2)

    var gs = make([]GirlGod, 0) //空的切片,删除所有关系,不再写入任何关系
    dog1 := Dog{Model: gorm.Model{ID: 3}}
    Db.Model(&dog1).Association("GirlGods").Replace(&gs)

    // 关联计数 Count all GirlGods
    //count:=Db.Model(&dog).Association("GirlGods").Count()
    //fmt.Println(count)

    //  条件计数 Count dog.name in ("GirlGod01", "GirlGod02") all GirlGods
    //count:=Db.Model(&dog).Where("name IN ?", []string{"GirlGod01", "GirlGod02"}).Association("GirlGods").Count()
    //fmt.Println(count)

    //删除g1和dog3的关系
    //Db.Model(&dog).Association("GirlGods").Delete(&g1)

    //删除dog3的所有关系
    //Db.Model(&dog).Association("GirlGods").Clear()
}

//  Delete with Select
//  删除记录的同时,也删除他在关联表里的所有的关联关系
//  你也可以在删除记录时通过 Select 来删除具有 has one、has many、many2many 关系的记录,例如:
//  删除god3,同时也删除god3在关联表里的所有的关联关系
func selectDelete() {
    //dog3 :=Dog{Model:gorm.Model{ID: 3}}

    //Db.Select("GirlGods").Delete(&dog3) //删除dog3的同时,删除和GirlGods的所有关系
    //Db.Select("GirlGods","Info").Delete(&dog3) //删除dog3的同时,删除和GirlGods / Info的所有关系

    // 删除 dog3 时,也删除用户所有 has one/many、many2many 记录
    //Db.Select(clause.Associations).Delete(&dog3)

}

自定义连接表

默认的连接表,只有双方的主键id,展示不了更多信息了
自定义连接表 连接表 可以是一个功能齐全的模型,比如支持 软删除、钩子函数功能,并且可以具有更多字段。您可以通过 SetupJoinTable 设置

package model

import (
    "fmt"
    "gorm.io/gorm"
    "time"
)

//自定义连接表
//默认的连接表,只有双方的主键id,展示不了更多信息了
//自定义连接表 连接表 可以是一个功能齐全的模型,比如支持 软删除、钩子函数功能,并且可以具有更多字段。您可以通过 SetupJoinTable 设置

type Article struct {
    ID    uint
    Title string
    Tags  []Tag `gorm:"many2many:article_tags"`
}

type Tag struct {
    ID   uint
    Name string
}

type ArticleTag struct {
    ArticleID uint `gorm:"primaryKey"`
    TagID     uint `gorm:"primaryKey"`
    CreatedAt time.Time
}

func manyToManyDome03() {
    // 设置Article的Tags表为ArticleTag
    err := Db.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})
    if err != nil {
        panic(err)
    }
    // 如果tag要反向应用Article,那么也得加上
    err = Db.SetupJoinTable(&Tag{}, "Articles", &ArticleTag{})
    if err != nil {
        panic(err)
    }
    Db.AutoMigrate(&Article{}, &Tag{}, &ArticleTag{})

    create3()
    //replace3()
    //getInfo()
}

// BeforeCreate 必须定义好所需的外键,否则会报错
func (p *ArticleTag) BeforeCreate(tx *gorm.DB) (err error) {
    //p.CreatedAt = time.Now() // CreatedAt time.Time 由于我们设置的是CreatedAt,gorm会自动填充当前时间,
    //p.Sort = 50  // 如果是其他的字段,需要使用到ArticleTag 的添加钩子 BeforeCreate 手动添加
    return nil
}

// 创建数据Title和tags 并添加关联数据
func create3() {
    Db.Create(&Article{
        Title: "flask零基础入门",
        Tags: []Tag{
            {Name: "python"},
            {Name: "后端"},
            {Name: "web"},
        },
    })

}

// 添加文章,关联已有标签
func create3_1() {
    Db.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})
    var tags []Tag
    Db.Find(&tags, "name in ?", []string{"python", "web"})
    Db.Create(&Article{
        Title: "flask请求对象",
        Tags:  tags,
    })
}

// 给已有文章关联标签
func create3_2() {
    Db.SetupJoinTable(&Article{}, "Tags", &ArticleTag{})
    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)
}

// 替换已有文章的标签 删除全部关联标签,写入新的标签
func replace3() {
    var article Article
    var tags []Tag
    //Db.Find(&tags, "name in ?", []string{"python1"}) // 查询出python1 如果查不到为空时,会删除全部关系。
    Db.Find(&tags, "name in ?", []string{"python"}) // 查询出python1 如果查不到为空时,会删除全部关系。
    Db.Take(&article, "title = ?", "flask零基础入门")
    Db.Model(&article).Association("Tags").Replace(tags)
}

// 查询文章列表,显示标签
/*
    SetupJoinTable
    添加和更新的时候得用这个
    这样才能走自定义的连接表,以及走它的钩子函数
    查询则不需要这个
*/
func getArticleWithTags() {
    var articles []Article
    Db.Preload("Tags").Find(&articles)
    fmt.Println(articles)
}

示列2

// setupJoinTable 自定义连接表
func setupJoinTable() {
    // 自定义连接表
    //设置Manufacturer的Platforms表为ManufacturerPlatforms
    err := DB.SetupJoinTable(&FaAdmin{}, "FaAuthGroups", &FaAuthGroupAccess{})
    if err != nil {
        logx.Error("db.SetupJoinTable FaAdmin FaAuthGroups table failed", zap.Error(err))
        panic("db.SetupJoinTable FaAdmin FaAuthGroups table failed")

    }
    // 设置Platform的Manufacturers表为ManufacturerPlatforms
    err = DB.SetupJoinTable(&FaAuthGroup{}, "FaAdmins", &FaAuthGroupAccess{})
    if err != nil {
        logx.Error("db.SetupJoinTable FaAuthGroup FaAdmins table failed", zap.Error(err))
        panic("db.SetupJoinTable FaAuthGroup FaAdmins table failed")
    }
}

重写中间表的外建

// FaAuthGroup 分组表
type FaAuthGroup struct {
    ID int64 `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"`
    ControlTime
    Pid    int64  `json:"pid" gorm:"pid"`       // 父组别
    Name   string `json:"name" gorm:"name"`     // 组名
    Rules  string `json:"rules" gorm:"rules"`   // 规则ID
    Status string `json:"status" gorm:"status"` // 状态 hidden 禁用 normal:正常

    //多对多关系  fa_auth_group_access为关联表
    //joinForeignKey为 FaAuthGroup 表的主键id 关联 中间表fa_auth_group_access中的 group_id
    //joinReferences为 faAdmins表的主键 关联 中间表fa_auth_group_access 中的 uid
    FaAdmins []FaAdmin `json:"faAdmins" gorm:"comment:管理员;many2many:fa_auth_group_access;joinForeignKey:group_id;JoinReferences:uid"` // 仓库信息

}

数据表

中间表

package models

// FaAuthGroupAccess 权限分组表
type FaAuthGroupAccess struct {
    Uid          int64         `json:"uid" gorm:"uid"` // 会员ID
    FaAdmins     []FaAdmin     `json:"faAdmins" gorm:"foreignKey:ID;references:uid"`
    GroupId      int64         `json:"group_id" gorm:"group_id"` // 级别ID
    FaAuthGroups []FaAuthGroup `json:"faAuthGroups"   gorm:"foreignKey:ID;references:group_id"`
}

// TableName 表名称
func (*FaAuthGroupAccess) TableName() string {
    return "fa_auth_group_access"
}

func (t *FaAuthGroupAccess) Create(list []FaAuthGroupAccess) (err error) {
    err = DB.Create(&list).Error
    return err
}

CREATE TABLE `fa_auth_group_access` (
  `uid` int(10) unsigned NOT NULL COMMENT '会员ID',
  `group_id` int(10) unsigned NOT NULL COMMENT '级别ID',
  UNIQUE KEY `uid_group_id` (`uid`,`group_id`) USING BTREE,
  KEY `uid` (`uid`) USING BTREE,
  KEY `group_id` (`group_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='权限分组表';

分组表

package models

// FaAuthGroup 分组表
type FaAuthGroup struct {
    ID int64 `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"`
    ControlTime
    Pid    int64  `json:"pid" gorm:"pid"`       // 父组别
    Name   string `json:"name" gorm:"name"`     // 组名
    Rules  string `json:"rules" gorm:"rules"`   // 规则ID
    Status string `json:"status" gorm:"status"` // 状态 hidden 禁用 normal:正常

    //多对多关系  fa_auth_group_access为关联表
    //joinForeignKey为 FaAuthGroup 表的主键id 关联 中间表fa_auth_group_access中的 group_id
    //joinReferences为 faAdmins表的主键 关联 中间表fa_auth_group_access 中的 uid
    FaAdmins []FaAdmin `json:"faAdmins" gorm:"comment:管理员;many2many:fa_auth_group_access;joinForeignKey:group_id;JoinReferences:uid"` // 仓库信息

}

// TableName 表名称
func (*FaAuthGroup) TableName() string {
    return "fa_auth_group"
}
CREATE TABLE `fa_auth_group` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `pid` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '父组别',
  `name` varchar(100) NOT NULL DEFAULT '' COMMENT '组名',
  `rules` text NOT NULL COMMENT '规则ID',
  `createtime` int(10) DEFAULT NULL COMMENT '创建时间',
  `updatetime` int(10) DEFAULT NULL COMMENT '更新时间',
  `status` varchar(30) NOT NULL DEFAULT '' COMMENT '状态',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='分组表';

package models

import (
    "errors"
    "ytss_go_zero/app/core/cmd/rpc/pb"
    "ytss_go_zero/common/utils"
)

// FaAdmin 管理员表
type FaAdmin struct {
    ID int64 `json:"id" gorm:"primaryKey;autoIncrement;comment:主键编码"`
    ControlTime
    Username     string `json:"username" gorm:"username"`            // 用户名
    Nickname     string `json:"nickname" gorm:"nickname"`            // 昵称
    Password     string `json:"password" gorm:"password"`            // 密码
    Salt         string `json:"salt" gorm:"salt"`                    // 密码盐
    Department   string `json:"department" gorm:"column:department"` // 部门
    Avatar       string `json:"avatar" gorm:"avatar"`                // 头像
    Email        string `json:"email" gorm:"email"`                  // 电子邮箱
    Loginfailure int8   `json:"loginfailure" gorm:"loginfailure"`    // 失败次数
    Logintime    int64  `json:"logintime" gorm:"logintime"`          // 登录时间
    Loginip      string `json:"loginip" gorm:"loginip"`              // 登录IP
    Token        string `json:"token" gorm:"token"`                  // Session标识
    Status       string `json:"status" gorm:"status"`                // 状态 hidden 禁用 normal:正常

    //多对多关系  fa_auth_group_access为关联表
    //joinForeignKey为 FaAdmin表的主键id 关联 中间表fa_auth_group_access中的uid
    //joinReferences为 FaAuthGroup 关联 中间表fa_auth_group_access 中的 group_id
    FaAuthGroups []FaAuthGroup `json:"faAuthGroups" gorm:"comment:归属角色;many2many:fa_auth_group_access;joinForeignKey:uid;JoinReferences:group_id;"` // 仓库信息
}

// TableName 表名称
func (*FaAdmin) TableName() string {
    return "fa_admin"
}
CREATE TABLE `fa_admin` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `username` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名',
  `nickname` varchar(50) NOT NULL DEFAULT '' COMMENT '昵称',
  `password` varchar(32) NOT NULL DEFAULT '' COMMENT '密码',
  `salt` varchar(30) NOT NULL DEFAULT '' COMMENT '密码盐',
  `department` varchar(50) DEFAULT NULL COMMENT '部门',
  `avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
  `email` varchar(100) NOT NULL DEFAULT '' COMMENT '电子邮箱',
  `loginfailure` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '失败次数',
  `logintime` int(10) DEFAULT NULL COMMENT '登录时间',
  `loginip` varchar(50) DEFAULT NULL COMMENT '登录IP',
  `createtime` int(10) DEFAULT NULL COMMENT '创建时间',
  `updatetime` int(10) DEFAULT NULL COMMENT '更新时间',
  `token` varchar(59) NOT NULL DEFAULT '' COMMENT 'Session标识',
  `status` varchar(30) NOT NULL DEFAULT 'normal' COMMENT '状态',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE KEY `username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=37 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='管理员表';
作者:海马  创建时间:2024-07-09 09:11
最后编辑:海马  更新时间:2024-12-22 19:32