前言
ORM全称Object Relational Mapping,是把编程语言中的Object/Struct数据类型映射到关系数据库中1张表,以下是详细映射关系。
结构体------------ 数据表 结构体实例---------- 表中1条记录 结构体字段------------ 结构体字段
gorm简介
面向github编程找一找Golang中比较流行的orm,
注意Django的orm是包含在Django web框架中的,而gorm有点像Python中的sqlalchemy它不限制你必须要在某个web框架中使用它。
gorm中文官方网站内含十分齐全的中文文档。
本文将围绕当前最近版本gorm v1.20.8展开。
go.mod
module golang-gorm go 1.14 require ( github.com/go-sql-driver/mysql v1.5.0 gorm.io/driver/mysql v1.0.3 gorm.io/gorm v1.20.8 )
gorm安装
SET GOPROXY=https://goproxy.cn
go get -u github.com/jinzhu/gorm
连接数据库
在golang中连接不同的数据就需要使用不同的driver驱动。
GORM 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server
"gorm.io/driver/mysql" //MySQL驱动
"gorm.io/driver/sqlite"//sqllite的驱动
"gorm.io/driver/postgres" //postgres的驱动
开始
package main import ( "fmt" "gorm.io/driver/mysql" //MySQL驱动 "gorm.io/gorm" "time" ) func init() { //配置数据连接:注意设置parseTime=true否则会报错!unsupported Scan 时间类型的字段 dbinfo := "zhanggen:xxoo@tcp(192.168.56.18:3306)/web?charset=utf8&parseTime=true&loc=Local" db, err := gorm.Open(mysql.Open(dbinfo), &gorm.Config{}) //配置一下数据连接参数! mySQL, err := db.DB() if err != nil { fmt.Println(err) } defer mySQL.Close() //设置最大空闲连接 mySQL.SetMaxIdleConns(10) //设置最大连接数 mySQL.SetMaxOpenConns(100) //设置连接超时时间:1分钟 mySQL.SetConnMaxLifetime(time.Minute) }
Model定义
我们在创建Table的时可以通过设置struct字段的tag,对数 表名、字段进行属性设置。
结构体
package book import "time" type Book struct { ID uint `gorm:"column:id;primaryKey"` Title string `gorm:"column:title;not null;type:varchar(100);unique_index"` //指定数据库列的数据类型 Author string `gorm:"column:author;not null;size:125"` //设置Author在数据库中不能为空,字段大小为255 Publishtime *time.Time `gorm:"column:publishtime"` } // 将默认表面Book 的表名设置为book func (Book) TableName() string { return "book" }
--------------------------------
package login //用户表 type UserInfo struct { ID uint `gorm:"column:id;primaryKey"` Username string `gorm:"column:username;unique;not null;index"` Password string `gorm:"column:password;not null;"` } // 将 UserInfos 的表名设置为 `user` func (UserInfo) TableName() string { return "user" }
Mysql
MariaDB [web]> desc book; +-------------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+---------------------+------+-----+---------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | title | varchar(100) | NO | | NULL | | | author | varchar(125) | NO | | NULL | | | publishtime | datetime(3) | YES | | NULL | | +-------------+---------------------+------+-----+---------+----------------+ 4 rows in set (0.00 sec)
-------------------------------------
MariaDB [web]> desc user; +----------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------+---------------------+------+-----+---------+----------------+ | id | bigint(20) unsigned | NO | PRI | NULL | auto_increment | | username | varchar(191) | NO | UNI | NULL | | | password | longtext | NO | | NULL | | +----------+---------------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec)
Django orm的差别
当我们Django的model中修改了表字段需要先make magrations生成操作,然后再make magirate去数据库真正执行这次magration。保证了数据库的安全。
而gorm只要你执行db.AutoMigrate()就会实时修改表结构,但是当你使用Django orm 修改了数据库字段magration时经常报错Exist table让你不知所措。
查看gorm操作日志
db.Debug可以帮助我们显示gorm对数据库的操作
db.Debug().AutoMigrate(&User{})
日志内容
[1.000ms] [rows:0] ALTER TABLE `users` MODIFY COLUMN `age` bigint DEFAULT 19
增删改查CURD
立即执行方法(Immediate Methods):查询只有执行了立即执行函数才会去数据库查询 First/Find/FirstrOrInit/FirstOrCreate/
内联条件 inline condition:就是把SQL查询条件当做参数传递到立即执行函数中执行。
gorm支持使用String、Struct&Map作为条件进行查询已经链样操作。
增
创建单条记录
/1.创建单条记录 u := User{Name: "Martin", Age: 19} result := Model.Create(&u) //返回插入数据的主键 fmt.Println(u.ID) //返回的error fmt.Println(result.Error) //返回插入记录的条数 fmt.Println(result.RowsAffected) //如果记录存在将不再重复创建 result = Model.FirstOrCreate(&u) if result.RowsAffected == 0 { fmt.Println("记录已经存在!") }
批量创建
在生产环境之中如果你插入数据特别频繁,可以使用批量插入的方式进行优化。如果数据没有实时展示的要求。
//批量创建 var userList = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}} Model.Debug().Create(&userList) for _, user := range userList { fmt.Println(user.ID) // 1,2,3 }
查询
new和make区别:make给map/channel/slince申请内存和容量返回值类型,new初始化基本类型string/int/结构体返回指针类型的变量
主键查询
Objects can be retrieved using primary key by using Inline Conditions.
Be extra careful with strings to avoid SQL Injection, check out Security section for details
通过内联条件的方式,我们可以根据主键获取到数据库中的1/多条记录。
db.First(&user, 10) // SELECT * FROM users WHERE id = 10; 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);
一般查询
//1.普通查询 u := new(User) //查询第1条数据 Model.Debug().First(u) //SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1 fmt.Println(u.Name) Model.Debug().First(u, 10) fmt.Println(u) //随机获取1条记录 u = new(User) //new和make区别:make给map/channel/slince申请内存和容量返回值类型,new初始化基本类型string/int/结构体返回指针类型的变量 Model.Debug().Take(u) //SELECT * FROM `users` LIMIT 1 fmt.Println(u.Name) //获取最后1条记录 u = new(User) Model.Last(u) fmt.Println(u.Name) //查询表中所有记录 userList := []User{} //SELECT * FROM `users` Model.Debug().Find(&userList) fmt.Println(userList)
Where 条件查询
//查询所有匹配的记录 然后获取第1条 u=new(User) Model.Debug().Where("name=?","jinzhu1").First(&u)//SELECT * FROM `users` WHERE name='jinzhu1' ORDER BY `users`.`id` LIMIT 1 fmt.Println(u.Name) //查询所有匹配的记录,获取所有 userList=[]User{} Model.Debug().Where("name=?","Martin").Find(&userList) //SELECT * FROM `users` WHERE name='Martin' fmt.Println(userList) userList=[]User{} Model.Debug().Where("name <>?","Martin").Find(&userList) //SELECT * FROM `users` WHERE name <>'Martin' fmt.Println(userList) userList=[]User{} Model.Debug().Where("name in?",[]string{"jinzhu"}).Find(&userList) //SELECT * FROM `users` WHERE name in('jinzhu') fmt.Println(userList)
使用Struct & Map & 切片进行查询
我们可使用结构体、map、slice把搜索条件组合起来。
//struct u = new(User) Model.Debug().Where(User{Name: "曹操", Age: 29}).First(&u) fmt.Println(u.Name) userList=[]User{} //map:我们可以使用map接收前端的json然后做组合搜索 Model.Debug().Where(map[string]interface{}{"name": "希拉里", "age": 29}).Find(&userList) //SELECT * FROM `users` WHERE `users`.`name` = '曹操' AND `users`.`age` = 29 ORDER BY `users`.`id` LIMIT 1 fmt.Println(userList) //主键的切片 userList=[]User{} Model.Where([]int64{1,2,4,6}).Find(&userList) fmt.Println(userList)
Not 条件查询
我们可以使用Where指定筛选条件,也可以使用Not排除一些条件。
//not条件查询 u = new(User) userList = []User{} //1.不等于 <> //SELECT * FROM `users` WHERE `name` <> 'jinzhu' ORDER BY `users`.`id` Model.Debug().Not("name", "jinzhu").First(&u) fmt.Println(u.Name) //SELECT * FROM `users` WHERE `name` <> 'Martin' Model.Debug().Not("name", "Martin").Find(&userList) fmt.Println(userList) //2.不在x范围之类 Not In userList = []User{} //SELECT * FROM `users` WHERE `users`.`id` NOT IN (1,4) Model.Debug().Not([]int64{1, 4}).Find(&userList) fmt.Println(userList) //SELECT * FROM `users` userList = []User{} Model.Debug().Not([]int64{}).Find(&userList) fmt.Println(userList) //4.执行原生Sql userList = []User{} Model.Debug().Not("name=? or age<10", "Martin").Find(&userList) fmt.Println(userList) //5.not和where一样同样支持 通过struct/map/切片来组装搜索条件 userList = []User{} Model.Debug().Not(User{Name: "Martin", Age: 19}).Find(&userList) fmt.Println(userList) userList = []User{} //SELECT * FROM `users` WHERE (`age` <> 19 AND `name` <> 'jinzhu') Model.Debug().Not(map[string]interface{}{"name": "jinzhu", "age": 19}).Find(&userList) fmt.Println(userList) userList = []User{} Model.Debug().Not([]int64{1, 2}).Find(&userList) //SELECT * FROM `users` WHERE `users`.`id` NOT IN (1,2) fmt.Println(userList)
Or条件查询
我可以使用Or连接2个搜索条件。
//Or条件 userList = []User{} Model.Where("name=?", "Martin").Or("name=?", "jinzhu").Find(&userList) fmt.Println(userList) //Or也支持Struct、map、slice进行条件组成搜索 userList = []User{} //SELECT * FROM `users` WHERE `users`.`name` = 'Martin' AND `users`.`age` = 29 OR (`users`.`name` = 'jinzhu' AND `users`.`age` = 29) Model.Debug().Where(User{Name: "Martin", Age: 29}).Or(User{Name: "jinzhu", Age: 29}).Find(&userList) fmt.Println(userList) //SELECT * FROM `users` WHERE `users`.`id` IN (1,2) OR `users`.`id` IN (5,4) Model.Debug().Where([]int64{1, 2}).Or([]int64{5,4}).Find(&userList) fmt.Println(userList)
内联条件查询
通过以上大量的实验你会发现我总会在Where/Not/Or里面组织搜索条件,然后再 .First/Find。
这是Where/Not/Or中的SQL需要借助立即执行方法 才真正把SQL发送到mysqld(服务端)执行,然后返回值,我们声明的变量才会获取到值。
您会发现.Find()和.First()t这些立即执行方法可以接收2个参数(dest interface{}, conds ...interface{})
第1个参数用于接收和保存服务端(mysqld)返回的结果、第2个用于客户端(MySQL)输入SQL搜索条件。
什么是内联条件查询呢?
就是我们在把接收查询结果的变量、SQL查询条件传递到.Find()/.First()/.Not()/.Or()这些立即执行函数中执行就属于内联条件查询。
//内联条件 //1.根据主键获取记录 (只适用于整形主键) u1 := new(User) Model.Debug().First(&u1, 1) fmt.Println(u1.Age) u1 = new(User) Model.Debug().First(&u1, "id = ?", 1) fmt.Println(u1.Name) //2.根据搜索条件获取多条数据 users := []User{} Model.Debug().Find(&users, "name = ?", "jinzhu") fmt.Println(users) //3.使用结构体作为搜索参数 users = []User{} Model.Debug().Find(&users, User{Age: 29}) fmt.Println(users) //4.使用map组织搜索条件 users = []User{} Model.Debug().Find(&users, map[string]interface{}{"age": "29"}) fmt.Println(users) //5.使用slice作为搜索条件 users=[]User{} Model.Debug().Find(&users,[]int64{1,6}) fmt.Println(users)
设置查询选项
GORM 提供了 Set
, Get
, InstanceSet
, InstanceGet
方法来允许用户传值给 勾子 或其他方法
Gorm 中有一些特性用到了这种机制,如迁移表格时传递表格选项。
err = db.Debug().Set("gorm:table_options","ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1").AutoMigrate(&User{}) if err != nil { fmt.Println("创建表失败",err) } fmt.Println("---------------------","创建表成功")
我们在查询 SQL是可以设置添加1个行锁(在我查询这条记录的时候别人就无法修改这条数据了)
users=[]User{} Model.Debug().Set("gorm:query_option","FOR UPDATE").Find(&users,[]int64{1,5}) fmt.Println("--------",users) u1=new(User) Model.Set("gorm:query_option","FOR UPDATE").Debug().First(&u1,10) fmt.Println(u1.Name)
FirstOrInit查询
gorm的核心功能就是把数据库返回的1条记录转换成golang中的结构体并对字段进行赋值。
如果在某些情况下我的SQL查询没有返回任何记录,那我的stuct变量的全部字段就会使用零值。
可以使用 Attrs和Assign方法对 FirstOrInit返回的空记录 进行struct字段进行赋值。
attrs:获取匹配的第1条记录,否则根据给定的条件初始化一个新的对象 (仅支持 struct 和 map 条件)
Assign:不管记录是否找到,都将参数赋值给 struct变量。
//FirstOrint //如果找到了就把数据库返回的结果赋值给u2,如果没有找到也不要紧把查询条件赋值给u2进行初始化。 u2:=new(User) Model.FirstOrInit(u2,User{Name: "Martin"}) fmt.Println(u2.Name) //在数据库没有返回查询结果的情况下:使用Attrs指定Init时字段的值 u2=new(User) Model.Attrs(User{Age:29} ).FirstOrInit(u2,User{Name: "Bob"}) fmt.Println(u2.Name,u2.Age,u2.ID) //Assigin:不管数据库有没有返回查询结果,在会初始化指定的字段 u2=new(User) Model.Debug().Assign(User{Age: 21}).FirstOrInit(u2,User{Name:"抱墙"}) fmt.Println(u2.Name,u2.Age)
FirstOrCreate插入数据
Attrs和Assign不仅可以对声明的结构体变量进行初始化,还可以指定FirstOrCreate将要创建的记录。
FirstOrCreate:获取匹配的第1条记录, 否则根据给定的条件创建一个新的记录 (仅支持 struct 和 map 条件)
Attrs:如果记录未找到,将使用attr中设置的参数创建 struct 和新记录.
Assign:不管记录是否找到,都将参数赋值给 struct 并保存至数据库.
//FirstOrCreate u3 := new(User) //如果SQL条件匹配到了记录就返回原有记录赋值给结构体变量。 Model.Debug().FirstOrCreate(u3, User{Name: "嬴政"}) fmt.Println(u3.Age, u3.ID) //如果SQl查询没有匹配到记录就 insert 1条新记录,返回新记录赋值给结构体变量。 u3 = new(User) Model.Debug().Where(User{Name: "嬴胡亥"}).FirstOrCreate(u3) fmt.Println(u3.ID) //Attrs //如果SQL匹配到记录,返回原记录并赋值给结构体变量。(不使用Attrs(User{Age: 20})中设置的参数) u3 = new(User) Model.Debug().Where(User{Name: "嬴胡亥"}).Attrs(User{Age: 20}).FirstOrCreate(u3) fmt.Println(u3.Name, u3.Age) //如果SQL没有匹配到记录,就使用Where(User{Name: "匹配不到嬴胡亥用户"})和.Attrs(User{Age: 20})中的参数, insert 1条新记录,返回新记录赋值给结构体变量。 u3 = new(User) Model.Debug().Where(User{Name: "匹配不到嬴胡亥用户"}).Attrs(User{Age: 2000}).FirstOrCreate(u3) fmt.Println(u3.Name, u3.ID) //Assign //SQL匹配到记录就更新原记录(update not insert new record) u3 = new(User) Model.Debug().Where(User{Name: "嬴胡亥"}).Attrs(User{Age: 20}).FirstOrCreate(u3) fmt.Println(u3.Name, u3.Age) //没有匹配到就使用Where(User{Name: "匹配不到嬴胡亥用户"})和.Attrs(User{Age: 20})中的参数, insert new record。 u3 = new(User) Model.Debug().Where(User{Name: "我不存在"}).Assign(User{Age: 90}).FirstOrCreate(u3) fmt.Println(u3.Name, u3.Age)
子查询
db.Where("amount > ?", db.Table("orders").Select("AVG(amount)").Where("state = ?", "paid").SubQuery()).Find(&orders) // SELECT * FROM "orders" WHERE "orders"."deleted_at" IS NULL AND (amount > (SELECT AVG(amount) FROM "orders" WHERE (state = 'paid')));
Select选择字段
Select,指定你想从数据库中检索出的字段,默认会选择全部字段。
//Select,指定你想从数据库中检索出的字段,默认会选择全部字段。 users := []User{} //SELECT id,name FROM `users` Model.Debug().Select("id,name").Find(&users) fmt.Println(users) users = []User{} //SELECT `name`,`age` FROM `users` Model.Debug().Select([]string{"name","age"}).Find(&users) fmt.Println(users) users=[]User{} Model.Debug().Table("users").Select("COALESCE(age,?)", 200) fmt.Println(users)
排序查询
我们可以通过Order api对查询的数据进行排序
userList:=[]User{} //SELECT * FROM `users` ORDER BY age desc, name Model.Debug().Order("age desc, name").Find(&userList) fmt.Println(userList) // 多字段排序 userList=[]User{} Model.Debug().Select("id,age").Order("id desc").Order("age").Find(&userList) fmt.Println(userList)
Limit & Offset
分页查询:Limit
specify the max number of records to retrieve Offset
specify the number of records to skip before starting to return the records
//limit and offset //返回3条数据 userList:=[]User{} Model.Limit(3).Find(&userList) fmt.Println(userList) // 通过 -1 消除 Limit 条件 Model.Limit(3).Find(&userList).Limit(-1).Find(&userList) fmt.Println(userList) //移动到ID=2的记录获取2条,分页就是这样搞的! Model.Debug().Offset(2).Limit(2).Find(&userList) fmt.Println(userList)
count获取查询总量
Count,该 model 能获取的记录总数。Count
必须是链式查询的最后一个操作 ,因为它会覆盖前面的 SELECT
,但如果里面使用了 count
时不会覆盖
count1:=new(int64) Model.Debug().Find(&userList).Count(count1) fmt.Println(*count1)
Group&Having
分组和聚合
//group by: SELECT name,sum(age) as total FROM `users` GROUP BY `name` type groupByage struct { Age int Total int } groupList :=[]groupByage{} Model.Debug().Model(&User{}).Select("age,count(name) as total").Group("age").Find(&groupList) fmt.Printf("%#v\n", groupList) //haveing可以对group by 之后的结果,进行进一步筛选。 //SELECT name,sum(age) as total FROM `users` WHERE name LIKE '刘%' GROUP BY `name` HAVING total >= 50 type groupByname struct { Name string Total int } groupList1:=[]groupByname{} Model.Debug().Model(&User{}).Select("name,sum(age) as total").Where("name LIKE ?", "刘%").Group("name").Having("total >= ?",50).Find(&groupList1) fmt.Println(groupList1)
join连表
type result struct { Name string Email string } db.Model(&User{}).Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&result{}) // SELECT users.name, emails.email FROM `users` left join emails on emails.user_id = users.id 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) // 带参数的多表连接 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)
Pluck
Query single column from database and scan into a slice, if you want to query multiple columns, use Select
with Scan
instead
Pluck可以帮助我们获取到table中的某1列数据并赋值给slice。
//pluck获取age列做位切片返回 var ageList []int64 Model.Debug().Model(&User{}).Pluck("age",&ageList) fmt.Println(ageList) nameList:=new([]string) Model.Debug().Model(&User{}).Pluck("name",&nameList) fmt.Println(nameList)
Scan
Scan results into a struct work similar to Find
Scan的功能类似于Find,可以把从数据库中查询到的结果映射到struct中。
//scan的用法 type scanResult struct { Name string Age int64 } //类似于First()的功能 scanResultObj := new(scanResult) Model.Table("users").Select("name,age").Where("name = ?", "嬴政").Scan(scanResultObj) fmt.Println(scanResultObj) //类似于Find()功能 scanResultList := new([]scanResult) Model.Table("users").Select("name,age").Where("name like ?", "刘%").Scan(scanResultList) fmt.Println(scanResultList)
Scope查询
Scopes allow you to re-use commonly logic, the shared logic needs to defined as type func(*gorm.DB) *gorm.DB
Scopes可以帮助我们把常用的查询逻辑封装到1个函数里面,后期使用查询时Scope这个函数。
//名字不为空 func NameNotNull(db *gorm.DB) *gorm.DB { return db.Where("name is not null") } //查询年龄>=18岁的用户 func AgeLe18(db *gorm.DB) *gorm.DB { return db.Where("age >= ?", 18) } //分页查询 func PageData(start, count int) func(db *gorm.DB) *gorm.DB { return func(db *gorm.DB) *gorm.DB { return db.Offset(start).Limit(count) } } //scope //查询姓名不为空 年龄>=18 userList := new([]User) Model.Scopes(NameNotNull, AgeLe18).Find(userList) fmt.Println(userList) //分页查询 Model.Scopes(PageData(5,10)).Find(userList) fmt.Println(userList)
链式操作相关
Django的Q查询可以做组合查询,gorm中的链式操作也可以。
var userlist []User contionChain:=Model.Debug().Not("name is null") contionChain.Where("name like ?","刘%") contionChain.Where("age >?",10) contionChain.Find(&userlist) fmt.Println(userlist)
更新 update
Save更新所有字段
var userObj User Model.First(&userObj) fmt.Println(userObj) //save更新: 默认会修改所有字段 userObj.Age=90 //UPDATE `users` SET `id`=1,`created_at`='2020-12-24 05:29:33.978',`updated_at`='2020-12-24 13:35:08.695',`deleted_at`=NULL,`name`='特朗普',`age`=90,`active`=true WHERE `id` = 1 Model.Debug().Save(userObj)
Update更新1个字段
Model(&userObj)我在Model中能传了1个userObj,gorm会根据userObj中的id字段去数据库where需要更新的记录。
//update:更新指定的字段 //根据结构体更新 1个字段 Model.Debug().Model(&userObj).Update("name","唐纳德.特朗普") //根据给定的条件更新 1个字段 Model.Debug().Model(&userObj).Where("name = ?","唐纳德.特朗普").Update("age",10)
Updates同时更新多个字段
我们可以给update方法传1个map,同时更新多个字段。
//更新多个字段 使用struct m1:=map[string]interface{}{ "name":"唐纳德.特朗普", "age":20, } //UPDATE `users` SET `age`=20,`name`='唐纳德.特朗普',`updated_at`='2020-12-24 13:50:07.519' WHERE `id` = 1 //注意:Model(&userObj)我在Model中能传来1个userObj,gorm会根据userObj中的id字段去where需要更新的值 Model.Debug().Model(&userObj).Updates(m1)
Updates更新指定的字段
有时我们接收到的map可能会包含一些我们不需要更新的字段,那么如何指定有效字段、排除无效字段呢?
//updates:更新指定字段 //指定age字段进行更新 Model.Debug().Model(&userObj).Select("age").Updates(map[string]interface{}{"name":"唐纳德.-特朗普","age":9000,"active":false}) //排除age字段进行更新 Model.Debug().Model(&userObj).Omit("age").Updates(map[string]interface{}{"name":"唐纳德.-特朗普","age":9000,"active":false})
UpdateColumns
UpdateColumns也叫无Hooks更新。
上面的更新操作会自动运行 model 的 BeforeUpdate
, AfterUpdate
方法。
更新 UpdatedAt
时间戳, 在更新时保存其 Associations
, 如果你不想调用这些方法,你可以使用 UpdateColumn
, UpdateColumns就不会更新
UpdatedAt字段。
//UpdateColumn Model.Debug().Model(&userObj).Update("name", "唐纳德.特朗普") Model.Debug().Model(&userObj).UpdateColumns(map[string]interface{}{ "name": "唐纳德.-特朗普", "age": 200, }) Model.Debug().Model(&userObj).UpdateColumns(User{Name:"唐纳德.特朗普",Active: true}) defer connectionPool.Close()
批量更新多条记录
注意在批量更新的时候不会更新 UpdatedAt
字段。
//批量更新 AffectedRows:=Model.Debug().Table("users").Where("id in (?)", []int{1, 2}).Updates(map[string]interface{}{ "age": 21, }).RowsAffected fmt.Println("更新的行数",AffectedRows)
全表更新
我想要1张表中的某个字段全部更新怎么办?还记得Django中的F在原数据的基础上+1?
//全局更新 //错误的全局更新方式,之前是可以的!! err := Model.Model(&User{}).Update("name", "二狗").Error // 错误的写法!!gorm.ErrMissingWhereClause fmt.Println(err) //全局更新方式1 Model.Debug().Exec("UPDATE users SET age = ?", "18") //让user表中用户年龄在原来的基础上+2,Expr 子查询 Model.Debug().Session(&gorm.Session{AllowGlobalUpdate: true}).Model(User{}).Update("age", gorm.Expr("age+ ?", 100))
删除delete
最后就是删除了~
删除1条记录
我跟可以根据对象删除一条记录,注意必须指定对象的主键否则会触发 批量 Delete,也可以根据主键直接删除1条记录。
//根据对象删除:删除对象需要指定主键,否则会触发 批量 Delete u:=new(User) u.ID=1 Model.Debug().Delete(u) //根据主键删除 Model.Debug().Delete(&User{},10) //根据主键删除多个 Model.Debug().Delete(&User{},[]int{4,11})
批量删除
gorm中都是软删除。
//2.批量删除 deletedUsers:=[]User{} Model.Debug().Where("name like ?","唐纳德%").Delete(User{}) Model.Debug().Delete(deletedUsers,"name like ?","嬴%") fmt.Println(deletedUsers)
全表删除
清空表中的内容
//全局删除 //1.错误的方式 err:=Model.Delete(&User{}).Error fmt.Println(err) //正确方式 Model.Where("1 = 1").Delete(&User{}) Model.Exec("truncate table users") Model.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
表关联
在关系型数据里表和表之间的关系:1对1(主键+外键联合唯一)、1对多(外键)、多对多(中间增加1张记录2个之间相互1对多关系的表),外键总在多的一方设置。
平时我们讨论表关系时有人会抛出 belongs to(属于)、多对1、1对多这些概念,此时你千万别懵逼。其实反映到数据库里还是外键。
主表:在数据库中建立的表格即Table,其中存在主键(primary key)用于与其它表相关联,并且作为在主表中的唯一性标识。
从表:以主表的主键(primary key)值为外键 (Foreign Key)的表;从表可以通过外键与主表进行关联查询,从表通过外键于主表进行关联查询
可能你理解的主和从表概念和综上所述是相反的,没关系这不影响技术爱好者之间的学习、交流,本文采用百度百科的定义的概念:外键在哪个表,那个表就是从表。
以上2张表存在外键关联:
一对多:从球队角度来说一个球队拥有多个球员 即为一对多
多对一:从球员角度来说多个球员属于一个球队 即为多对一数据表间一对多关系如下图:
无论是1对多,还是多对1,本质在数据库里的体现还是1个外键。
外键连表查询
以上我们上面大量篇幅说主表从表,其实就是2个表存在外键关系。
根据自身业务需求设计外键就好了,我一般称在从表使用外键查询就属于正向连表,否则就是反向连表查询。
一个外键引发了太多没有意义的思考,好吧开始定义model和外键吧。
使用默认外键字段名称
type Role struct { gorm.Model Name string `gorm:"not null"` } type User struct { gorm.Model Name string `gorm:"not null"` //RoleID:不是可以随便写的,指定User表和Role表名的ID是字段建立外键。 RoleID string `gorm:"not null"` Role Role }
自定义外键名称
以上我们通过默认的外键:使用拥有者(Role)的struct名+上主字段名做外键关联到了主表。也可以在从表自定义外键的名称。
type Role struct { gorm.Model Name string `gorm:"not null"` } type User struct { gorm.Model Name string `gorm:"not null"` //外键字段名称叫role_id不好听,我要叫role_refer RoleRefer string `gorm:"not null"` Role Role `gorm:"foreignKey:RoleRefer"` }
自定义外键字段
我从表一定要外键到主表ID这个列?我想外键到其他列!
type Role struct { gorm.Model Name string `gorm:"not null"` } type User struct { gorm.Model Name string `gorm:"not null"` //从表外键字段名称叫role_id不好听,我要叫role_refer RoleRefer string `gorm:"not null"` //我想要外键到主表的Name字段 Role Role `gorm:"foreignKey:RoleRefer;AssociationForeignKey:Name"` }
最终定义1个模型
type Role struct { gorm.Model Name string `gorm:"not null"` //反向连表查询用到Association(Users) Users []User } type User struct { gorm.Model Name string `gorm:"not null"` //RoleID:与Role表名的ID是字段建立外键。正向连表查询 RoleID string `gorm:"not null"` Role Role }
正向和反向连表查询
//查询id=1的用户是什么角色? var user1 User //正向连表Preload Model.Preload("Role","id is not null").First(&user1,"id =?",2) fmt.Println("---",user1.Role) //在主表查询id=2的角色被多少用户使用? var role Role Model.First(&role,"id=?",2) fmt.Println(role.Name) //反向连表 Model.Model(&role).Association("Users").Find(&role.Users) fmt.Println(role.Users)
多对多查询
我现在想把用户和角色变成多对多的关系。1个用户可以有多个角色,1个角色也可以被多个用户使用。这就不需要外键了,现在得使用1张中间关系表。
type Role struct { gorm.Model Name string `gorm:"not null"` Users []User `gorm:"many2many:user_roles;"` }
type User struct { gorm.Model Name string `gorm:"not null"` Roles []Role `gorm:"many2many:user_roles;"` }
多对多查询最简单了
var user1 User Model.Where("id = ?", "1").First(&user1) //查询一下当前用户都有那些角色 Model.Model(&user1).Association("Roles").Find(&user1.Roles) fmt.Println(user1.Roles) var role1 Role Model.Where("id = ?", "2").First(&role1) //查询一下当前角色都有那些用户在使用 Model.Model(&role1).Association("Users").Find(&role1.Users) fmt.Println(role1.Users)