ORM及GORM介绍
1. ORM
当谈论到数据库和应用程序之间的交互时,ORM(对象关系映射)是一个重要的概念。ORM 是一种编程技术,用于将对象模型和关系数据库之间建立映射。ORM 的主要目标是简化数据库操作和数据访问层的开发,使开发人员能够专注于业务逻辑而不是与数据库交互的细节。通过使用 ORM,开发人员可以使用类和对象来表示数据库中的表和记录,从而以面向对象的方式进行增删改查操作。
2. GORM
GORM 是 Go 语言中一个流行的 ORM 库,用于与关系型数据库进行交互。GORM 提供了许多便捷的功能,使得在 Go 语言中使用数据库变得更加容易。下面是 GORM 提供的一些主要功能:
- 数据模型定义:开发人员可以通过定义结构体来表示数据库中的表,并使用 GORM 提供的标签将结构体字段与数据库表的列进行映射。
- 数据库迁移:GORM 支持数据库迁移,即通过代码定义数据库表结构的变化,然后自动将这些变化应用到数据库中。
- CRUD 操作:GORM 提供了方便的方法来执行数据库的增删改查操作,如 Create、Read、Update 和 Delete 等。
- 关联关系:GORM 支持定义表之间的关联关系,如一对一、一对多和多对多等,并能够方便地进行关联查询。
- 钩子函数:GORM 允许开发人员定义在数据操作前后触发的钩子函数,用于处理特定的逻辑。
- 事务支持:GORM 支持事务操作,确保多个数据库操作要么全部成功,要么全部回滚。
- 查询构造器:GORM 提供了强大的查询构造器,允许开发人员使用链式调用来构建复杂的查询条件。
GORM安装
GORM 是 Go 语言中的一个第三方库,因此在使用之前,需要通过 Go 的包管理工具来安装 GORM 包。
go get -u gorm.io/gorm
在 Go 代码中,导入 GORM 包:
import "gorm.io/gorm"
GORM连接数据库(MySQL、SQLite)
1. MySQL
- 安装 MySQL 的驱动程序
go get -u gorm.io/driver/mysql
- 安装并启动 MySQL 数据库
确保已经安装了 MySQL 数据库,安装教程可参考其他文章
- 连接 MySQL 数据库
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"log"
)
func main() {
// 定义 MySQL 数据库连接字符串
dsn := "user:password@tcp(localhost:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
// 使用 gorm.Open() 连接 MySQL 数据库
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// 现在可以使用 db 变量进行数据库操作了
// 例如:db.AutoMigrate(&YourModel{})
}
在连接 MySQL 时,参数如下:
user: 数据库用户名,用于登录数据库的用户名。password: 数据库用户对应的密码,用于登录数据库的密码。tcp(localhost:3306): 数据库的网络地址和端口号。在这个例子中,使用的是 TCP 连接方式,MySQL 服务器在本地(localhost)的 3306 端口上监听连接。dbname: 数据库名称,要连接的具体数据库名。charset=utf8mb4: 字符集设置。在这里设置为 utf8mb4,用于支持更广泛的 Unicode 字符集,包括 emoji 表情等。parseTime=True: 这个参数用于告诉 GORM 在将数据库中的日期时间字段解析为 Go 中的时间类型时,是否启用自动解析功能。loc=Local: 设置时区。在这里设置为 Local,表示使用本地时区。
2. SQLite
- 安装 MySQL 的驱动程序
go get -u gorm.io/driver/sqlite
- 安装 SQLite 数据库
SQLite 是一个轻量级的嵌入式数据库,它不需要独立的安装过程,只需要在项目中引入相关的库,连接数据库时指定 SQLite 数据库文件路径即可。
- 连接 SQLite 数据库
package main
import (
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"log"
)
func main() {
// 定义 SQLite 数据库文件路径
dbPath := "my-database.db"
// 使用 gorm.Open() 连接 SQLite 数据库
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
// 现在可以使用 db 变量进行数据库操作了
// 例如:db.AutoMigrate(&YourModel{})
}
单表代码示例:GORM连接 SQLite 数据库,实现增删改查
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"time"
)
// User 是数据库表的结构体映射
type User struct {
ID uint `gorm:"primaryKey"`
Username string `gorm:"not null"`
Age int
Registered time.Time
}
func main() {
// 连接 SQLite 数据库
db, err := gorm.Open(sqlite.Open("my-database.db"), &gorm.Config{})
if err != nil {
panic("连接数据库失败")
}
// 自动创建表结构
err = db.AutoMigrate(&User{})
if err != nil {
panic("自动迁移表失败")
}
// 增加用户记录
user1 := User{Username: "user_name1", Age: 25, Registered: time.Now()}
user2 := User{Username: "user_name1", Age: 28, Registered: time.Now()}
user3 := User{Username: "user_name3", Age: 30, Registered: time.Now()}
user4 := User{Username: "user_name4", Age: 18, Registered: time.Now()}
db.Create(&user1)
db.Create(&user2)
db.Create(&user3)
db.Create(&user4)
//***************查询*******************
// 查询单个用户:名为 "user_name1" 记录
var user User
db.First(&user, "username = ?", "user_name1")
fmt.Println("User 1:", user)
// 条件查询:用户名为 "user_name1" 的记录
var usersWithCondition1 []User
db.Where("username = ?", "user_name1").Find(&usersWithCondition1)
fmt.Println("User with username user_name1:", usersWithCondition1)
// 条件查询: age 在 25 到 30 之间的用户记录中的 id 和 registered 字段
var usersWithCondition2 []User
db.Select("id, registered").Where("age BETWEEN ? AND ?", 25, 30).Find(&usersWithCondition2)
fmt.Println("Users with age between 25 and 30:", usersWithCondition2)
// 查询 age 在 20 到 30 之间的用户记录,并按id降序排序,只返回前两条记录
var usersWithCondition3 []User
db.Where("age BETWEEN ? AND ?", 20, 30).Order("id desc").Limit(2).Find(&usersWithCondition3)
fmt.Println("Users with age between 20 and 30, ordered by id desc, limit 2:", usersWithCondition3)
// 查询所有用户记录
var users []User
db.Find(&users)
fmt.Println("All users:", users)
//***************查询*******************
//***************更新*******************
// Updates更新用户名为 "user_name3" 的记录的 age 字段为 40
db.Model(&User{}).Where("username = ?", "user_name3").Updates(User{Age: 40})
// Save更新记录的 age 字段为 50,先查询用户名为 "user_name4" 的记录
var user_save User
db.First(&user_save, "username = ?", "user_name4")
user_save.Age = 50
db.Save(&user_save)
// 输出更新后所有用户记录
db.Find(&users)
fmt.Println("After Update, All users:", users)
//***************更新*******************
//***************删除*******************
// 删除 age 为 40 的记录
db.Where("age = ?", 40).Delete(&User{})
// 查询 age 为 28 的记录并删除
var users_delete []User
db.Where("age = ?", 28).Find(&users_delete)
db.Delete(&users_delete)
// 输出更新后所有用户记录
db.Find(&users)
fmt.Println("After Delete, All users:", users)
//***************删除*******************
}
运行结果:
标签:
gorm:"primaryKey":在数据库表中,主键是用于唯一标识每个记录的字段。在 GORM 中,通过在结构体字段上添加gorm:"primaryKey"标签,你可以指定该字段作为数据库表的主键。对于大多数情况下,定义主键是很有用的。如果你不指定主键,GORM 会默认使用名为 "ID" 的字段作为主键(如果存在的话)。但如果你的表结构中没有 "ID" 字段,那么你就需要通过gorm:"primaryKey"标签来显式指定主键。unique: 表示该字段在数据库表中是唯一的,即每条记录在此字段上的值必须是唯一的,不能重复。not null: 表示该字段在数据库表中不允许为空,即每条记录在此字段上必须有一个非空的值。autoIncrement: 定义字段为自增长。index: 定义字段为索引,可以提高查询效率。default: 定义字段的默认值。size:50: 定义字段的长度限制。column: 定义字段在数据库表中的列名。type: 定义字段的数据库类型。
其他:
- 在 GORM 中,如果你查询数据时没有选择某个字段,那么这个字段会保持为零值。
多表代码示例:
package main
import (
"fmt"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
)
// 定义用户表结构
type User struct {
ID uint `gorm:"primaryKey"`
Name string // 用户姓名
Age int // 年龄
RegistrationDate string // 注册日期
Orders []Order `gorm:"foreignKey:UserID"` // 关联到订单表
}
// 定义订单表结构
type Order struct {
ID uint `gorm:"primaryKey"`
UserID uint // 用户ID(关联到用户表的ID)
Price float64 // 订单价格
}
func main() {
// 连接数据库
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("连接数据库失败")
}
// 自动迁移模式,创建表
db.AutoMigrate(&User{}, &Order{})
// 添加一些测试数据
users := []User{
{ID: 1, Name: "Alice", Age: 25, RegistrationDate: "2023-08-02"},
{ID: 2, Name: "Bob", Age: 30, RegistrationDate: "2023-08-01"},
{ID: 3, Name: "Charlie", Age: 22, RegistrationDate: "2023-07-31"},
}
orders := []Order{
{ID: 1, UserID: 1, Price: 100},
{ID: 2, UserID: 1, Price: 150},
{ID: 3, UserID: 2, Price: 200},
{ID: 4, UserID: 3, Price: 50},
}
// 创建记录
db.Create(&users)
db.Create(&orders)
// 多表查询“用户年龄在20-30的用户ID、用户姓名、订单价格”
var results []struct {
UserID uint
UserName string
Price float64
}
// 使用GORM的原始SQL进行多表查询
// 选择users表的ID和name字段,以及orders表的price字段
// 在users表和orders表之间进行JOIN操作,使用users表的ID和orders表的user_id字段进行关联
// 使用WHERE子句筛选年龄在20到30之间的用户
db.Table("users").
Select("users.id as user_id, users.name as user_name, orders.price as price").
Joins("JOIN orders ON users.id = orders.user_id").
Where("users.age BETWEEN ? AND ?", 25, 30).
Scan(&results)
// 打印结果
for _, result := range results {
fmt.Printf("UserID: %d, UserName: %s, Price: %.2f\n", result.UserID, result.UserName, result.Price)
}
}
运行结果:
标签:
foreignKey:UserID表示Orders是User结构体中的一个切片字段,用于表示一个用户拥有多个订单。UserID是关联表的外键字段,它指明了Orders切片中的每个订单所属的用户。- 在 GORM 中,如果没有定义
foreignKey,默认情况下,GORM 会使用关联表的类型名称和主键字段名称来生成外键字段的名称。但通过明确指定foreignKey,你可以自定义外键字段的名称,使其更符合你的业务逻辑。
注:
db.Table("users"):使用Table方法指定要操作的数据库表为名为 "users" 的表。这表明接下来的查询操作将在 "users" 表上执行。.Select("users.id as user_id, users.name as user_name, orders.price as price"):使用Select方法选择我们想要获取的字段。这里我们将 "users" 表的 "id" 字段映射为 "user_id","name" 字段映射为 "user_name",并将 "orders" 表的 "price" 字段映射为 "price"。这样在查询结果中,字段名将按照我们指定的新名称返回。.Joins("JOIN orders ON users.id = orders.user_id"):使用Joins方法连接 "users" 表和 "orders" 表。这里我们通过 "users" 表的 "id" 字段与 "orders" 表的 "user_id" 字段进行关联(JOIN操作)。这样,我们可以根据用户的ID在两个表中进行匹配,将用户表和订单表关联在一起。.Where("users.age BETWEEN ? AND ?", 25, 30):使用Where方法添加查询条件,筛选出年龄在25到30岁之间的用户。这个条件会在查询结果中只返回符合条件的用户数据。.Scan(&results):执行查询并将结果扫描到指定的变量results中。results是一个切片,用于存储查询结果的结构体。
- 在GORM中,结构体字段的小写和大写有特定的含义。当字段名是小写字母开头时,表示该字段是私有字段(private field),仅在定义它的包内可见。而当字段名是大写字母开头时,表示该字段是公有字段(public field),可以被其他包访问。
- 在GORM的Select方法中,使用小写字段名是因为在进行数据库查询时,GORM使用的是反射机制来解析结构体的字段。在这个过程中,它只能访问到公有字段,无法访问到私有字段。因此,如果想在查询结果中包含某个字段,该字段必须是公有字段,以便GORM能够正确地找到和解析它。
- 在例子中,User结构体中的ID字段是大写字母开头的公有字段,所以GORM可以正确地访问和映射它。然而,我们在Select方法中使用了小写字段名"users.id",这是因为在数据库查询中,我们需要指定表名和字段名,而数据库表名和字段名一般都是小写的。所以,虽然在GORM的结构体定义中使用了大写字母开头的字段名,但在查询时,我们需要使用对应的小写字段名来指定表名和字段名,以确保查询操作能够正确地执行。
- 在GORM中,如果字段名是驼峰式命名(如UserID),则在进行多表查询时,会将其转换为下划线式命名(如user_id)来匹配数据库表的列名。这是因为在不同的命名风格之间进行映射可以简化数据库字段和Go结构体字段之间的转换。
db.Table("users")表示我们要在名为 "users" 的数据库表上执行操作。如果我们使用User结构体定义,GORM 默认情况下会将其映射到名为 "users" 的数据库表,因为 "User" 结构体的复数形式通常为 "users"。