Bootstrap

go语言操作Gorm

go语言GORM

1.1 gorm介绍

Gorm 是一个流行的 Go 语言的 ORM(对象关系映射)库,用于在 Go 语言中进行数据库操作。它提供了简洁的 API 和强大的功能,帮助开发者简化数据库的操作和管理。Gorm 支持多种关系型数据库,包括 MySQL、PostgreSQL、SQLite 和 SQL Server 等。它提供了一种面向对象的方式来处理数据库,将数据库表映射为 Go 结构体,并提供了丰富的方法来执行增删改查等操作。

1.2 gorm特性

  • 全功能 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,命名参数,子查询
  • 复合主键,索引,约束
  • Auto Migration
  • 自定义 Logger
  • 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
  • 每个特性都经过了测试的重重考验
  • 开发者友好

1.3 安装

go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

1.4 创建数据库

CREATE DATABASE IF NOT EXISTS database_name

`注意: 可以在 navicat 上直接新建查询创建。

1.5 连接数据库

gorm.Open 的函数签名如下:

func Open(dialect string, args ...interface{}) (*gorm.DB, error)

参数说明:

  • dialect 是数据库的方言,表示要连接的数据库类型,如 “mysql”、“postgres”、“sqlite3” 等。
  • args 是连接数据库时的参数,如连接字符串等,具体根据不同的数据库类型和驱动而有所不同。

在调用 gorm.Open 时,你需要传入合适的数据库方言和连接参数,以便与目标数据库建立连接。根据不同的数据库类型和驱动,连接参数的格式会有所不同。

以下是一个示例,展示如何使用 gorm.Open 建立与 MySQL 数据库的连接:

package main

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

func main() {
	dsn := "root:123456@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
	// 建立与数据库的连接
	_, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	fmt.Println("connection succeeded")
}

在上述示例中,我们使用 “mysql” 方言和连接字符串来连接 MySQL 数据库。连接字符串的格式根据不同的数据库和驱动而有所不同,你需要替换示例中的 usernamepassworddatabase_name 为你实际的数据库连接信息。

请注意,在使用 Gorm 进行数据库操作之前,你需要确保成功建立与数据库的连接,以便后续的增删改查操作能够顺利进行。

1.6 模型定义

// User gorm.Model 定义
type User struct {
	ID   uint   `gorm:"primary_key"`
	Name string `gorm:"size:255"`
	Age  int
}

1.7 日志使用

Gorm有内置的日志记录器支持,默认情况下,它会打印发生的错误

 // 启用Logger,显示详细日志
db.LogMode(true)

// 禁用日志记录器,不显示任何日志
db.LogMode(false)

// 调试单个操作,显示此操作的详细日志
db.Debug().Where("name = ?", "jinzhu").First(&User{})

1.8 CRUD

1.8.1 自动迁移数据库表结构

	// 自动迁移数据库表结构
	err := db.AutoMigrate(&User{})
	if err != nil {
		log.Fatal(err)
	}

1.8.2 插入数据

插入数据:使用 Gorm,你可以使用 Create 函数创建新的记录。例如:

// CreateUser 创建用户
func CreateUser(db *gorm.DB, name string, age int) error {
	user := User{
		Name: name,
		Age:  age,
	}
	return db.Create(&user).Error
}

1.8.3 查询数据

查询单个数据:使用 Gorm,你可以使用 Find 函数查询记录。例如:

// GetUserByID 通过id查找用户
func GetUserByID(db *gorm.DB, id uint) (User, error) {
   var user User
   err := db.First(&user, id).Error
   return user, err
}

查询所有数据:使用 Gorm,你可以使用 Find 函数查询记录。例如:

// GetAllUsers 查询所有用户
func GetAllUsers(db *gorm.DB) ([]User, error) {
	var users []User
	err := db.Find(&users).Error
	return users, err
}

1.8.4 更新数据

更新记录:使用 Gorm,你可以使用 Update 函数或者Save 函数更新记录。例如:

// UpdateUserName 更新用户名称
func UpdateUserName(db *gorm.DB, id uint, name string) error {
	return db.Model(&User{}).Where("id = ?", id).Update("name", name).Error
}

1.8.5 删除数据

删除记录:使用 Gorm,你可以使用 Delete 函数删除记录。例如:

// DeleteUser 删除用户
func DeleteUser(db *gorm.DB, id uint) error {
	return db.Delete(&User{}, id).Error
}

1.8.6 代码示例

当使用 Gorm 实现增删改查函数时,你可以在建立数据库连接的基础上调用这些函数。以下是一个示例代码,展示了如何调用上述的增删改查函数:

package main

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

// User gorm.Model 定义
type User struct {
	ID   uint   `gorm:"primary_key"`
	Name string `gorm:"size:255"`
	Age  int
}

func main() {
	dsn := "root:123456@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
	// 建立与数据库的连接
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		panic(err)
	}
	fmt.Println("Connected to the database")

	// 自动迁移数据库表结构
	err = db.AutoMigrate(&User{})
	if err != nil {
		log.Fatal(err)
	}
	// 创建记录(插入数据)
	err = CreateUser(db, "倾心1", 25)
	if err != nil {
		log.Fatal(err)
	}
	err = CreateUser(db, "倾心2", 25)
	if err != nil {
		log.Fatal(err)
	}

	// 查询单条数据
	user, err := GetUserByID(db, 1)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(user)

	// 查询所有数据
	users, err := GetAllUsers(db)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("所有用户:")
	for _, user := range users {
		fmt.Println(user)
	}

	// 更新数据
	err = UpdateUserName(db, 1, "倾心3")
	if err != nil {
		log.Fatal(err)
	}

	// 查询更新后的数据
	user, err = GetUserByID(db, 1)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(user)

	// 删除记录
	err = DeleteUser(db, 1)
	if err != nil {
		log.Fatal(err)
	}

	// 再次查询所有数据
	users, err = GetAllUsers(db)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("所有用户:")
	for _, user := range users {
		fmt.Println(user)
	}
}

// CreateUser 创建用户
func CreateUser(db *gorm.DB, name string, age int) error {
	user := User{
		Name: name,
		Age:  age,
	}
	return db.Create(&user).Error
}

// GetUserByID 通过id查找用户
func GetUserByID(db *gorm.DB, id uint) (User, error) {
	var user User
	err := db.First(&user, id).Error
	return user, err
}

// GetAllUsers 查询所有用户
func GetAllUsers(db *gorm.DB) ([]User, error) {
	var users []User
	err := db.Find(&users).Error
	return users, err
}

// UpdateUserName 更新用户名称
func UpdateUserName(db *gorm.DB, id uint, name string) error {
	return db.Model(&User{}).Where("id = ?", id).Update("name", name).Error
}

// DeleteUser 删除用户
func DeleteUser(db *gorm.DB, id uint) error {
	return db.Delete(&User{}, id).Error
}

运行结果:

image-20230606110755563

这个示例代码定义了一个 User 结构体来映射数据库表。在 main 函数中,它首先建立与数据库的连接,然后使用 db.AutoMigrate 函数自动迁移数据库表结构。接下来,它调用 CreateUser 函数创建二个用户,先使用GetUserByID 查询id为1的用户打印结果,然后又使用 GetAllUsers 函数查询所有用户并打印结果。然后,它使用 UpdateUserName 函数更新用户名称,然后又使用GetUserByID函数查询更新后的数据信息,并使用 DeleteUser 函数删除用户。最后,它再次查询所有用户并打印结果。

你可以根据自己的需求和实际数据库连接信息,修改代码中的数据库连接字符串以及执行的增删改查操作。更多的需求可见 https://gorm.io/zh_CN/docs/ 文档学习进行操作。

1.9 Scanner/Valuer接口

Scanner 和 Valuer 是 Gorm 库中的接口,用于自定义类型在与数据库进行数据读取和写入时的行为。

1.9.1 Scanner

  • Scanner 接口用于将数据库的值扫描到自定义类型中。
  • 它定义了一个 Scan 方法,接收一个 interface{} 参数,用于接收数据库的值。
  • 实现了 Scanner 接口的类型可以自定义如何从数据库中读取数据并将其存储到自身。
  • 示例代码:
type JSON json.RawMessage

// 实现 sql.Scanner 接口,Scan 将 value 扫描至 Jsonb
func (j *JSON) Scan(value interface{}) error {
  bytes, ok := value.([]byte)
  if !ok {
    return errors.New(fmt.Sprint("Failed to unmarshal JSONB value:", value))
  }

  result := json.RawMessage{}
  err := json.Unmarshal(bytes, &result)
  *j = JSON(result)
  return err
}

1.9.2 Valuer

  • Valuer 接口用于将自定义类型的值转换为数据库可以接受的值。
  • 它定义了一个 Value 方法,返回一个 driver.Value 类型的值,用于表示数据库可以接受的数据。
  • 实现了 Valuer 接口的类型可以自定义如何将自身的值转换为数据库可以接受的形式。
  • 示例代码:
type JSON json.RawMessage

// 实现 driver.Valuer 接口,Value 返回 json value
func (j JSON) Value() (driver.Value, error) {
  if len(j) == 0 {
    return nil, nil
  }
  return json.RawMessage(j).MarshalJSON()
}

1.9.3 使用场景

  1. 自定义数据类型的数据库映射:
    • 当你需要将自定义的数据类型存储到数据库中或从数据库中读取时,可以实现 Scanner 和 Valuer 接口来自定义类型的读写行为。
    • 例如,你可以定义一个 Money 类型,它将货币值以整数形式存储在数据库中,但在应用程序中以浮点数或其他形式使用。
    • 通过实现 Valuer 接口,你可以将 Money 类型的值转换为数据库可以接受的整数值,通过实现 Scanner 接口,你可以将数据库的整数值转换为 Money 类型的值。
  2. 序列化和反序列化:
    • 当你需要将结构体或其他复杂类型的数据序列化为数据库可以存储的形式,或者从数据库中读取并反序列化为复杂类型时,可以使用 Scanner 和 Valuer 接口。
    • 你可以在实现 Valuer 接口的方法中将复杂类型转换为可存储的格式(例如 JSON 字符串),然后在实现 Scanner 接口的方法中将数据库的值转换回原始的复杂类型。
  3. 数据库字段加密和解密:
    • 当你需要在数据库中存储敏感数据(如密码)时,可以使用 Scanner 和 Valuer 接口来对数据进行加密和解密。
    • 在实现 Valuer 接口的方法中,你可以对敏感数据进行加密并返回加密后的值,而在实现 Scanner 接口的方法中,你可以对数据库的值进行解密并返回原始的敏感数据。

总的来说,Scanner 和 Valuer 接口提供了一种灵活的方式,允许你在 Gorm 中自定义数据类型的读写行为,以满足特定的需求,例如自定义数据类型的数据库映射、复杂类型的序列化和反序列化,以及数据库字段的加密和解密等场景。

1.9.4 代码示例

下面是一个示例代码,演示了如何在 Gorm 中使用 Scanner 和 Valuer 接口进行自定义类型的读写行为:

package main

import (
	"database/sql/driver"
	"fmt"
	"log"

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

type CustomData struct {
	Data string
}

func (cd *CustomData) Scan(value interface{}) error {
	// 从数据库的值中解析数据并存储到自定义类型中
	if v, ok := value.([]byte); ok {
		cd.Data = string(v)
	}
	return nil
}

func (cd CustomData) Value() (driver.Value, error) {
	// 将自定义类型的值转换为数据库可以接受的形式
	return []byte(cd.Data), nil
}

type User struct {
	ID   uint       `gorm:"primary_key"`
	Name string     `gorm:"size:255"`
	Data CustomData `gorm:"type:text"`
}

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

	// 自动迁移数据库表结构
	err = db.AutoMigrate(&User{})
	if err != nil {
		log.Fatal(err)
	}

	// 创建记录
	user := User{
		Name: "John Doe",
		Data: CustomData{Data: "Some data"},
	}
	err = db.Create(&user).Error
	if err != nil {
		log.Fatal(err)
	}

	// 查询记录
	var retrievedUser User
	err = db.First(&retrievedUser, user.ID).Error
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(retrievedUser)

	// 更新记录
	err = db.Model(&retrievedUser).Update("Data", CustomData{Data: "Updated data"}).Error
	if err != nil {
		log.Fatal(err)
	}

	// 再次查询记录
	var updatedUser User
	err = db.First(&updatedUser, user.ID).Error
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(updatedUser)

	// 删除记录
	err = db.Delete(&updatedUser).Error
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("操作完成")
}

运行结果:image-20230606112005350

在此示例中,我们定义了一个名为 CustomData 的自定义类型,并实现了 ScannerValuer 接口的方法。然后我们定义了一个 User 结构体,其中包含一个 Data 字段,类型为 CustomData。我们使用 Gorm 进行数据库操作,包括创建记录、查询记录、更新记录和删除记录。通过实现 ScannerValuer 接口,我们可以自定义 CustomData 类型的读写行为,并将其与 Gorm 一起使用。

在上述示例中,我们使用 “mysql” 方言和连接字符串来连接 MySQL 数据库。连接字符串的格式根据不同的数据库和驱动而有所不同,你需要替换示例中的 usernamepassworddatabase_name 为你实际的数据库连接信息。

1.10 事务的使用

在 GORM 中,可以使用 Begin 方法开始一个事务,然后使用 Commit 方法提交事务,或者使用 Rollback 方法回滚事务。下面是一个使用 GORM 进行事务操作的示例代码:

package main

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

type User struct {
	ID   uint   `gorm:"primary_key"`
	Name string `gorm:"size:255"`
	Age  int
}

func main() {
	dsn := "root:123456@tcp(127.0.0.1:3306)/database_name?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("Connected to the database")

	// 开始事务
	tx := db.Begin()

	// 创建记录(插入数据)
	err = CreateUser(tx, "倾心", 25)
	if err != nil {
		// 回滚事务
		tx.Rollback()
		log.Fatal(err)
	}

	// 查询记录
	user, err := GetUserByID(tx, 1)
	if err != nil {
		// 回滚事务
		tx.Rollback()
		log.Fatal(err)
	}
	fmt.Println(user)

	// 提交事务
	err = tx.Commit().Error
	if err != nil {
		log.Fatal(err)
	}

	// 查询所有数据
	users, err := GetAllUsers(db)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("所有用户:")
	for _, user := range users {
		fmt.Println(user)
	}
}

// CreateUser 创建用户
func CreateUser(db *gorm.DB, name string, age int) error {
	user := User{
		Name: name,
		Age:  age,
	}
	return db.Create(&user).Error
}

// GetUserByID 通过id查找用户
func GetUserByID(db *gorm.DB, id uint) (User, error) {
	var user User
	err := db.First(&user, id).Error
	return user, err
}

// GetAllUsers 查询所有用户
func GetAllUsers(db *gorm.DB) ([]User, error) {
	var users []User
	err := db.Find(&users).Error
	return users, err
}

运行结果:image-20230606111038562

在上面的示例中,我们使用 Begin 方法开始了一个事务,并将事务对象赋值给 tx 变量。然后,我们在事务中执行了插入数据的操作,并在查询数据时使用了事务。如果在事务执行过程中发生错误,我们调用 Rollback 方法回滚事务。如果一切正常,我们调用 Commit 方法提交事务。

事务可以确保一系列数据库操作要么全部成功提交,要么全部回滚。这样可以保持数据的一致性。在实际应用中,事务经常用于处理复杂的数据操作,确保数据的完整性和一致性。

如果本篇文章对你有用,欢迎点赞转发关注本 倾心全栈成长录 公众号,一起交流学习进步。相关的代码资料公众号发送关键字 gorm

;