GORM数据库操作

1. GORM连接MySQL

1.1 ORM

1.1.1 什么是ORM

gorm 数据库 链接MySQL gorm操作数据库_User

gorm 数据库 链接MySQL gorm操作数据库_Time_02

1.1.2 ORM优缺点

优点:

  1. 提高开发效率

缺点:

  1. 牺牲执行性能
  2. 牺牲灵活性
  3. 弱化SQL能力

1.2 GROM

官方文档

执行下面命令安装GROM

go get -u gorm.io/gorm

go get -u gorm.io/driver/mysql(数据库不同这个也不同)

1.3 连接数据库

gorm.Open来完成数据库的连接,参数是规定好的,一般就按照这样就可以,有特殊需求再去修改。

dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})

其中root:123456前面是账号,后面是密码;@后面是连接的服务器及其端口(本地默认端口127.0.0.1:3306);/后面grom是数据库的名字;?后面就是一些配置了。

package main

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

type UserInfo struct {
	ID     int
	Name   string
	Gender string
	Hobby  string
}

func main() {
	// 连接数据库
	dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	// 创建表 自动迁移 (把结构体和数据表进行对应)
	db.AutoMigrate(&UserInfo{})

	// 创建数据行
	u1 := UserInfo{
		ID:     1,
		Name:   "firecar",
		Gender: "男",
		Hobby:  "篮球",
	}
	db.Create(&u1)
}

可以使用AutoMigrate方法来创建数据表(根据结构体的成员信息来创建)。

插入行直接db.Create(&u1),很方便,注意传的是地址就可以了。

1.4 简单操作

1.4.1 增Create

u1 := UserInfo{
    ID:     1,
    Name:   "firecar",
    Gender: "男",
    Hobby:  "篮球",
}
db.Create(&u1)

1.4.2 查First(第一条数据)

还有其他很多方法后续再补充。

u := new(UserInfo)
db.First(u)
fmt.Printf("u:%#v\n", u)

1.4.3 改

因为在First里面已经给u赋值了,所以才能通过Model...修改。

u := new(UserInfo)
db.First(u)
db.Model(u).Update("hobby", "rap")
fmt.Printf("u:%#v\n", u)

1.4.4 删

u := new(UserInfo)
db.First(u)
db.Delete(u)

可以看出来修改和删除都必须通过查询来得到具体的实例才可以进行操作。

1.5 MySQL数据库启动模板

package main

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

func main() {
	// 连接数据库
	dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
}

2. GORM Model

在使用ORM工具时,通常我们需要在代码中定义模型(Models)与数据库中的数据表进行映射,在GORM中模型
(Models)通常是正常定义的结构体、基本的go类型或它们的指针。同时也支持sql.Scannerdriver.Valuer接口(interfaces) 。

2.1 gorm.Model

// gorm.Model
type Model struct{
    ID		uint `gorm:"primary_key"`
    CreatedAt time.Time
    UpdateAt time.Time
    DeleteAt *time.Time
}

可以把它嵌入到自己的模型中:

type User struct {
    gorm.Model
    Name string
}

当然用不用无所谓的……

2.2 定义模型(模型Tag)

package main

import (
	"database/sql"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"time"
)

type User struct {
	gorm.Model   // 内嵌gorm.Model
	Name         string
	Age          sql.NullInt64 // 零值类型
	Birthday     *time.Time
	Email        string  `gorm:"type:varchar(100);unique_index"`
	Role         string  `gorm:"size:255"`        // 设置字段大小为255
	MemberNumber *string `gorm:"unique;not null"` // 设置会员号(member number)唯一并且不为空
	Num          int     `gorm:"AUTO_INCREMENT"`  // 设置 num 为自增类型
	Address      string  `gorm:"index:addr"`      // 给address字段创建名为addr的索引
	IgnoreMe     int     `gorm:"-"`               // 忽略本字段
}

func main() {
	// 连接数据库
	dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}

}

使用结构体声明模型时,标记(tags)是可选项。

gorm支持以下标记:

gorm 数据库 链接MySQL gorm操作数据库_User_03

gorm 数据库 链接MySQL gorm操作数据库_mysql_04

2.3 主键、表名、列名的约定

2.3.1 主键

默认把ID作为主键。如果想要自行设置主键可以:

type Animal struct {
    AnimalID int64 `gorm:"primary_key"`
    Name string
    Age int64
}

通过使用tag来设置主键。

2.3.2 表名

默认是结构体名称的小写、复数,驼峰标明的会用_分隔开。

如:type Animal -> animalstype UserInfo->user_infos

如果想要自行设置,需要写一个方法:

type Animal struct {
	AnimalID int `gorm:"primary_key"`
	Name     string
}

func (Animal) TableName() string {
	return "firecar"
}

还可以用方法加一些判断,一下就是判断是不是管理员再决定使用哪个表。

func(User) TableName()string {
    return "profiles"
}
func(u User) TableName() string {
    if u.Role == "admin" {
        return "admin_users"
    }
    return "users"
}

禁用表的复数形式db.SingularTable(true)

还可以直接自己指定表的名字:

// 使用User结构体创建名为`deleted_users`的表
db.Table("deleted_users").CreateTable(&User{})

var deleted_users []User
db.Table("deleted_users").Find(&deleted_users)
//// SELECT * FROM deleted_users;

db.Table("deleted_users").Where("name = ?", "jinzhu").Delete()
//// DELETE FROM deleted_users WHERE name = 'jinzhu';

但是好像要一直自己指定,因为直接用db的话他会以规则来查找表。

那最终的方案就是修改db的从实例出发找表的默认规则了。

gorm.DefaultTableNameHandler = func (db *gorm.DB, defaultTableName string) string  {
  return "prefix_" + defaultTableName;
}

放在main函数开头,defaultTableName就是你的结构体名,可以给他加一个前缀,那么就return "prefix_" + defaultTableName;当然也可以进行其他的操作。

当然,因为是默认规则,所以如果使用了其他方法修改了,就不会再去使用默认规则了。

2.3.3 列名

列名由字段名称进行下划线分割来生成,大写变小写。

type User struct {
  ID        uint      // `id`
  Name      string    // `name`
  Birthday  time.Time // `birthday`
  CreatedAt time.Time // `created_at`
}

使用tag来进行指定:

type Animal struct {
  AnimalId    int64     `gorm:"column:beast_id"`
  Birthday    time.Time `gorm:"column:day_of_the_beast"`
  Age         int64     `gorm:"column:age_of_the_beast"`
}

2.4 时间戳

2.4.1 CreatedAt

db.Create(&user) // `CreatedAt`将会是当前时间

// 可以使用`Update`方法来改变`CreateAt`的值
db.Model(&user).Update("CreatedAt", time.Now())

2.4.2 UpdateAt

db.Save(&user) // `UpdatedAt`将会是当前时间

db.Model(&user).Update("name", "jinzhu") // `UpdatedAt`将会是当前时间

2.4.3 DeleteAt

如果模型有DeletedAt字段,调用Delete删除该记录时,将会设置DeletedAt字段为当前时间,而不是直接将记录从数据库中删除(软删除)。

3. CRUD(增查改删)

增加(Create)、读取(Read)、更新(Update)和删除(Delete)

3.1 创建记录及字段默认值相关

通过Debug的方法来查看具体使用的SQL语句

3.1.1 创建记录

package main

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

// User 1. 定义模型
type User struct {
	ID   int
	Name string
	Age  int
}

func main() {
	// 连接MySQL数据库

	dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	// 2. 把模型与数据库中的表对应起来
	db.AutoMigrate(&User{})
	// 3. 创建
	u := User{Name: "firecar", age: 18} // 在代码层面创建一个User对象
	res := db.Create(&u)                // 通过数据的指针来创建
    
	fmt.Println(u.ID)                   // 返回插入数据的主键
	fmt.Println(res.Error)              // 返回 error
	fmt.Println(res.RowsAffected)       // 返回插入记录的条数
}
/*
1
<nil>
1
*/

3.1.2 默认值

插入记录到数据库时,默认值会被用于填充值为零值的字段

package main

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

// User 1. 定义模型
type User struct {
	ID   int
	Name string `gorm:"default:'firecar'"`
	Age  int
}

func main() {
	// 连接MySQL数据库

	dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	// 2. 把模型与数据库中的表对应起来
	db.AutoMigrate(&User{})
	// 3. 创建
	u := User{Age: 18}    // 在代码层面创建一个User对象
	db.Debug().Create(&u) // 通过数据的指针来创建

}
/*
[7.326ms] [rows:1] INSERT INTO `users` (`name`,`age`) VALUES ('firecar',18)
*/

注意 对于声明了默认值的字段,像 0''false 等零值是不会保存到数据库。

什么意思呢,就是说,如果你设置了默认值,但是你想让你的name = ""这是不行的,他会被默认忽略掉。

u := User{Name: "", Age: 18}
// [5.752ms] [rows:1] INSERT INTO `users` (`name`,`age`) VALUES ('firecar',18)

您需要使用指针类型或 Scanner/Valuer 来避免这个问题:

type User struct {
	ID   int
	Name *string `gorm:"default:'firecar'"`
	Age  int
}
u := User{Name: new(string), Age: 28}

// [4.747ms] [rows:1] INSERT INTO `users` (`name`,`age`) VALUES ('',28)

第二个方法就是用一个sql.NullXxxx类型来解决,先看一下这个类型:

type NullString struct {
	String string
	Valid  bool // Valid is true if String is not NULL
}

Valid就代表是不是有值,也就是说如果Valid = true那么无论String的值是什么(包括空字符串“”)它也同样会使用。

type User struct {
	ID   int
	Name sql.NullString `gorm:"default:'firecar'"`
	Age  int
}
u := User{Name: sql.NullString{
    String: "",
    Valid:  true,
}, Age: 28} // 在代码层面创建一个User对象
// [5.715ms] [rows:1] INSERT INTO `users` (`name`,`age`) VALUES ('',28)

3.2 查询操作

现在数据库里弄两条数据:

package main

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

// 1. 创建模型
type User struct {
	gorm.Model
	Name string
	Age  int
}

func main() {
	// 连接数据库
	dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	// 2. 模型和数据库的表对应起来
	db.AutoMigrate(&User{})
	u1 := User{
		Name: "firecar1",
		Age:  18,
	}
	db.Create(&u1)
	u2 := User{
		Name: "sky6",
		Age:  21,
	}
	db.Create(&u2)
}

3.2.1 一般查询

// 3. 查询
user := new(User)
db.First(user)
fmt.Printf("user:%#v", user)
// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;

// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;

// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;

result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error or nil

// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)

3.2.2 条件查询

// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

Find之前加上Where语句,具体的就是SQL的内容了。

也可以把他直接写在db.Find()的后面的参数里,如:

db.Find(&users,"name = ?", "firecar")

3.2.3 FirstOrInit查找或创建

如果查询的不存在就创建,否则还是查询。

db.FirstOrInit(&user, User{
    Name : "firecar",
})

如果不存在就创建一个,存在就返回第一个。

3.2.4 Attrs创建时附加

如果要创建信息,就要把内容给附加上,如:

db.Attrs(User{
	Age : 22
}).FirstOrInit(&user, User{
    Name : "firecar",
})

这样,当firecar不存在的时候创建的firecar也会有Age参数,并且等于22了。

3.2.5 Assign必须附加

和上面唯一的区别就是,Assign不管找没找到都要附加上信息。

……更多看一下官方文档。

3.3 更新操作

更新操作和后面的删除操作那肯定是要依赖于上面的查询操作的,也可以说查询操作才是最关键的内容,因为你要更新总得要有个要修改的对象,对象哪里来?就是要通过查询操作来获取的。

3.3.1 更新所有字段

使用Save

package main

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

// 1. 创建模型
type User struct {
	gorm.Model
	Name   string
	Age    int
	Active bool
}

func main() {
	// 连接数据库
	dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	// 2. 模型和数据库的表对应起来
	db.AutoMigrate(&User{})

	// 3. 查询
	user := new(User)
	db.First(user)
	user.Name = "huoyanche"
	user.Age = 100
	db.Save(user)
}

此时所有的字段都会更新。

3.3.2 更新修改字段

使用UpdateUpdates来进行修改。

db.First(user)
db.Debug().Model(user).Update("name", "王大炮")
// UPDATE `users` SET `name`='王大,`updated_at`='2023-03-09 19:24:49.36' WHERE `users`.`deleted_at` IS NULL AND `id` = 1

3.3.3 更新选定字段

SelectOmit方法,Select是只修改选中的,Omit相反,只不修改选中的。

m1 := map[string]any{
    "name":   "ii7",
    "age":    22,
    "active": false,
}
db.Debug().Model(user).Updates(m1)
db.Debug().Model(user).Select("age").Updates(m1)
db.Debug().Model(user).Omit("active").Updates(m1)

Select

UPDATE `users` SET `age`=22,`up
dated_at`='2023-03-09 19:32:59.796' WHERE `users`.`deleted_at` IS NULL AND `id` = 1

Omit

UPDATE `users` SET `age`=22,`na
me`='ii7',`updated_at`='2023-03-09 19:32:59.801' WHERE `users`.`deleted_at` IS NULL AND `id` = 1

3.3.4 无HOOK

前面写的方法都会默认修改更新时间,尽管我们没有操作。如果Only修改制定的就用db.Debug().Model(user).UpdateColumn("age",30)

这样除了age什么都不会修改了。

3.3.5 使用SQL表达式更新

让所有用户年龄+2。哎呀,这一块上次改了好久,终于发现问题所在,如果Find的参数是User类型,它只会返回第一条查询;如果参数是[]User类型,就会返回所有的查询。(db.Find = SELECT * FROM ...)

而且可能是版本不同,或者视频上有什么出入,感觉应该是不对的。

package main

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

// 1. 创建模型
type User struct {
	gorm.Model
	Name   string
	Age    int
	Active bool
}

func main() {
	// 连接数据库
	dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	//2. 模型和数据库的表对应起来
	_ = db.AutoMigrate(&User{})
	var users []User
	db.Find(&users)
	db.Model(&users).Update("age", gorm.Expr("age+?", 2))
}

3.4 删除操作

3.4.1 单条删除

软删除,就是给delete_at赋值为当前的时间,表示已经被删除了,之后也不会对他进行操作。

package main

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

type User struct {
	gorm.Model
	Name   string
	Age    int
	Active bool
}

func main() {
	dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	_ = db.AutoMigrate(&User{})
	u := new(User)
	u.ID = 1
	db.Debug().Delete(u)
}

这种开一个实例对象然后再u.ID = 1最后db.Delete的删除方式只适用于删除指定主键,因为在上面的代码中主键是ID因此可以通过这种操作完成任务。

如果不写主键(ID),会导致整张表的所有内容都被删除。

也就是说只要没有u.ID就会进行一个相当于DELETE * 的操作(当然因为是软删除,所以只是把所有数据的delete_at赋值)

3.4.2 批量删除

那如果我想根据别的列来删除,或者我要删除满足某个条件的所有信息就可以使用Where来进行限制。

func main() {
	dsn := "root:123456@tcp(127.0.0.1:3306)/gorm?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	_ = db.AutoMigrate(&User{})
	db.Debug().Where("age = ?", 18).Delete(&User{})
}
/*
UPDATE `users` SET `deleted_at`='2023-03-11 11:08:09.101' WHERE age = 18 AND `users`.`deleted_at` IS NULL
*/

上面命令等价于db.Delete(&User{},"age = ?",18)

3.4.3 软删除以及物理删除

只要有deleted_at这一列就会是软删除,被软删除的记录想要查询也是可以的:db.Unscoped().Where("age = 18").Find(&users)

如果想要物理删除(直接在数据库表中移除这一行)。

db.Unscoped().Delete(&order)