首页 > 其他分享 >gorm入门

gorm入门

时间:2024-02-21 16:44:27浏览次数:20  
标签:name fmt db gorm result id 入门

目录

1.简介

特定:
	全功能 ORM
	关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
	Create,Save,Update,Delete,Find 中钩子方法
	支持 Preload、Joins 的预加载
	事务,嵌套事务,Save Point,Rollback To Saved Point
	Context、预编译模式、DryRun 模式
	批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
	SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
	复合主键,索引,约束
	数据迁移 实例化数据表数据
	自定义 Logger
	灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus
	支持MySQL, PostgreSQL, SQLite, SQL Server 和 TiDB


2. 实例

# 包下载
go get -u gorm.io/gorm

# 按需下载驱动
go get -u gorm.io/driver/sqlite   # sqlite
go get -u gorm.io/driver/mysql  # mysql
go get -u gorm.io/driver/postgres  # postgresql
package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

type Product struct {
	gorm.Model // GORM 定义一个 gorm.Model 结构体,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt
	Code       string
	Price      uint
}

func main() {
	dsn := "username:password@tcp(host:port)/database?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
		},
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	db.AutoMigrate(&Product{})

	// 新增
	tx := db.Create(&Product{Code: "D42", Price: 100})
	fmt.Printf("受影响行数=%v\n", tx.RowsAffected)

	// 查询
	var product Product
	tx = db.First(&product, 1).Scan(&product) // 根据整型主键查找
	fmt.Printf("受影响行数=%v;结果:%v\n", tx.RowsAffected, product)

	// 更新 - 将 product 的 price 更新为 200
	tx = db.Model(&product).Update("Price", 200)
	fmt.Printf("受影响行数=%v\n", tx.RowsAffected)
	//// 更新 - 更新多个字段
	//db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
	//db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})

    // 删除 - 删除 product(软删除-存在DeletedAt字段 每次删除只会更新DeletedAt时间 不过常规方式查询不会输出已标记删除的数据)
	tx = db.Delete(&product, 1)
	fmt.Printf("受影响行数=%v\n", tx.RowsAffected)
}




3.增删改查

新增

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/schema"
	"time"
)

type MyUser struct {
	ID         uint64    `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
	Name       string    `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
	Age        uint8     `gorm:"column:age;not null;default:0;comment:年龄"`
	CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
	UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
}

/**
CREATE TABLE `my_user` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/

// BeforeCreate钩子
func (u *MyUser) BeforeCreate(tx *gorm.DB) (err error) {
	fmt.Printf("BeforeCreate...")
	return
}

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
		NowFunc: func() time.Time {
			tmp := time.Now().Local().Format("2006-01-02 15:04:05")
			now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
			return now
		}, // 字段存在时间的精确到秒
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	err = db.AutoMigrate(&MyUser{})
	if err != nil {
		fmt.Println("数据表迁移异常:" + err.Error())
		return
	}
	database := db.Migrator().CurrentDatabase()
	hasTableFlag := db.Migrator().HasTable(&MyUser{})
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)

	// 新增-单个
	u0 := MyUser{
		Name:       "王一",
		Age:        10,
		CreateTime: time.Now(),
		UpdateTime: time.Now(),
	}
	result := db.Create(&u0) // 需要传递指针对象
	fmt.Printf("插入数据的主键:%v;插入的数据量:%v;异常:%v\n", u0.ID, result.RowsAffected, result.Error)

	// 新增-特定字段
	u1 := MyUser{
		Name: "李二",
		Age:  11,
	}
	result = db.Select("Name", "Age").Create(&u1)
	fmt.Printf("插入数据的主键:%v;插入的数据量:%v;异常:%v\n", u1.ID, result.RowsAffected, result.Error)

	// 新增-动态指定字段(Omit声明的字段无论结构体的字段是否已赋值均忽略)
	u2 := MyUser{
		Name: "张三",
	}
	result = db.Omit("Age", "CreateTime", "UpdateTime").Create(&u2)
	fmt.Printf("插入数据的主键:%v;插入的数据量:%v;异常:%v\n", u2.ID, result.RowsAffected, result.Error)
	// 新增-批量-Create
	us := []MyUser{
		{Name: "p1"},
		{Name: "p2"},
		{Name: "p3"},
	}
	result = db.Create(&us)
	ids := [3]uint64{}
	for i, u := range us {
		ids[i] = u.ID
	}
	fmt.Printf("批量插入数据的主键:%v;插入的数据量:%v;异常:%v\n", ids, result.RowsAffected, result.Error)

	// 新增-批量-CreateInBatches
	us2 := []MyUser{
		{Name: "pp1"},
		{Name: "pp2"},
		{Name: "pp3"},
	}
	result = db.CreateInBatches(us2, len(us2))
	ids = [3]uint64{}
	for i, u := range us2 {
		ids[i] = u.ID
	}
	fmt.Printf("批量插入数据的主键:%v;插入的数据量:%v;异常:%v\n", ids, result.RowsAffected, result.Error)

	// 新增-单个或批量-map数据形式
	result = db.Model(&MyUser{}).Create(map[string]interface{}{
		"Name": "m1", "Age": 12,
	}) // 单个
	fmt.Printf("map-插入的数据量:%v;异常:%v\n", result.RowsAffected, result.Error)
	result = db.Model(&MyUser{}).Create([]map[string]interface{}{
		{"Name": "m2", "Age": 12},
		{"Name": "m3", "Age": 12},
	}) // 多个
	fmt.Printf("map-插入的数据量:%v;异常:%v\n", result.RowsAffected, result.Error)

	/**
	支持在创建链接时配置批量处理的数据量且支持关联表的创建
	db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
	  CreateBatchSize: 1000,
	})
	db := db.Session(&gorm.Session{CreateBatchSize: 1000})
	users = [5000]User{{Name: "jinzhu", Pets: []Pet{pet1, pet2, pet3}}...}
	db.Create(&users)
	// INSERT INTO users xxx (5 batches)
	// INSERT INTO pets xxx (15 batches)
	*/

	// Insert ... ON DUPLICATE KEY UPDATE 有则更新、没有则新增(默认根据主键id或唯一键)
	/**
	type OnConflict struct {
		Columns      []Column  // 相关字段参与判断 是新增还是更新
		Where        Where
		TargetWhere  Where
		OnConstraint string
		DoNothing    bool
		DoUpdates    Set   // 特定字段参与更新clause.AssignmentColumns([]string{"name", "age"})
		UpdateAll    bool  // 所有字段参与更新
	}
	*/
	us3 := []MyUser{
		{Name: "m1_", Age: 10, ID: 10},
		{Name: "m2_", Age: 11, ID: 11},
		{Name: "m4", Age: 13},
	}
	result = db.Clauses(clause.OnConflict{
		Columns: []clause.Column{
			{Name: "id"},
		},
		DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
	}).Create(&us3)
	fmt.Printf("clause.OnConflict-受影响的数据量:%v;异常:%v\n", result.RowsAffected, result.Error)

	// hook钩子
	/**
	BeforeSave, BeforeCreate, AfterSave, AfterCreate
	可在事务操作前后过程中对数据预处理
	*/
	gu := MyUser{
		Name: "g1",
	}
	result = db.Session(&gorm.Session{SkipHooks: false}).Create(&gu)
	fmt.Printf("hook-新增主键id:%v;受影响行数:%v;异常:%v\n", gu.ID, result.RowsAffected, result.Error)

	gus := []MyUser{
		{Name: "g2"},
		{Name: "g3"},
	}
	result = db.Session(&gorm.Session{SkipHooks: true}).CreateInBatches(gus, len(gus)) // SkipHooks跳过hooks方法
	fmt.Printf("hook-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

}


更新

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"time"
)

type myUser struct {
	ID         uint64    `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
	Name       string    `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
	Age        uint8     `gorm:"column:age;not null;default:0;comment:年龄"`
	CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
	UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
}

/**
CREATE TABLE `my_user` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
		NowFunc: func() time.Time {
			tmp := time.Now().Local().Format("2006-01-02 15:04:05")
			now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
			return now
		}, // 字段存在时间的精确到秒
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	err = db.AutoMigrate(&myUser{})
	if err != nil {
		fmt.Println("数据表迁移异常:" + err.Error())
		return
	}
	database := db.Migrator().CurrentDatabase()
	hasTableFlag := db.Migrator().HasTable(&myUser{})
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)

	// 修改-默认查询条件以ID主键
	//1.修改-Save(尽量避免使用)
	//save方式只有在传递ID主键且ID主键数据存在才是更新数据
	//save不传ID则为新增数据、传递了ID但不存在依旧为新增数据
	result := db.Save(&myUser{Name: "小红", Age: 23, CreateTime: time.Now(), UpdateTime: time.Now(), ID: 20})
	fmt.Printf("save-修改影响的行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	// 2.1修改-Update单列
	u0 := myUser{ID: 100}
	result = db.Model(&u0).Update("name", "小红2") // 针对结构体对象明确的 默认以主键id为查询条件
	fmt.Printf("update单列-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
	result = db.Model(&myUser{}).Where("id=?", 20).Update("name", "小红-21") // 针对结构体对象不明确的 需要携带Where查询条件
	fmt.Printf("update单列-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	// 2.2 修改-Update多列
	u1 := myUser{ID: 21}
	result = db.Model(&u1).Updates(myUser{Name: "小红-22", Age: 22})
	fmt.Printf("update多列(struct)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
	result = db.Model(&u1).Updates(map[string]interface{}{
		"name": "小红22",
		"age":  22,
	})
	fmt.Printf("update多列(map)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)
	result = db.Model(&myUser{}).Where("id=?", 21).Updates(myUser{Age: 23})
	fmt.Printf("update多列-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	// 2.3 修改-动态字段数量Select&Omit
	// Select指定参与更新的字段、Omit指定不参与更新的字段、2者可搭配一起使用
	result = db.Model(&myUser{}).Select("name").Where("id=?", 22).Updates(map[string]interface{}{
		"name": "小红22",
		"age":  25,
	}) // 等价于单列 只更新name即select指定的字段
	fmt.Printf("update-select(单列)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	u2 := myUser{
		ID: 22,
	}
	result = db.Model(&u2).Omit("age", "create_time", "update_time").Updates(map[string]interface{}{
		"name": "小红_22",
		"age":  26,
	}) // omit指定的字段不参与更新
	fmt.Printf("update-omit(单列)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	result = db.Model(&u2).Select("name", "age").Updates(myUser{Name: "小红-22", Age: 22}) // 等价于多列参与更新
	fmt.Printf("update-select(多列)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	result = db.Model(&u2).Select("*").Updates(myUser{
		Name: "小红-222", Age: 222, CreateTime: time.Now(), UpdateTime: time.Now(), ID: u2.ID,
	}) // 所有字段参与更新 字段非空需要赋值 ID也需要赋值(后续可以过滤处理)
	fmt.Printf("update-select(所有列)-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	result = db.Model(&u2).Select("*").Omit("update_time", "create_time", "id").Updates(
		myUser{Name: "小红-22", Age: 22})
	fmt.Printf("update-select&omit-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	// 2.4 修改-批量更新
	result = db.Model(myUser{}).Where("id in ?", []int{19, 20, 21, 22}).Updates(myUser{
		Name: "xh",
		Age:  18,
	})
	fmt.Printf("update-批量Model-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	result = db.Table("my_user").Where("id in ?", []int{19, 20, 21, 22}).Updates(map[string]interface{}{
		"name": "xh1",
		"age":  19,
	})
	fmt.Printf("update-批量Table-修改受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	// 获取修改后的数据
	var u myUser
	result = db.Model(myUser{}).Where("id=?", 10).Updates(myUser{Name: "m1", Age: 11}).Scan(&u)
	fmt.Printf("%v-%v-%v\n", result.RowsAffected, result.Error, u)

	// 其它高级用法
	/**
	// 1. 嵌套sql表达式
	// product's ID is `3`
	db.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
	// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;

	db.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
	// UPDATE "products" SET "price" = price * 2 + 100, "updated_at" = '2013-11-17 21:34:10' WHERE "id" = 3;

	db.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
	// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3;

	db.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
	// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = 3 AND quantity > 1;

	// 2. 子查询
	db.Model(&user).Update("company_name", db.Model(&Company{}).Select("name").Where("companies.id = users.company_id"))
	// UPDATE "users" SET "company_name" = (SELECT name FROM companies WHERE companies.id = users.company_id);

	db.Table("users as u").Where("name = ?", "jinzhu").Update("company_name", db.Table("companies as c").Select("name").Where("c.id = u.company_id"))
	db.Table("users as u").Where("name = ?", "jinzhu").Updates(map[string]interface{}{"company_name": db.Table("companies as c").Select("name").Where("c.id = u.company_id")})

	// 3. 全局更新相关
	db.Model(&User{}).Update("name", "jinzhu").Error // gorm.ErrMissingWhereClause

	db.Model(&User{}).Where("1 = 1").Update("name", "jinzhu")
	// UPDATE users SET `name` = "jinzhu" WHERE 1=1

	db.Exec("UPDATE users SET name = ?", "jinzhu")
	// UPDATE users SET name = "jinzhu"

	db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
	// UPDATE users SET `name` = "jinzhu"

	// 4. hook钩子函数-BeforeSave, BeforeUpdate, AfterSave, AfterUpdate
	通过实现对应的结构体的方法 可对在修改前后的数据做预处理
	func (u *User) BeforeUpdate(tx *gorm.DB) (err error) {return}
	*/
}



删除

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/schema"
	"time"
)

//type myUser struct {
//	ID         uint64    `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
//	Name       string    `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
//	Age        uint8     `gorm:"column:age;not null;default:0;comment:年龄"`
//	CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
//	UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
//}

/**
CREATE TABLE `my_user` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
		NowFunc: func() time.Time {
			tmp := time.Now().Local().Format("2006-01-02 15:04:05")
			now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
			return now
		}, // 字段存在时间的精确到秒
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	err = db.AutoMigrate(&myUser{})
	if err != nil {
		fmt.Println("数据表迁移异常:" + err.Error())
		return
	}
	database := db.Migrator().CurrentDatabase()
	hasTableFlag := db.Migrator().HasTable(&myUser{})
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)

	// 1.1 删除-单条记录
	result := db.Delete(&myUser{}, 0)
	fmt.Printf("删除指定对象(指定主键)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	// 1.2 删除-指定主键
	result = db.Delete(&myUser{}, 1) // delete from xx where id=10;
	fmt.Printf("删除(指定主键)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	result = db.Delete(&myUser{}, []int{1, 2, 3}) // delete from xx where id in (1,2,3);
	fmt.Printf("删除(指定多个主键)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	// 1.3 删除-批量(根据条件)
	result = db.Where("name=?", "p1").Delete(&myUser{}) // name='p1'
	fmt.Printf("删除(批量)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	result = db.Delete(&myUser{}, "name=?", "p1") // 等价于上面的db.Where("name=?", "p1").Delete(&myUser{})
	fmt.Printf("删除(批量)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	us := []myUser{
		{ID: 4},
		{ID: 5},
		{ID: 6},
	}
	result = db.Delete(&us) // 等价于 db.Delete(&us, "id in ?", []int{4,5,6})
	fmt.Printf("删除(批量-指定对象)-受影响行数:%v;异常:%v\n", result.RowsAffected, result.Error)

	// 1.4 返回删除行的数据(回显被删除的数据)-需要数据库支持回写功能-待验证
	var us2 []myUser
	result = db.Clauses(clause.Returning{}).Where("name=?", "xh1").Delete(&us2)
	fmt.Printf("删除(回显删除数据)-受影响行数:%v;删除数据:%v;异常:%v\n", result.RowsAffected, us2, result.Error)
	// 回显指定列名
	//result = db.Clauses(clause.Returning{Columns: []clause.Column{{Name: "name"}, {Name: "age"}}}).Where("name=?", "xh1").Delete(&us2)

	var uu myUser
	result = db.Clauses(clause.Returning{}).Where("name=?", "pp2").Delete(&uu)
	fmt.Printf("删除(回显删除数据)-受影响行数:%v;删除数据:%v;异常:%v\n", result.RowsAffected, uu, result.Error)
	// 删除(回显删除数据)-受影响行数:1;删除数据:{0  0 0001-01-01 00:00:00 +0000 UTC 0001-01-01 00:00:00 +0000 UTC};异常:<nil>

	/**
	1. 阻止全局删除
	当执行不带任何条件的批量删除时,GORM将不会运行并返回ErrMissingWhereClause 错误
	如果一定要这么做,必须添加一些条件Where,或者使用原生SQL,或者开启AllowGlobalUpdate 模式,如下例:
	db.Delete(&User{}).Error // gorm.ErrMissingWhereClause
	db.Delete(&[]User{{Name: "jinzhu1"}, {Name: "jinzhu2"}}).Error // gorm.ErrMissingWhereClause
	db.Where("1 = 1").Delete(&User{}) // DELETE FROM `users` WHERE 1=1
	db.Exec("DELETE FROM users") // DELETE FROM users
	db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{}) // DELETE FROM users


	//2. 软删除(结构体字段包含 gorm.DeletedAt 存在该字段则调用删除不会进行物理删除 而是更新该字段的时间)
	// 2.1 根据id主键删除 user's ID is `111`
	db.Delete(&user) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;

	// 2.2 批量删除
	db.Where("age = ?", 20).Delete(&User{}) // UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;

	// 2.3.1 软删除-数据查询
	db.Where("age = 20").Find(&user)  // 软删除的记录将被忽略 这种方式不会有输出 SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
	db.Unscoped().Where("age = 20").Find(&users) // 这种方式有输出 SELECT * FROM users WHERE age = 20;

	// 2.3.2 软删除-永久删除形式
	db.Unscoped().Delete(&order) // DELETE FROM orders WHERE id=10;

	// 3. 删除操作相关的钩子函数-BeforeDelete、AfterDelete 当调用Delete触发该钩子函数
		func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
	    if u.Role == "admin" {
	        return errors.New("admin user not allowed to delete")
	    }
	    return
	}
	*/
}


查询

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"time"
)

//type myUser struct {
//	ID         uint64    `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
//	Name       string    `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
//	Age        uint8     `gorm:"column:age;not null;default:0;comment:年龄"`
//	CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
//	UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
//}

/**
CREATE TABLE `my_user` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
		NowFunc: func() time.Time {
			tmp := time.Now().Local().Format("2006-01-02 15:04:05")
			now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
			return now
		}, // 字段存在时间的精确到秒
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	err = db.AutoMigrate(&myUser{})
	if err != nil {
		fmt.Println("数据表迁移异常:" + err.Error())
		return
	}
	database := db.Migrator().CurrentDatabase()
	hasTableFlag := db.Migrator().HasTable(&myUser{})
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)

	// 1. 查询-单个对象(First|Take|Last)
	// First|Take|Last 查询未有记录会抛出异常 建议使用db.Find()形式
	// 相关Model没有定义主键 会按照Model(myUser)的第一个字段排序
	var u0 myUser
	result := db.First(&u0) // select * from my_user order by id limit 1; 需要保证Model有ID主键字段
	fmt.Printf("查询-单个对象First-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u0, result.Error)

	var u1 myUser
	result = db.Take(&u1) // select * from my_user limit 1;
	fmt.Printf("查询-单个对象Take-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u1, result.Error)

	var u2 myUser         // 需要指定不同的变量(*)
	result = db.Last(&u2) // select * from my_user order by id desc limit 1;
	fmt.Printf("查询-单个对象Last-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u2, result.Error)

	u3 := map[string]interface{}{}
	result = db.Model(&myUser{}).First(&u3)
	fmt.Printf("查询-单个对象Map-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u3, result.Error)

	var u4 myUser
	//result = db.Find(&u4, 9) // select * from my_user where id=9  <=>db.Find(&u4, "id=?", 9)
	result = db.Find(&u4, "id=?", 9)
	fmt.Printf("查询-单个对象Find-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u4, result.Error)

	// 2. 查询-获取所有对象
	var us []myUser
	result = db.Find(&us)
	fmt.Printf("查询-多个对象Find-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, len(us), result.Error)

	// 3.1 条件查询-string
	// var user = User{ID: 10} (❌) 不建议使用这种方式
	//db.Where("id = ?", 20).First(&user) // SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1

	var u5 myUser
	result = db.Where("name=?", "g3").Find(&u5)
	//result = db.Where("name=? and id > ?", "g3", 1).First(&u5)  // 可多个条件 参考sql条件查询
	fmt.Printf("查询-Where-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u5, result.Error)
	// 3.2 条件查询-struct&map
	var u6 myUser
	/**
	1.下面的结构体方式查询 若字段值为0|''|false|或者其它标识0值的都不会参与查询 可使用map方式携带0值字段 或者指定结构体查询的字段
		db.Where(&myUser{Name: "g3"}).Find(&u6)
	2. 通过这种方式指定结构体查询的字段
		db.Where(&myUser{Name:"g3"}, "name", "Age").Find(&u6) <=>select * from my_user where name='g3' and age=0;
		db.Where(&myUser{Name:"g3"}, "Age").Find(&u6) <=>select * from my_user where age=0;
	*/
	result = db.Where(&myUser{Name: "g3"}).Find(&u6)
	fmt.Printf("查询-Where(struct)-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u6, result.Error)

	var u7 myUser
	result = db.Where(map[string]interface{}{"name": "g3"}).First(&u7)
	fmt.Printf("查询-Where(map)-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u7, result.Error)

	// 3.3 条件查询-内联条件
	var u8 myUser
	/**
	db.Find(&u8, myUser{Name: "g3"}) <=> db.Find(&u8, map[string]interface{}{"name": "g3"}) <=> db.Find(&u8, "name=?", "g3")
	*/
	result = db.Find(&u8, myUser{Name: "g3"}) // select * from my_user where name='g3';
	fmt.Printf("查询-Find内联条件-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u8, result.Error)

	// 3.4 条件查询-not、or
	var uu myUser
	/**
	db.Not("name=?", "g3").First(&uu)
	<=>
	db.Not(map[string]interface{}{"name": "g3"}).First(&uu)
	<=>
	db.Not(myUser{Name: "g3"}).First(&uu)
	ps:
	db.Not(myUser{Name: "g3", Age: 10}).First(&uu)=>select * from my_user where name != g3 and age !=10;
	*/
	result = db.Not("name=?", "g3").First(&uu)
	fmt.Printf("查询-Not-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, uu, result.Error)

	var uus []myUser
	/**
	db.Where("name=?", "g3").Or("name=?", "g2").Find(&uus)
	<=>
	db.Where("name=?", "g3").Or(myUser{Name: "g2"}).Find(&uus)
	<=>
	db.Where("name='g3'").Or(map[string]interface{}{"name":"g2"}).Find(&uus)
	*/
	result = db.Where("name=?", "g3").Or("name=?", "g2").Find(&uus)
	fmt.Printf("查询-OR-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, uus, result.Error)

	// 3.5 查询-输出特定字段Select
	var u9 myUser
	/**
	只有指定的特定字段有实际的值 其它字段均为0标识值int=0 bool=false time.time=0001-01-01 00:00:00 +0000 UTC
	db.Select("name", "age").Find(&u9, myUser{Name: "g3"})<=>db.Select([]string{"name", "age"}).Find(&u9, myUser{Name: "g3"})
	*/
	result = db.Select("name", "age").Find(&u9, myUser{Name: "g3"})
	//result = db.Select([]string{"name", "age"}).Find(&u9, myUser{Name: "g3"})
	fmt.Printf("查询-Select-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, u9, result.Error)

	// 3.6 查询-排序Order
	var up myUser
	result = db.Order("age desc, name").Find(&up).Limit(1) // <=> db.Order("age desc").Order("name").Find(&up).Limit(1)
	fmt.Printf("查询-Order-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, up, result.Error)

	// 3.7 查询-分页Limit&Offset
	// offset:偏移量 limit: 输出量
	var up1 myUser
	result = db.Limit(1).Offset(1).Find(&up1)
	fmt.Printf("查询-Limit&Offset-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, up1, result.Error)

	// 3.8 查询-分组group by + having
	var ug1 myUser
	// select name, sum(age) as total from my_user group by name having name='g3';
	result = db.Model(&myUser{}).Select("name, sum(age) as total").Group("name").Having("name=?", "g3").Find(&ug1).Limit(1)
	fmt.Printf("查询-Modle-groupby&having-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, ug1, result.Error)

	var ug2 myUser
	result = db.Table("my_user").Select("name, sum(age) as total").Group("name").Having("name=?", "g3").Limit(1).Scan(&ug2)
	fmt.Printf("查询-Table-groupby&having-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, ug2, result.Error)

	// 3.9 查询-原生sql
	var su myUser
	/**
	db.Table("my_user").Select("name", "age").Where("name=?", "g3").Scan(&su)
	<=>
	db.Raw("select name, age from my_user where name=?", "g3").Scan(&su)
	*/
	result = db.Raw("select name, age from my_user where name=?", "g3").Scan(&su)
	fmt.Printf("查询-Raw-影响行数:%v;对象:%v;异常:%v\n", result.RowsAffected, su, result.Error)

	/**
	1. 关联查询JOIN
	type result struct {
	  Name  string
	  Email string
	}
	// SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id
	db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{})
	<=>
	rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
	for rows.Next() {
	  ...
	}
	<=>
	db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)

	2. 多表关联查询
	db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "[email protected]").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "1").Find(&user)

	3. 左连接
	// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
	db.Joins("Company").Find(&users)  // type Company struct{}

	4. 内连接
	// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` INNER JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id`;
	db.InnerJoins("Company").Find(&users)  // type Company struct{}

	5. 连接查询附带条件
	// SELECT `users`.`id`,`users`.`name`,`users`.`age`,`Company`.`id` AS `Company__id`,`Company`.`name` AS `Company__name` FROM `users` LEFT JOIN `companies` AS `Company` ON `users`.`company_id` = `Company`.`id` AND `Company`.`alive` = true;
	db.Joins("Company", db.Where(&Company{Alive: true})).Find(&users)

	*/
}


高级查询

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
	"gorm.io/gorm/schema"
	"time"
)

//type myUser struct {
//	ID         uint64    `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
//	Name       string    `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
//	Age        uint8     `gorm:"column:age;not null;default:0;comment:年龄"`
//	CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
//	UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
//}

/**
CREATE TABLE `my_user` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
		NowFunc: func() time.Time {
			tmp := time.Now().Local().Format("2006-01-02 15:04:05")
			now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
			return now
		}, // 字段存在时间的精确到秒
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	err = db.AutoMigrate(&myUser{})
	if err != nil {
		fmt.Println("数据表迁移异常:" + err.Error())
		return
	}
	database := db.Migrator().CurrentDatabase()
	hasTableFlag := db.Migrator().HasTable(&myUser{})
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)

	// 1. 查询-智能选择字段(通过定义结构体控制查询字段)
	/**
	&gorm.Config{
		QueryFields: true, // gorm配置 实际会对所有字段进行select
	}
	var user myUser
	db.Session(&gorm.Session{QueryFields: true}).Find(&user)
	<=>
	db.Find(&user)
	*/
	type ApiUser struct {
		ID   uint
		Name string
	}
	var u myUser
	result := db.Model(&myUser{}).Limit(1).Find(&ApiUser{}).Scan(&u)
	fmt.Printf("查询-结构体控制字段输出-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, u, result.Error)

	// 2. 查询-锁
	/**
	gorm支持多种类型锁
	s锁:共享锁=>加锁的记录 允许其他事务加s锁 不允许加x锁  =>select ... lock in share mode
	x锁:排他锁=>加锁的记录 不允许其他事务加s锁或x锁  =>select ... from update
	is锁:意向共享锁=>事务在请求s锁前 需要先获取is锁
	ix锁:意向排他锁=>事务在请求x锁前 需要先获取ix锁
	兼容性:
		x和所有锁冲突
		ix兼容ix、is
		s兼容s、is
		is兼容is、ix、s
	lock in share: 给表加is 给行加s 多个事务允许同时持有一行的读锁 其他事务可以读取 不可修改 需要等待持有锁的事务提交后才能进行写入操作
	for update:  给表加ix 给行加x  事务锁定行|表后 会阻塞其他事务对行|表的读写操作
	*/
	var u0 myUser
	result = db.Clauses(clause.Locking{
		Strength: "update",                                // update or share
		Table:    clause.Table{Name: clause.CurrentTable}, // 数据表
		Options:  "",                                      // 可选参数
	}).Where("id=?", 18).Find(&u0)
	fmt.Printf("查询-for update-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, u0, result.Error)

	var u1 myUser
	result = db.Clauses(clause.Locking{
		Strength: "share",
		Table:    clause.Table{Name: clause.CurrentTable}}).Where("id=?", 18).Find(&u1)
	fmt.Printf("查询-for share-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, u1, result.Error)

	// 3. 查询-子查询
	/**
	子查询其他形式
	// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");
	db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)

	// SELECT AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
	subQuery := db.Select("AVG(age)").Where("name LIKE ?", "name%").Table("users")
	db.Select("AVG(age) as avgage").Group("name").Having("AVG(age) > (?)", subQuery).Find(&results)

	// SELECT * FROM (SELECT `name`,`age` FROM `users`) as u WHERE `age` = 18
	db.Table("(?) as u", db.Model(&User{}).Select("name", "age")).Where("age = ?", 18).Find(&User{})

	// SELECT * FROM (SELECT `name` FROM `users`) as u, (SELECT `name` FROM `pets`) as p
	subQuery1 := db.Model(&User{}).Select("name")
	subQuery2 := db.Model(&Pet{}).Select("name")
	db.Table("(?) as u, (?) as p", subQuery1, subQuery2).Find(&User{})

	*/
	var u2 myUser
	// select * from (select name,age from my_user) as u where id=1;
	// select 确定了 where可选的查询字段  where用其它字段会出现异常
	result = db.Table("(?) as u", db.Model(&myUser{}).Select("id", "name")).Where("id=?", 18).Find(&u2)
	fmt.Printf("查询-子查询-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, u2, result.Error)

	// 4. 分组查询-条件
	/**
	// SELECT * FROM `pizzas` WHERE (pizza = "pepperoni" AND (size = "small" OR size = "medium")) OR (pizza = "hawaiian" AND size = "xlarge")
	db.Where(
	    db.Where("pizza = ?", "pepperoni").Where(db.Where("size = ?", "small").Or("size = ?", "medium")),
	).Or(
	    db.Where("pizza = ?", "hawaiian").Where("size = ?", "xlarge"),
	).Find(&Pizza{}).Statement
	*/

	// 5. 带多个列的in查询
	/**
	// SELECT * FROM users WHERE (name, age, role) IN (("jinzhu", 18, "admin"), ("jinzhu 2", 19, "user"));
	db.Where("(name, age, role) IN ?", [][]interface{}{{"jinzhu", 18, "admin"}, {"jinzhu2", 19, "user"}}).Find(&users)
	*/

	// 6. 查询-命名参数
	// gorm 支持sql.NamedArg和map[string]interface{}{}形式的命令参数
	var u3 myUser
	//result = db.Where("name=@n1", sql.Named("n1", "王一")).Find(&u3)
	// <=>等价于下面的语句
	result = db.Where("name=@n1", map[string]interface{}{"n1": "g3"}).Find(&u3)
	fmt.Printf("查询-命令参数-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, u3, result.Error)

	// 7. 查询结果-结构化(map)
	r := map[string]interface{}{}
	result = db.Model(&myUser{}).Find(&r)
	fmt.Printf("查询-map结果结构化-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, r, result.Error)
	rs := []map[string]interface{}{}
	result = db.Model(&myUser{}).Limit(2).Find(&rs) // <=>db.Table("my_user").Limit(2).Find(&rs)
	fmt.Printf("查询-map结果结构化-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, rs, result.Error)

	// 8. 查询-返回结果的对象(struct|map)初始化 FirstOrInit
	// 获取第一条匹配的记录 或者根据给定的条件初始化一个实例(仅支持 struct 和 map 条件)
	// 使用Attrs对struct或map初始化属性 Attrs内部的字段不参与sql查询 查询到的对象会替换Attrs内部字段的值
	// 使用Assign对struct或map初始化属性 Assign内部的字段不参与sql查询 无论是否查询到对象 Assign都会把字段值作为初始化值
	var fu myUser
	result = db.FirstOrInit(&fu, myUser{Name: "haha"}) // 未查询到数据则初始化一个实例 name=haha的myUser结构体
	fmt.Printf("查询-firstOrInit-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, fu, result.Error)
	var fu2 myUser
	result = db.Where(myUser{Name: "王一"}).FirstOrInit(&fu2) // 找到数据则返回并初始化myUser
	//<=>
	//result = db.FirstOrInit(&fu2, map[string]interface{}{"name": "王一"})
	fmt.Printf("查询-firstOrInit-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, fu2, result.Error)

	var au myUser
	result = db.FirstOrInit(&au, myUser{Name: "g3"}).Attrs("age", 20) // 查询到对象则Attrs-age字段值以对象值为准
	//<=>等价于
	//result = db.Where(myUser{Name: "g3"}).Attrs(myUser{Age: 20}).FirstOrInit(&au)
	fmt.Printf("查询-Attrs-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, au, result.Error)

	var au2 myUser
	result = db.Where(myUser{Name: "g3"}).Assign(myUser{Age: 20}).FirstOrInit(&au2) // 无论是否查询到对象 Assign内部的字段的值都会赋给au2
	fmt.Printf("查询-Assign-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, au2, result.Error)

	// 9. 查询-FirstOrCreate(对返回对象-map|struct 初始化赋值)
	// 获取匹配的第一条记录或者根据给定条件创建一条新纪录(仅 struct, map 条件有效) RowsAffected 返回创建、更新的记录数
	// 若未找到记录 则会新建一条记录
	// Attrs 和 Assign 修饰的字段不会参与sql查询 Attrs修饰的字段值只在未找到结果时生效  Assign修饰的字段值无论是否查询到都生效
	var cu myUser
	result = db.FirstOrCreate(&cu, myUser{Name: "g4"}) // 未找到则会新增 否则返回记录结果并赋值给cu
	//<=>
	//result = db.Where(myUser{Name: "g4"}).FirstOrCreate(&cu)
	fmt.Printf("查询-FirstOrCreate-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, cu, result.Error)

	// 10. hints使用
	// 优化器、索引提示 注解提示
	// go get -u gorm.io/hints
	/**
	var hus []myUser
	result = db.Clauses(hints.New("max_execution_time(10)")).Find(&hus)
	result = db.Clauses(hints.UseIndex("create_time")).Find(&hus) // create_time需要为索引名 可多个
	result = db.Clauses(hints.ForceIndex("create_time", "update_time").ForJoin()).Find(hus)
	fmt.Printf("查询-hints-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, hus, result.Error)
	*/

	//11. 查询-迭代返回查询对象(查询结果处理)
	rows, _ := db.Model(&myUser{}).Where("age=?", 0).Rows()
	defer rows.Close()
	for rows.Next() {
		var u myUser
		db.ScanRows(rows, &u)
		fmt.Printf("rows查询的对象:%v\n", u)
	}

	// 12. 查询-批量处理(batchSize=?个)查询结果
	var myBatch []myUser
	result = db.Where("age=?", 0).FindInBatches(&myBatch, 2, func(tx *gorm.DB, batch int) error {
		for _, r := range myBatch {
			// 批量处理
			fmt.Printf("FindInBatches-对象:%v\n", r)
		}
		//tx.Save(&myBatch)  // 批量数据入库修改|新增或其它操作
		//affected := tx.RowsAffected // 输出受影响的记录行数
		fmt.Printf("myBatch=%v\n", myBatch)
		return nil
	})

	// 13. 查询钩子函数 AfterFind
	/**
	func (u *myUser) AfterFind(tx *gorm.DB)(err error){return}
	*/

	// 14. 查询-输出单列-pluck
	// 超过单列字段的输出需要使用Select搭配Scan或Find
	//db.Select("name", "age").Scan(&p)
	//db.Select("name", "age").Find(&p)
	var p myUser
	result = db.Model(&myUser{}).Where("id=?", 18).Pluck("name", &p)
	fmt.Printf("查询-pluck-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, p, result.Error)

	// 15. 查询-Scopes 通过定义常用查询方法 再使用Scopes引用该方法 该方法可以多个
	var myF myUser
	result = db.Scopes(func(db *gorm.DB) *gorm.DB {
		return db.Where("id=?", 18)
	}).Find(&myF)
	fmt.Printf("查询-Scopes-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, myF, result.Error)

	// 16. 查询-统计计数
	var count int64
	result = db.Model(&myUser{}).Where("id > ?", 1).Or("age > ?", 0).Count(&count)
	fmt.Printf("查询-Count-受影响行数:%v;数据:%v;异常:%v\n", result.RowsAffected, count, result.Error)

}


原生sql

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"time"
)

//type myUser struct {
//	ID         uint64    `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
//	Name       string    `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
//	Age        uint8     `gorm:"column:age;not null;default:0;comment:年龄"`
//	CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
//	UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
//}

/**
CREATE TABLE `my_user` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
  `age` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '年龄',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
		NowFunc: func() time.Time {
			tmp := time.Now().Local().Format("2006-01-02 15:04:05")
			now, _ := time.ParseInLocation("2006-01-02 15:04:05", tmp, time.Local)
			return now
		}, // 字段存在时间的精确到秒
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	err = db.AutoMigrate(&myUser{})
	if err != nil {
		fmt.Println("数据表迁移异常:" + err.Error())
		return
	}
	database := db.Migrator().CurrentDatabase()
	hasTableFlag := db.Migrator().HasTable(&myUser{})
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;hasTable:%v;tables:%v\n", database, hasTableFlag, tables)

	// 1. gorm支持使用原生sql
	// Raw方式执行原生sql-可返回对象Scan
	// Exec方式执行原生sql gorm.Expr扩展sql表达式 (select语句不生效) 无返回对象
	var u myUser
	tx := db.Raw("select id, name, age from my_user where id=?", 18).Scan(&u)
	fmt.Printf("原生sql-Raw-受影响行数:%v;结果:%v;异常:%v\n", tx.RowsAffected, u, tx.Error)

	tx = db.Exec("update my_user set age=? where id=?", gorm.Expr("age * ? + ?", 1, 1), 18)
	fmt.Printf("原生sql-Exec-受影响行数:%v;异常:%v\n", tx.RowsAffected, tx.Error)

	// 2.1 调试模式-DryRun
	// DryRun模式-生成sql但不执行
	// 可获取sql语句和对应的参数 会根据不同的数据库驱动生成不同的sql语句
	stmt := db.Session(&gorm.Session{DryRun: true}).First(&myUser{}, 1).Statement
	fmt.Printf("DryRun-sql语句:%v;参数:%v\n", stmt.SQL.String(), stmt.Vars)

	// 2.2 调试模式-ToSQL
	// ToSQL模式-返回生成的sql但不执行
	sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
		return tx.Where("id=?", 1).First(&myUser{})
	})
	fmt.Printf("ToSQL-sql语句:%v\n", sql)

	// 3. 获取结果
	// 3.1 获取*sql.Row结果
	var name string
	var age uint
	//row := db.Table("my_user").Where("name=?", "g2").Select("name, age").Row()
	// <=>等价于
	row := db.Raw("select name,age from my_user where name=?", "g2").Row()
	row.Scan(&name, &age)
	fmt.Printf("Row-name=%v;age=%v;model=%v\n", name, age)

	// 3.2 获取*sql.Rows结果
	//rows, err := db.Model(&myUser{}).Where("id between ? and ?", 17, 18).Select("name", "age").Rows()
	// <=>等价于
	rows, err := db.Raw("select name, age from my_user where id between ? and ?", 17, 18).Rows()
	defer rows.Close()
	var us myUser
	for rows.Next() {
		rows.Scan(&name, &age) // 获取特定字段值
		db.ScanRows(rows, &us) // 初始化赋值到model
		fmt.Printf("Rows-name=%v;age=%v;model=%v\n", name, age, us)
	}

	// 3.3 连接(非事务)
	// 在一条tcp DB连接中运行多条sql
	db.Connection(func(tx *gorm.DB) error {
		var d1 myUser
		tx.Model(&myUser{}).Where("id >= ?", 16).Scan(&d1)
		fmt.Printf("scan=%v\n", d1) // tx的结果已缓存
		var d2 myUser
		tx.Last(&d2) // 获取上次tx结果的最后一个结果
		fmt.Printf("scan=%v\n", d2)
		return nil
	})
}



4.模型类

约定

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"time"
)

// 2.2 表名-实现Tabler接口来更改默认表名
type MyUU struct {
	gorm.Model
}

func (MyUU) TableName() string {
	return "myuu"
}


func main() {
	/**
	GORM倾向于约定优于配置
	默认情况下GORM 使用 ID 作为主键
	使用结构体名的蛇形复数作为表名
	字段名的蛇形作为列名 并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间
	如果遵循GORM的约定 您就可以少写的配置、代码
	如果约定不符合您的实际要求 GORM允许你配置它们
	*/

	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			//SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			//NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 1.1 主键-默认约定ID作为主键
	type MyUser struct {
		ID   uint64 // 默认情况下 即便未声明primaryKey标签 约定ID作为主键
		Name string
	}

	// 1.2 主键-自定义主键
	type MyUser2 struct {
		MyID   uint64 `gorm:"primaryKey"` // 自定义相关字段为主键 若不需要整型自增情况 需要声明标签autoIncrement:false
		MyName string
	}

	// 1.3 主键-复合主键(声明多个字段为主键)
	type MyUser3 struct {
		PA    string `gorm:"primaryKey"`
		PB    string `gorm:"primaryKey"`
		PName string
	}

	// 2.表名-结构体名
	//type MyUU struct {
	//	gorm.Model
	//}

	// 2.1 表名-默认为复数形式 MyUser4=>my_user4 | MyUU=>my_uus
	err = db.AutoMigrate(&MyUU{})
	if err != nil {
		fmt.Println("数据表迁移异常:" + err.Error())
		return
	}
	database := db.Migrator().CurrentDatabase()
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;tables:%v\n", database, tables)

	//2.2 表名-实现Tabler接口来更改默认表名
	//type MyUU struct {
	//	gorm.Model
	//}
	//
	//func (MyUU) TableName() string {
	//	return "myuu"  // 指定MyUU-结构体并返回指定表名
	//}

	// 2.3 表名-指定临时表名(Table指定表名)
	// TableName 不支持动态变化 它会被缓存下来以待后续使用 若想使用动态表名 需使用Scopes
	db.Table("myuuu").AutoMigrate(&MyUU{})
	tables, _ = db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;tables:%v\n", database, tables)
	// 2.4 表名-通过指定Gorm配置-NamingStrategy配置项 &gorm.Config{NamingStrategy: schema.NamingStrategy{SingularTable: true, // 表名非复数即不带s}}
	
	// 3. 列名
	// 3.1 默认约定的命名规范
	type MyUser5 struct {
		ID      uint64    // id
		Name    string    // name
		MyBirth time.Time // my_birth
	}

	// 3.2 自定义列名
	type MyUser6 struct {
		ID      uint64    `gorm:"column:my_id"`
		Name    string    `gorm:"column:my_name"`
		MyBirth time.Time `gorm:"column:my_birth"`
	}

}

字段标签

标签名(大小写不敏感 默认小驼峰) 说明
column 指定 db 列名
type 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not nullsize, 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 迁移时为字段添加注释

关联标签

标签 描述
foreignKey 指定当前模型的列作为连接表的外键
references 指定引用表的列名,其将被映射为连接表外键
polymorphic 指定多态类型,比如模型名
polymorphicValue 指定多态值、默认表名
many2many 指定连接表表名
joinForeignKey 指定连接表的外键列名,其将被映射到当前表
joinReferences 指定连接表的外键列名,其将被映射到引用表
constraint 关系约束,例如:OnUpdateOnDelete

单个声明

package main

import (
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
	"time"
)

// 1. 模型类定义
type User struct {
	gorm.Model // GORM 定义一个 gorm.Model 结构体,其包括字段 ID、CreatedAt、UpdatedAt、DeletedAt
}

// gorm.Model 定义的结构体(自带集成的结构体)
type Model struct {
	ID        uint `gorm:"primarykey"`
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt gorm.DeletedAt `gorm:"index"`
}

// 2.1 相关字段约束
// gorm migrate 创建表时 不会创建忽略的字段
type User2 struct {
	Name1 string `gorm:"<-:create"`          // 允许读和创建
	Name2 string `gorm:"<-:update"`          // 允许读和更新
	Name3 string `gorm:"<-"`                 // 允许读和写(创建和更新)
	Name4 string `gorm:"<-:false"`           // 允许读,禁止写
	Name5 string `gorm:"->"`                 // 只读(除非有自定义配置,否则禁止写)
	Name6 string `gorm:"->;<-:create"`       // 允许读和写
	Name7 string `gorm:"->:false;<-:create"` // 仅创建(禁止从 db 读)
	Name8 string `gorm:"-"`                  // 通过 struct 读写会忽略该字段
	Name9 string `gorm:"-:all"`              // 通过 struct 读写、迁移会忽略该字段
	Name0 string `gorm:"-:migration"`        // 通过 struct 迁移会忽略该字段
}

// 2.2 相关字段约束-时间
// GORM 约定使用 CreatedAt、UpdatedAt 追踪创建/更新时间 如果定义了这种字段,GORM 在创建、更新时会自动填充 当前时间
//
type User3 struct {
	CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充
	UpdatedAt int       // 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充
	Updated1  int64     `gorm:"autoUpdateTime:nano"`  // 使用时间戳纳秒数填充更新时间
	Updated2  int64     `gorm:"autoUpdateTime:milli"` // 使用时间戳毫秒数填充更新时间
	Created   int64     `gorm:"autoCreateTime"`       // 使用时间戳秒数填充创建时间
}

// 3.1 嵌套结构体-gorm.Model自带的结构体
/**
下面例子等价于
type User4 struct{
  ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
  Name string
}
*/
type User4 struct {
	gorm.Model
	Name string
}

// 3.2 嵌套结构体-标签指定嵌套的结构体embedded
// 同时可针对嵌套的结构体内的字段使用标签embeddedPrefix进行结构体内字段前缀声明
type User5 struct {
	Name string
	Age  uint
}

type UserInfo struct {
	ID     uint  //
	MyUser User5 `gorm:"embedded;embeddedPrefix:t_"`
}

/**
等价于
type UserInfo struct{
	ID int
	TName string
	TAge uint
}
创建user_info表
CREATE TABLE `user_info` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `t_name` longtext,
  `t_age` bigint unsigned DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	db.AutoMigrate(&UserInfo{})

}

Belongs To 一对一

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

//type Stu struct {
//	ID         uint64    `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
//	Name       string    `gorm:"column:name;type:string;size:8;not null;default:'';comment:姓名"`
//	Age        uint8     `gorm:"column:age;not null;default:0;comment:年龄"`
//	CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null;default:current_timestamp;comment:创建时间"`
//	UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null;default:current_timestamp on update current_timestamp;comment:更新时间"`
//}

type CU struct {
	CID  uint64 `gorm:"column:c_id;primaryKey;autoIncrement;not null;comment:主键id"`
	Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:姓名"`
}

type CC struct {
	ID   uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
	Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:公司名称"`
	// gorm 默认使用关联的struct名+主键名(ID)方式指定外键 且需要指定struct
	// 也可采用struct + 列名指定外键
	// 默认方式 会采用CU + ID方式指定为外键 即CUID为外键 CUID需要声明
	//CU   CU
	// 可对外键添加更多约束(关联的<c_id>更新则更新该外键|关联的<c_id>删除则设置外键值为NULL)
	CU   CU `gorm:"foreignKey:c_uid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
	CUID uint64
}

/**
CREATE TABLE `cu` (
  `c_id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
  PRIMARY KEY (`c_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `cc` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '公司名称',
  `c_uid` bigint unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_cc_cu` (`c_uid`),
  //CONSTRAINT `fk_cc_cu` FOREIGN KEY (`c_uid`) REFERENCES `cu` (`c_id`) // 第一版默认
  CONSTRAINT `fk_cc_cu` FOREIGN KEY (`c_uid`) REFERENCES `cu` (`c_id`) ON DELETE SET NULL ON UPDATE CASCADE // 第二版对外键添加约束
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			//NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	err = db.AutoMigrate(&CC{}, &CU{})
	if err != nil {
		fmt.Println("数据表迁移异常:" + err.Error())
		return
	}
	database := db.Migrator().CurrentDatabase()
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;tables:%v\n", database, tables)

	// 对于需要建立belongs to 关系 数据表中需要存在外键
	// 1. 新增
	c := CC{ID: 1, Name: "c1", CU: CU{Name: "u1"}}
	tx := db.Create(&c) // 关联数据CU也会创建
	//tx := db.Omit("CU").Create(&c) // 忽略CU关联数据创建
	//tx := db.Omit(clause.Associations).Create(&c) // 忽略所有关联数据创建
	fmt.Printf("新增-影响行数:%v;异常:%v\n", tx.RowsAffected, tx.Error)

	// 2. 查询
	var c0 CC
	//db.Preload("CU").Find(&c0, "id=?", 1)
	// <=>
	//db.Preload("CU", "c_id=?", 1).Find(&c0)
	// <=>
	//db.Joins("CU").Find(&c0, "id=?", 1)
	// <=>
	db.Joins("CU", db.Where(&CU{CID: 1})).Find(&c0)
	sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
		return tx.Joins("CU", db.Where(&CU{CID: 1})).Find(&c0)
	})
	fmt.Printf("关联查询sql=%v\n", sql)
	fmt.Printf("关联查询-数据:%v\n", c0)
}


Has One一对一

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

type AU struct {
	AID  uint64 `gorm:"column:a_id;primaryKey;autoIncrement;not null;comment:主键id"`
	Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:姓名"`
}

type AC struct {
	ID   uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
	Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:公司名称"`
	// gorm 默认使用关联的struct名+主键名(ID)方式指定外键 且需要指定struct
	// 也可采用struct + 列名指定外键
	// 默认方式 会采用AU + ID方式指定为外键 即AUID为外键 AUID需要声明
	// 可对外键添加更多约束(关联的<a_id>更新则更新该外键|关联的<c_id>删除则设置外键值为NULL)
	AU   AU     `gorm:"foreignKey:a_uid;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
	AUID uint64 `gorm:"unique"` // 一对一需要设置外键为唯一
}

/**
CREATE TABLE `ac` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '公司名称',
  `a_uid` bigint unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `a_uid` (`a_uid`),
  CONSTRAINT `fk_ac_au` FOREIGN KEY (`a_uid`) REFERENCES `au` (`a_id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

CREATE TABLE `au` (
  `a_id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
  PRIMARY KEY (`a_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			//NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	err = db.AutoMigrate(&AC{}, &AU{})
	if err != nil {
		fmt.Println("数据表迁移异常:" + err.Error())
		return
	}
	database := db.Migrator().CurrentDatabase()
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;tables:%v\n", database, tables)


	// 1. 新增
	c := AC{ID: 1, Name: "c1", AU: AU{Name: "u1"}}
	tx := db.Create(&c) // 关联数据AU也会创建
	//tx := db.Omit("AU").Create(&c) // 忽略CU关联数据创建
	//tx := db.Omit(clause.Associations).Create(&c) // 忽略所有关联数据创建
	fmt.Printf("新增-影响行数:%v;异常:%v\n", tx.RowsAffected, tx.Error)

	// 2. 查询
	var c0 AC
	//db.Preload("AU").Find(&c0, "id=?", 1)
	// <=>
	//db.Preload("AU", "c_id=?", 1).Find(&c0)
	// <=>
	//db.Joins("AU").Find(&c0, "id=?", 1)
	// <=>
	db.Joins("AU", db.Where(&AU{AID: 1})).Find(&c0)
	sql := db.ToSQL(func(tx *gorm.DB) *gorm.DB {
		return tx.Joins("AU", db.Where(&AU{AID: 1})).Find(&c0)
	})
	fmt.Printf("关联查询sql=%v\n", sql)
	fmt.Printf("关联查询-数据:%v\n", c0)

}


一对多

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

type Novel struct {
	ID     uint64   `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
	Name   string   `gorm:"column:name;type:varchar(8);not null;default:'';comment:小说名称"`
	Person []Person `gorm:"foreignKey:novel_id;constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`
}

type Person struct {
	ID   uint64 `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
	Name string `gorm:"column:name;type:varchar(8);not null;default:'';comment:姓名"`
	// gorm 默认使用关联的struct名+主键名方式指定外键 且需要指定struct
	// 也可采用struct + 列名指定外键
	NovelID uint64
}

/**
CREATE TABLE `novel` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '小说名称',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;


CREATE TABLE `person` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(8) NOT NULL DEFAULT '' COMMENT '姓名',
  `novel_id` bigint unsigned DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_novel_person` (`novel_id`),
  CONSTRAINT `fk_novel_person` FOREIGN KEY (`novel_id`) REFERENCES `novel` (`id`) ON DELETE SET NULL ON UPDATE CASCADE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
*/

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			//NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	err = db.AutoMigrate(&Novel{}, &Person{})
	if err != nil {
		fmt.Println("数据表迁移异常:" + err.Error())
		return
	}
	database := db.Migrator().CurrentDatabase()
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表迁移成功;db:%v;tables:%v\n", database, tables)

	// 1. 新增
	c := Novel{ID: 1, Name: "笑傲江湖", Person: []Person{{Name: "令狐冲"}, {Name: "任盈盈"}}}
	tx := db.Create(&c) // 关联数据MAU也会创建
	//tx := db.Omit("Novel").Create(&c) // 忽略NOVEL关联数据创建
	//tx := db.Omit(clause.Associations).Create(&c) // 忽略所有关联数据创建
	fmt.Printf("新增-影响行数:%v;异常:%v\n", tx.RowsAffected, tx.Error)

	// 2. 查询(查询novel并输出关联的person信息)
	var ns []Novel
	err = db.Model(&Novel{}).Preload("Person").Find(&ns).Error
	fmt.Printf("关联查询-数据:%v\n", ns)
}


多对多

package main

import (
	"fmt"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/schema"
)

// 不使用中间表
type novel struct {
	ID      uint64    `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
	Name    string    `gorm:"column:name;type:varchar(8);not null;default:'';comment:小说名称"`
	Persons []*person `json:"persons" gorm:"many2many:novel_person;foreignKey:ID;joinForeignKey:novelId;joinReferences:personId"`
	/**
	many2many: 定义中间表名
	foreignKey: 指定表字段为外键
	joinForeignKey: 当前表关联到中间表的字段名
	joinReferences: 反向引用的字段 用于查询关联表的信息
	PS:相当于有个中间表 表名为novel_person;字段名有person_id和novel_id
	*/
}

type person struct {
	ID     uint64   `gorm:"column:id;primaryKey;autoIncrement;not null;comment:主键id"`
	Name   string   `gorm:"column:name;type:varchar(8);not null;default:'';comment:姓名"`
	Novels []*novel `json:"novels" gorm:"many2many:novel_person;foreignKey:ID;joinForeignKey:personId;joinReferences:novelId"`
}

func main() {
	dsn := "root:python@tcp(localhost:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		NamingStrategy: schema.NamingStrategy{
			SingularTable: true, // 表名非复数即不带s
			//TablePrefix: "t_",  // 表名前缀
			//NoLowerCase: false, // 表名剔除下划线且字段名和结构体的字段保持一致(大写反之则为a_b形式) 推荐为false
			//NameReplacer: strings.NewReplacer("ID", "id"),  // 替换数据表字段ID为id
		},
	})
	if err != nil {
		panic("数据库连接失败")
	}

	// 迁移 schema
	err = db.AutoMigrate(&novel{}, &person{})
	if err != nil {
		fmt.Println("数据表创建异常:" + err.Error())
		panic("数据表创建异常:" + err.Error())
	}
	database := db.Migrator().CurrentDatabase()
	tables, _ := db.Migrator().GetTables()
	fmt.Printf("数据表创建成功;db:%v;tables:%v\n", database, tables)

	// 1. 新增
	ps := []*person{{Name: "令狐冲"}, {Name: "任盈盈"}}
	db.Create(&ps)
	n := novel{ID: 1, Name: "笑傲江湖", Persons: ps}
	tx := db.Create(&n)
	fmt.Printf("新增-影响行数:%v;异常:%v\n", tx.RowsAffected, tx.Error)

	// 2. 查询  preload后跟结构体关联的字段名称
	var ns []novel
	err = db.Model(&novel{}).Preload("Persons").Find(&ns).Error
	fmt.Printf("关联查询-数据:%v\n", ns)

	var pss []person
	//err = db.Preload("Novels", "name=?", "笑傲江湖").Find(&ps).Error
	err = db.Model(&person{}).Preload("Novels").Find(&pss).Error
	fmt.Printf("关联查询-数据:%v\n", pss)
}


文档

[1] https://gorm.io/zh_CN/docs/index.html

[2] https://pkg.go.dev/gorm.io/driver/mysql#section-readme

[3] https://learnku.com/docs/gorm

标签:name,fmt,db,gorm,result,id,入门
From: https://www.cnblogs.com/fsh19991001/p/18025620

相关文章

  • Python 机器学习工具 scikit-learn 的入门使用
    参考文档:https://www.scikitlearn.com.cn/通过对已有多组数据的训练,从而实现对单个数据的结果预测安装pipinstall-Uscikit-learnDemo通过使用sklearn包中的决策树DecisionTreeclassifier算法实现简单预测importsklearnfromsklearnimporttreefeature=[[178,1],......
  • Java入门
    Java入门1.1Java特性和优势简单性面向对象可移植性高性能分布式动态性多线程安全性健壮性1.2Java三大版本JavaSE:标准版JavaME:嵌入式开发JavaEE:企业级开发1.3JDK,JRE,JVMJDK:JavaDevelopmentKitJRE:JavaRuntimeEnvironmentJVM:JavaVirtualMachi......
  • Linux 网络编程从入门到进阶 学习指南
    前言大家好,我是小康。在上一篇文章中,我们探讨了Linux系统编程的诸多基础构件,包括文件操作、进程管理和线程同步等,接下来,我们将视野扩展到网络世界。在这个新篇章里,我们要让应用跳出单机限制,学会在网络上跨机器交流信息。接下来,我们要深入套接字(sockets)和TCP/IP协议,揭示如何......
  • Linux 系统编程从入门到进阶 学习指南
    引言大家好,我是小康,今天我们来学习一下Linux系统编程相关的知识。Linux系统编程是连接高级语言和硬件的桥梁,它对深入理解计算机系统至关重要。无论你是打算构建高性能服务器还是开发嵌入式设备,掌握Linux系统编程是C和C++开发者的基本技能。本文旨在为初学者提供一个清......
  • kettle从入门到精通 第四十四课 kettle 去重
     1、我们平常在写应用程序的时候,会有去重的业务场景,可以在数据库层面解决,也可以在内存层面解决。同样kettle也有去重的步骤【唯一行(哈希值)】和【去除重复记录】唯一行(哈希值):使用HashSet来删除重复行,只保留唯一的行。去除重复记录(UniqueRows):删除重复行,只保留唯一的行。这只......
  • [转]基于前端技术栈的PC跨平台桌面应用开发技术Electron简介及快速入门
    原文地址:Electron简介及快速入门-知乎大江东去:基于EA的软件工程创新理论与最佳实践第四章:桌面应用系统开发基础及入门第四节:Electron简介及快速入门一、Electron基本介绍官网地址:https://www.electronjs.org/Electron是一个由OpenJS基金会维护的开源项目,也是一个活跃的......
  • docker快速入门与基本指令
    参考资料:https://zhuanlan.zhihu.com/p/137895577https://www.runoob.com/docker/ubuntu-docker-install.html安装docker的安装相对简单,官方提供了一个安装命令:curl-fsSLhttps://test.docker.com-otest-docker.shsudoshtest-docker.sh可以使用piplist|grepd......
  • 【前端开发】VSCode下载安装教程,新手入门(超详细)附安装包
    ​1.VSCode简介        VSCode,全称VisualStudioCode,是一款由微软开发的跨平台源代码编辑器,可用于Windows、Linux和macOS操作系统。以下是对VSCode的详细介绍:功能丰富:VSCode支持语法高亮、代码自动补全(又称IntelliSense)、代码重构、查看定义功能,并内置了命令行工......
  • 【解题报告】【比赛复现】洛谷入门赛 #17 题解
    洛谷入门赛#17题解今日推歌:《春嵐feat.初音ミク》john感觉这首都快成周榜战神了(Before关于我做入门赛的精神状态:没做T4,因为题面读得我头疼……而且大模拟不想做(虽然也不是多大的模拟展开目录目录洛谷入门赛#17题解BeforeA食堂B数学选择题AfterC风球E式神考核Af......
  • 热辣滚烫,Salesforce开发入门指南:零基础学习宝典!
    开发人员将Salesforce组织扩展到声明式配置之外,构建应用程序,进而优化业务运营。Salesforce开发人员通常会使用两种编程语言:Apex和JavaScript。然而,Salesforce开发不仅仅只包括代码。为了在职业道路上脱颖而出,开发人员还需要了解声明性功能,将组织的设计和性能保持最佳状态。Sal......