[go-每日一库]golang-gorm实现增删改查CRUD(三)
原文链接:
1.(四)GORM插入数据 https://www.cnblogs.com/infodriven/p/16351285.html
2.(五)GORM查询数据 https://www.cnblogs.com/infodriven/p/16351370.html
3.(六)GORM更新数据 https://www.cnblogs.com/infodriven/p/16351548.html
4.(七)GORM删除数据 https://www.cnblogs.com/infodriven/p/16351552.html
1.gorm的新增记录
1.1 插入数据
//定义一个用户,并初始化数据 u := User{ Username:"tizi365", Password:"123456", CreateTime:time.Now().Unix(), } //插入一条用户数据 //下面代码会自动生成SQL语句:INSERT INTO `users` (`username`,`password`,`createtime`) VALUES ('tizi365','123456','1540824823') db.Create(&u) //一般项目中我们会类似下面的写法,通过Error对象检测,插入数据有没有成功,如果没有错误那就是数据写入成功了。 if err := db.Create(&u).Error; err != nil { fmt.Println("插入失败", err) return }
1.2 gorm获取新插入记录的自增Id
gorm 2.0版本以后的,默认会自动返回主键Id值。
实例代码:
//定义一个用户,并初始化数据 u := User{...忽略初始化代码...} //插入记录 db.Create(&u) u.ID // 返回主键id,默认主键名为ID,也可以通过gorm标签定义,请参考前面的模型定义章节 u.Error // 返回 error u.RowsAffected // 返回插入记录的条数
提示:如果gorm设置了数据库连接池,那么每次执行数据库查询的时候都会从数据库连接池申请一个数据库连接,那么上述代码必须使用数据库事务,确保插入数据和查询自增id两条sql语句是在同一个数据库连接下执行,否则在高并发场景下,可能会查询不到自增id,或者查询到错误的id。
2.gorm查询数据
gorm查询数据本质上就是提供一组函数,帮我们快速拼接sql语句,尽量减少编写sql语句的工作量。
gorm查询结果我们一般都是保存到结构体(struct)变量,所以在执行查询操作之前需要根据自己想要查询的数据定义结构体类型。
提示:gorm库是协程安全的,gorm提供的函数可以并发的在多个协程安全的执行。
下面是教程用到的foods表结构定义:
CREATE TABLE `foods` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '商品id', `title` varchar(100) NOT NULL COMMENT '商品名', `price` float DEFAULT '0' COMMENT '商品价格', `stock` int(11) DEFAULT '0' COMMENT '商品库存', `type` int(11) DEFAULT '0' COMMENT '商品类型', `create_time` datetime NOT NULL COMMENT '商品创建时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
下面是foods表对应的golang结构体类型
//商品 type Food struct { Id int Title string Price float32 Stock int Type int //mysql datetime, date类型字段,可以和golang time.Time类型绑定, 详细说明请参考:gorm连接数据库章节。 CreateTime time.Time } //为Food绑定表名 func (v Food) TableName() string { return "foods" }
2.1 gorm链式操作函数查询数据
gorm查询主要由以下几个部分的函数组成,这些函数可以串起来组合sql语句,使用起来类似编写sql语句的习惯。
query
执行查询的函数,gorm提供下面几个查询函数:
- Take
查询一条记录 例子: //定义接收查询结果的结构体变量 food := Food{} //等价于:SELECT * FROM `foods` LIMIT 1 db.Take(&food)
- First
查询一条记录,根据主键ID排序(正序),返回第一条记录 例子: //等价于:SELECT * FROM `foods` ORDER BY `foods`.`id` ASC LIMIT 1 db.First(&food)
- Last
查询一条记录, 根据主键ID排序(倒序),返回第一条记录 //等价于:SELECT * FROM `foods` ORDER BY `foods`.`id` DESC LIMIT 1 //语义上相当于返回最后一条记录 db.Last(&food)
- Find
查询多条记录,Find函数返回的是一个数组 //因为Find返回的是数组,所以定义一个商品数组用来接收结果 var foods []Food //等价于:SELECT * FROM `foods` db.Find(&foods)
- Pluck
查询一列值 //商品标题数组 var titles []string //返回所有商品标题 //等价于:SELECT title FROM `foods` //Pluck提取了title字段,保存到titles变量 //这里Model函数是为了绑定一个模型实例,可以从里面提取表名。 db.Model(&Food{}).Pluck("title", &titles)
查询错误处理
通过db.Error属性判断查询结果是否出错, Error属性不等于nil表示有错误发生。
当 First、Last、Take 方法找不到记录时,GORM 会返回 ErrRecordNotFound 错误。
在实际开发中查询不到数据,我们不一定会当成错误处理, gorm库通过下面办法检测Error是不是查询不到数据。
例子: err := db.Take(&food).Error if errors.Is(err, gorm.ErrRecordNotFound) { fmt.Println("查询不到数据") } else if err != nil { //如果err不等于record not found错误,又不等于nil,那说明sql执行失败了。 fmt.Println("查询失败", err) }
where
上面的例子都没有指定where条件,这里介绍下如何设置where条件,主要通过db.Where函数设置条件.
函数说明:
db.Where(query interface{}, args ...interface{})
参数说明:
例子1: //等价于: SELECT * FROM `foods` WHERE (id = '10') LIMIT 1 //这里问号(?), 在执行的时候会被10替代 db.Where("id = ?", 10).Take(&food) //例子2: // in 语句 //等价于: SELECT * FROM `foods` WHERE (id in ('1','2','5','6')) LIMIT 1 //args参数传递的是数组 db.Where("id in (?)", []int{1,2,5,6}).Take(&food) //例子3: //等价于: SELECT * FROM `foods` WHERE (create_time >= '2018-11-06 00:00:00' and create_time <= '2018-11-06 23:59:59') //这里使用了两个问号(?)占位符,后面传递了两个参数替换两个问号。 db.Where("create_time >= ? and create_time <= ?", "2018-11-06 00:00:00", "2018-11-06 23:59:59").Find(&foods) //例子4: //like语句 //等价于: SELECT * FROM `foods` WHERE (title like '%可乐%') db.Where("title like ?", "%可乐%").Find(&foods)
select
设置select子句, 指定返回的字段
//例子1: //等价于: SELECT id,title FROM `foods` WHERE `foods`.`id` = '1' AND ((id = '1')) LIMIT 1 db.Select("id,title").Where("id = ?", 1).Take(&food) //这种写法是直接往Select函数传递数组,数组元素代表需要选择的字段名 db.Select([]string{"id", "title"}).Where("id = ?", 1).Take(&food) //例子2: //可以直接书写聚合语句 //等价于: SELECT count(*) as total FROM `foods` total := []int{} //Model函数,用于指定绑定的模型,这里生成了一个Food{}变量。目的是从模型变量里面提取表名,Pluck函数我们没有直接传递绑定表名的结构体变量,gorm库不知道表名是什么,所以这里需要指定表名 //Pluck函数,主要用于查询一列值 db.Model(&Food{}).Select("count(*) as total").Pluck("total", &total) fmt.Println(total[0])
order
设置排序语句,order by子句
//例子: //等价于: SELECT * FROM `foods` WHERE (create_time >= '2018-11-06 00:00:00') ORDER BY create_time desc db.Where("create_time >= ?", "2018-11-06 00:00:00").Order("create_time desc").Find(&foods)
limit && offset
设置limit和Offset子句,分页的时候常用语句。
//等价于: SELECT * FROM `foods` ORDER BY create_time desc LIMIT 10 OFFSET 0 db.Order("create_time desc").Limit(10).Offset(0).Find(&foods)
count
Count函数,直接返回查询匹配的行数。
//例子: var total int64 = 0 //等价于: SELECT count(*) FROM `foods` //这里也需要通过model设置模型,让gorm可以提取模型对应的表名 db.Model(Food{}).Count(&total) fmt.Println(total)
group by
设置group by子句
//例子: //统计每个商品分类下面有多少个商品 //定一个Result结构体类型,用来保存查询结果 type Result struct { Type int Total int } var results []Result //等价于: SELECT type, count(*) as total FROM `foods` GROUP BY type HAVING (total > 0) db.Model(Food{}).Select("type, count(*) as total").Group("type").Having("total > 0").Scan(&results) //scan类似Find都是用于执行查询语句,然后把查询结果赋值给结构体变量,区别在于scan不会从传递进来的结构体变量提取表名. //这里因为我们重新定义了一个结构体用于保存结果,但是这个结构体并没有绑定foods表,所以这里只能使用scan查询函数。
提示:Group函数必须搭配Select函数一起使用
2.2 执行原生sql语句
对于复杂的查询,例如多表连接查询,我们可以直接编写sql语句,然后执行sql语句。
gorm通过db.Raw设置sql语句,通过Scan执行查询。
例子: sql := "SELECT type, count(*) as total FROM `foods` where create_time > ? GROUP BY type HAVING (total > 0)" //因为sql语句使用了一个问号(?)作为绑定参数, 所以需要传递一个绑定参数(Raw第二个参数). //Raw函数支持绑定多个参数 db.Raw(sql, "2018-11-06 00:00:00").Scan(&results) fmt.Println(results)
3.gorm更新数据
方便描述,依然使用上面查询数据的foods表结构定义的数据为例。
3.1 Save
用于保存模型变量的值。
提示: 相当于根据主键id,更新所有模型字段值。
food := Food{} //先查询一条记录, 保存在模型变量food //等价于: SELECT * FROM `foods` WHERE (id = '2') LIMIT 1 db.Where("id = ?", 2).Take(&food) //修改food模型的值 food.Price = 100 //等价于: UPDATE `foods` SET `title` = '可乐', `type` = '0', `price` = '100', `stock` = '26', `create_time` = '2018-11-06 11:12:04' WHERE `foods`.`id` = '2' db.Save(&food)
3.2 Update
更新单个字段值
//例子1: //更新food模型对应的表记录 //等价于: UPDATE `foods` SET `price` = '25' WHERE `foods`.`id` = '2' db.Model(&food).Update("price", 25) //通过food模型的主键id的值作为where条件,更新price字段值。 //例子2: //上面的例子只是更新一条记录,如果我们要更全部记录怎么办? //等价于: UPDATE `foods` SET `price` = '25' db.Model(&Food{}).Update("price", 25) //注意这里的Model参数,使用的是Food{},新生成一个空白的模型变量,没有绑定任何记录。 //因为Food{}的id为空,gorm库就不会以id作为条件,where语句就是空的 //例子3: //根据自定义条件更新记录,而不是根据主键id //等价于: UPDATE `foods` SET `price` = '25' WHERE (create_time > '2018-11-06 20:00:00') db.Model(&Food{}).Where("create_time > ?", "2018-11-06 20:00:00").Update("price", 25)
3.3 Updates
更新多个字段值
//例子1: //通过结构体变量设置更新字段 updataFood := Food{ Price:120, Title:"柠檬雪碧", } //根据food模型更新数据库记录 //等价于: UPDATE `foods` SET `price` = '120', `title` = '柠檬雪碧' WHERE `foods`.`id` = '2' //Updates会忽略掉updataFood结构体变量的零值字段, 所以生成的sql语句只有price和title字段。 db.Model(&food).Updates(&updataFood) //例子2: //根据自定义条件更新记录,而不是根据模型id updataFood := Food{ Stock:120, Title:"柠檬雪碧", } //设置Where条件,Model参数绑定一个空的模型变量 //等价于: UPDATE `foods` SET `stock` = '120', `title` = '柠檬雪碧' WHERE (price > '10') db.Model(&Food{}).Where("price > ?", 10).Updates(&updataFood) //例子3: //如果想更新所有字段值,包括零值,就是不想忽略掉空值字段怎么办? //使用map类型,替代上面的结构体变量 //定义map类型,key为字符串,value为interface{}类型,方便保存任意值 data := make(map[string]interface{}) data["stock"] = 0 //零值字段 data["price"] = 35 //等价于: UPDATE `foods` SET `price` = '35', `stock` = '0' WHERE (id = '2') db.Model(&Food{}).Where("id = ?", 2).Updates(data)
提示: 通过结构体变量更新字段值, gorm库会忽略零值字段。就是字段值等于0, nil, "", false这些值会被忽略掉,不会更新。如果想更新零值,可以使用map类型替代结构体。
3.4 更新表达式
UPDATE foods SET stock = stock + 1 WHERE id = '2'
这样的带计算表达式的更新语句gorm怎么写?
gorm提供了Expr函数用于设置表达式
//等价于: UPDATE `foods` SET `stock` = stock + 1 WHERE `foods`.`id` = '2' db.Model(&food).Update("stock", gorm.Expr("stock + 1"))
4.gorm删除数据
4.1 删除模型数据
删除模型数据一般用于删除之前查询出来的模型变量绑定的记录。
用法:db.Delete(模型变量)
//例子: food := Food{} //先查询一条记录, 保存在模型变量food //等价于: SELECT * FROM `foods` WHERE (id = '2') LIMIT 1 db.Where("id = ?", 2).Take(&food) //删除food对应的记录,通过主键Id标识记录 //等价于: DELETE from `foods` where id=2; db.Delete(&food)
4.2 根据Where条件删除数据
用法:db.Where(条件表达式).Delete(空模型变量指针)
//等价于:DELETE from `foods` where (`type` = 5); db.Where("type = ?", 5).Delete(&Food{})
提示:这里Delete函数需要传递一个空的模型变量指针,主要用于获取模型变量绑定的表名。 不能传递一个非空的模型变量,否则就变成删除指定的模型数据,自动在where语句加上类似id = 2这样的主键约束条件。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通