目录
1.gorm介绍:
gorm 框架是国内的大神 jinzhu 基于 go 语言开源实现的一款数据库 orm 框架. 【gorm】一词恢弘大气,前缀 go 代表 go 语言, 后缀 orm 全称 Object Relation Mapping,指的是使用对象映射的方式,让使用方能够像操作本地对象实例一样轻松便捷地完成远端数据库的操作.orm框架的好处是提高开发效率
缺点是:
- 牺牲执行性能【中间多了一个环节】
- 牺牲灵活性
- 弱化SQL能力
gorm 框架开源地址为: https://github.com/go-gorm/gorm
gorm官网文档:GORM - The fantastic ORM library for Golang, aims to be developer friendly.
如果想了解一下框架原理,可以看一下知乎上学霸写的文章。
gorm 框架原理&源码解析:https://zhuanlan.zhihu.com/p/663707360
2.安装gorm
导入依赖
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql
注意:
- GORM 的开发已经迁移至 github.com/go-gorm,import 路径也修改为
gorm.io/gorm
,对于以前的项目,您可以继续使用github.com/jinzhu/gorm
- 数据库驱动被拆分为独立的项目,例如:github.com/go-gorm/mys…,且它的 import 路径也变更为
gorm.io/driver/mysql
连接数据库:
我以mysql为例子:
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func main() {
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
}
gorm操作数据库:
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// UserInfo 用户信息
type UserInfo struct {
ID uint
Name string
Gender string
Hobby string
}
func main() {
dsn := "root:@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("failed to connect database")
}
// 自动迁移
err = db.AutoMigrate(&UserInfo{})
if err != nil {
panic("failed to migrate")
}
u1 := UserInfo{1, "杨洋", "男", "篮球"}
// 创建记录
db.Create(&u1)
db.Create(&UserInfo{2, "王楚然", "女", "足球"})
// 查询
var u = new(UserInfo)
db.First(&u, 1) // 根据整型主键查找
fmt.Printf("%#v\n", u)
var uu UserInfo
db.First(&uu, "hobby=?", "足球") //查找 hobby 字段值为 足球 的记录
fmt.Printf("%#v\n", uu)
// 更新
db.Model(&u).Update("hobby", "排球")
//Update - 更新多个字段
db.Model(&u).Updates(UserInfo{Name: "肖战", Hobby: "跳舞"}) // 仅更新非零值字段
db.Model(&uu).Updates(map[string]interface{}{"Hobby": "演戏", "Name": "赵丽颖"})
// 删除
db.Delete(&u, 1)
}
在使用ORM工具时,通常我们需要在代码中定义模型(Models)与数据库中的数据表进行映射,在GORM中模型(Models)通常是正常定义的结构体、基本的go类型或它们的指针。 同时也支持sql.Scanner
及driver.Valuer
接口(interfaces)。
为了方便模型定义,GORM内置了一个gorm.Model
结构体。gorm.Model
是一个包含了ID
, CreatedAt
, UpdatedAt
, DeletedAt
四个字段的Golang结构体。
// gorm.Model 定义
type Model struct {
ID uint `gorm:"primary_key"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time
}
可以把这个默认的结构体引入到我们定义的结构体中
// 将 `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`字段注入到`User`模型中
type User struct {
gorm.Model
Name string
}
也可以自定义嵌套
type Author struct {
Name string
Email string
}
type Blog struct {
ID int
Author Author `gorm:"embedded"` //使用embedden关键字
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
Name string
Email string
Upvotes int32
}
还可以定义到每一个字段的长度,大小。索引等等
type User struct {
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:"-"` // 忽略本字段
}
GORM 倾向于约定优于配置 默认情况下,GORM 使用 ID
作为主键,使用结构体名的 蛇形复数
作为表名,字段名的 蛇形
作为列名,并使用 CreatedAt
、UpdatedAt
字段追踪创建、更新时间。
结构体标签定义的字段
GORM 默认会使用名为ID的字段作为表的主键。
// 根据 User 的字段创建 `deleted_users` 表 db.Table("deleted_users").AutoMigrate(&User{})
3.使用grom框架:
3.1新增
创建一条记录
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
type User struct {
ID uint
Name string
Age uint8
Birthday time.Time
}
func main() {
dsn := "root:@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("failed to connect database")
}
// 自动迁移
err = db.AutoMigrate(&User{})
if err != nil {
panic("failed to migrate")
}
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
fmt.Println(user.ID) // 返回插入数据的主键
fmt.Println(result.Error) // 返回 error
fmt.Println(result.RowsAffected) // 返回插入记录的条数
}
创建多条记录
users := []User{
{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
{Name: "Jackson", Age: 19, Birthday: time.Now()},
}
result := db.Create(users)
db.CreateInBatches(users, 100) //批量处理,一次处理一千条
fmt.Println(result.Error)
fmt.Println(result.RowsAffected)
创建记录并为指定的字段分配值:
db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18, "2020-07-04 11:05:21.775")
创建记录并忽略要省略的传递字段的值:
db.Omit("Name", "Age", "CreatedAt").Create(&user)
还可以根据map创建,开发中推荐使用结构体创建
db.Model(&User{}).Create(map[string]interface{}{
"Name": "jinzhu", "Age": 18,
})
// batch insert from `[]map[string]interface{}{}`
db.Model(&User{}).Create([]map[string]interface{}{
{"Name": "jinzhu_1", "Age": 18},
{"Name": "jinzhu_2", "Age": 20},
})
默认值问题:
通过tag定义字段的默认值,在创建记录时候生成的 SQL 语句会排除没有值或值为 零值 的字段。 在将记录插入到数据库后,Gorm会从数据库加载那些字段的默认值。
var user = User{Name: "", Age: 99} db.Create(&user)
上面代码实际执行的SQL语句是INSERT INTO users("age") values('99');
,排除了零值字段Name
,而在数据库中这一条数据会使用设置的默认值作为Name字段的值。
**注意:**所有字段的零值, 比如0
, ""
,false
或者其它零值
,都不会保存到数据库内,但会使用他们的默认值。 如果你想避免这种情况,可以考虑使用指针或实现 Scanner/Valuer
接口,比如:
// 使用指针
type User struct {
ID int64
Name *string `gorm:"default:galeone"`
Age int64
}
user := User{Name: new(string), Age: 18}
db.Create(&user) // 此时数据库中该条记录name字段的值就是''
3.2查询
3.2.1 一般查询
GORM 提供了 First
、Take
、Last
方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1
条件,且没有找到记录时,它会返回 ErrRecordNotFound
错误。
// 根据主键查询第一条记录
var user User
result := db.First(&user)
fmt.Println(result.RowsAffected) // 返回找到的记录数
fmt.Println(result.Error) // returns error or nil
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;
// 查询所有的记录
db.Find(&users)
SELECT * FROM users;
// 查询指定的某条记录(仅当主键为整型时可用)
db.First(&user, 10)
SELECT * FROM users WHERE id = 10;
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);
//主键为字符串
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a"
如果你想避免
ErrRecordNotFound
错误,你可以使用Find
,比如db.Limit(1).Find(&user)
,Find
方法可以接受struct和slice的数据。
对单个对象使用
Find
而不带limit,db.Find(&user)
将会查询整个表并且只返回第一个对象,这是性能不高并且不确定的。
3.2.2 Where查询
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
SELECT * FROM users WHERE name = 'jinzhu' limit 1;
// Get all matched records
db.Where("name = ?", "jinzhu").Find(&users)
SELECT * FROM users WHERE name = 'jinzhu';
// <>
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';
3.2.3 结构体或者map查询
// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;
// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
// 主键的切片
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
当通过结构体进行查询时,GORM将会只通过非零值字段查询,这意味着如果你的字段值为
0
,''
,false
或者其他零值
时,将不会被用于构建查询条件,若要在查询条件中包含零值,可以使用映射,该映射将包含所有键值作为查询条件
db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu";
db.Where(map[string]interface{}{"Name": "jinzhu", "Age": 0}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
在使用 struct 进行搜索时,可以通过传递相关的字段名或 dbname 到 Where ()来指定在查询条件中使用 struct 中的哪些特定值,例如:
db.Where(&User{Name: "jinzhu"}, "name", "Age").Find(&users)// SELECT * FROM users WHERE name = "jinzhu" AND age = 0;
db.Where(&User{Name: "jinzhu"}, "Age").Find(&users)// SELECT * FROM users WHERE age = 0;
Not和Where基本一致
内联条件
// 根据主键获取记录 (只适用于整形主键)
db.First(&user, 23)
// SELECT * FROM users WHERE id = 23 LIMIT 1;
// 根据主键获取记录, 如果它是一个非整形主键
db.First(&user, "id = ?", "string_primary_key")
// SELECT * FROM users WHERE id = 'string_primary_key' LIMIT 1;
// Plain SQL
db.Find(&user, "name = ?", "jinzhu")
// SELECT * FROM users WHERE name = "jinzhu";
db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20)
// SELECT * FROM users WHERE name <> "jinzhu" AND age > 20;
// Struct
db.Find(&users, User{Age: 20})
// SELECT * FROM users WHERE age = 20;
// Map
db.Find(&users, map[string]interface{}{"age": 20})
// SELECT * FROM users WHERE age = 20;
需要注意的是
GORM 允许扫描结果至
map[string]interface{}
或[]map[string]interface{}
,此时别忘了指定Model
或Table
result := map[string]interface{}{}
db.Model(&User{}).First(&result, "id = ?", 1)
var results []map[string]interface{}
db.Table("users").Find(&results)
3.2.4 高级查询
1.子查询
db.Where("amount > (?)", db.Table("orders").Select("AVG(amount)")).Find(&orders)
// SELECT * FROM "orders" WHERE amount > (SELECT AVG(amount) FROM "orders");
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 AVG(age) as avgage FROM `users` GROUP BY `name` HAVING AVG(age) > (SELECT AVG(age) FROM `users` WHERE name LIKE "name%")
2.选择查询
db.Select("name, age").Find(&users)
// SELECT name, age FROM users;
db.Select([]string{"name", "age"}).Find(&users)
// SELECT name, age FROM users;
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
// SELECT COALESCE(age,'42') FROM users;
type User struct {
ID uint
Name string
Age int
Gender string
// 假设后面还有几百个字段...
}
type APIUser struct {
ID uint
Name string
}
// 查询时会自动选择 `id`, `name` 字段
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10
3.排序和分页查询
db.Order("age desc, name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// 多字段排序
db.Order("age desc").Order("name").Find(&users)
// SELECT * FROM users ORDER BY age desc, name;
// 覆盖排序
db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
// SELECT * FROM users ORDER BY age desc; (users1)
// SELECT * FROM users ORDER BY age; (users2)
//分页查询
db.Limit(3).Find(&users)
// SELECT * FROM users LIMIT 3;
// Cancel limit condition with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
// SELECT * FROM users LIMIT 10; (users1)
// SELECT * FROM users; (users2)
db.Offset(3).Find(&users)
// SELECT * FROM users OFFSET 3;
db.Limit(10).Offset(5).Find(&users)
// SELECT * FROM users OFFSET 5 LIMIT 10;
// Cancel offset condition with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
// SELECT * FROM users OFFSET 10; (users1)
// SELECT * FROM users; (users2)
4.Count查询
必须位于链式编程的最后一位
db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count)
// SELECT * from USERS WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (users)
// SELECT count(*) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (count)
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
// SELECT count(*) FROM users WHERE name = 'jinzhu'; (count)
db.Table("deleted_users").Count(&count)
// SELECT count(*) FROM deleted_users;
db.Table("deleted_users").Select("count(distinct(name))").Count(&count)
// SELECT count( distinct(name) ) FROM deleted_users; (count)
5.其他查询,限制,分组,去重。连接等
//分组,限制
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
//去重
db.Distinct("name", "age").Order("name, age desc").Find(&results)
//连接查询
db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)
// multiple joins with parameter
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)
6.Pluck和Scan
//Pluck,查询 model 中的一个列作为切片,
var ages []int64
db.Find(&users).Pluck("age", &ages)
var names []string
db.Model(&User{}).Pluck("name", &names)
db.Table("deleted_users").Pluck("name", &names)
// 想查询多个字段? 这样做:
db.Select("name, age").Find(&users)
//Scan,扫描结果至一个 struct.
type Result struct {
Name string
Age int
}
var result Result
db.Table("users").Select("name, age").Where("name = ?", "Antonio").Scan(&result)
var results []Result
db.Table("users").Select("name, age").Where("id > ?", 0).Scan(&results)
// 原生 SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)
3.2.5 链式操作
// 创建一个查询
tx := db.Where("name = ?", "jinzhu")
// 添加更多条件
if someCondition {
tx = tx.Where("age = ?", 20)
} else {
tx = tx.Where("age = ?", 30)
}
if yetAnotherCondition {
tx = tx.Where("active = ?", 1)
}
tx.Find(&user)
//sql生成
SELECT * FROM users where name = 'jinzhu' AND age = 30 AND active = 1;
3.2.6 立即执行方法
立即执行方法:
Immediate methods ,立即执行方法是指那些会立即生成
SQL
语句并发送到数据库的方法, 他们一般是CRUD
方法,比如:
Create
,First
,Find
,Take
,Save
,UpdateXXX
,Delete
,Scan
,Row
,Rows
…
在 GORM 中使用多个立即执行方法时,后一个立即执行方法会复用前一个立即执行方法的条件 (不包括内联条件) 。
db.Where("name LIKE ?", "jinzhu%").Find(&users, "id IN (?)", []int{1, 2, 3}).Count(&count)
SELECT * FROM users WHERE name LIKE 'jinzhu%' AND id IN (1, 2, 3)
SELECT count(*) FROM users WHERE name LIKE 'jinzhu%'
3.2.7 Scopes(范围)
Scope是建立在链式操作的基础之上的。
基于它,你可以抽取一些通用逻辑,写出更多可重用的函数库。
func AmountGreaterThan1000(db *gorm.DB) *gorm.DB {
return db.Where("amount > ?", 1000)
}
func PaidWithCreditCard(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}
func PaidWithCod(db *gorm.DB) *gorm.DB {
return db.Where("pay_mode_sign = ?", "C")
}
func OrderStatus(status []string) func (db *gorm.DB) *gorm.DB {
return func (db *gorm.DB) *gorm.DB {
return db.Scopes(AmountGreaterThan1000).Where("status IN (?)", status)
}
}
db.Scopes(AmountGreaterThan1000, PaidWithCreditCard).Find(&orders)
// 查找所有金额大于 1000 的信用卡订单
db.Scopes(AmountGreaterThan1000, PaidWithCod).Find(&orders)
// 查找所有金额大于 1000 的 COD 订单
db.Scopes(AmountGreaterThan1000, OrderStatus([]string{"paid", "shipped"})).Find(&orders)
// 查找所有金额大于 1000 且已付款或者已发货的订单
3.3 更新
3.3.1 单个更新
dsn := "root:root@tcp(127.0.0.1:3306)/company?charset=utf8mb4&parseTime=True&loc=Local"
//var model.UserModel
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
//查询操作
var user UserInfo
db.First(&user)
user.Name = "测试小"
user.Gender = "男"
//全部更新,查出来所有字段赋值
db.Save(&user)
//更新单个值
db.Model(&user).Update("name", "张三")
3.3.2 批量更新
//更新多个属性,只会更新非零字段,如果要更新默认值,可以使用map更新
db.Model(&user).Updates(UserInfo{Name: "hello", Hobby: "羽毛球", Gender: "女"})
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
//更新多个属性
db.Model(&user).UpdateColumns(UserInfo{Name: "hello", Hobby: "篮球"})
//批量更新,没有指定主键
_ = db.Model(UserInfo{}).Updates(UserInfo{Name: "hello", Hobby: "女"}).RowsAffected
//通过gorm.Expr 更新
db.Model(&user).Update("name", gorm.Expr(" ?", "张三"))
3.4删除
警告 删除记录时,请确保主键字段有值,GORM 会通过主键去删除记录,如果主键为空,GORM 会删除该 model 的所有记录。
3.4.1 删除单个
// 删除现有记录
db.Delete(&email)
DELETE from emails where id=10;
db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;
db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;
db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);
// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";
3.4.2 批量删除
db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
// DELETE from emails where email LIKE "%jinzhu%";
db.Delete(Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";
var users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
db.Delete(&users)
// DELETE FROM users WHERE id IN (1,2,3);
db.Delete(&users, "name LIKE ?", "%jinzhu%")
// DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3);
3.4.3 软删除
如果一个 model 有 DeletedAt
字段,他将自动获得软删除的功能! 当调用 Delete
方法时, 记录不会真正的从数据库中被删除, 只会将DeletedAt
字段的值会被设置为当前时间
db.Delete(&user)
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
// 批量删除
db.Where("age = ?", 20).Delete(&User{})
// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
// 查询记录时会忽略被软删除的记录
db.Where("age = 20").Find(&user)
// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
// Unscoped 方法可以查询被软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;
如果你并不想嵌套gorm.Model
,你也可以像下方例子那样开启软删除特性:
type User struct {
ID int
Deleted gorm.DeletedAt
Name string
}
3.4.4 物理删除
// Unscoped 方法可以物理删除记录
db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;