go 学习 之 GORM数据插入,查询,修改,删除

GORM插入数据

gorm新增记录

//定义一个用户,并初始化数据
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
}

gorm如何获取新插入记录的自增Id

gorm库默认不会自动返回新插入记录的id,需要我们自己额外处理。
我们想获取刚插入数据的自增Id,本质上是通过sql语句SELECT LAST_INSERT_ID() 获取上一条插入语句的自增id

那么gorm新增记录后获取自增id的步骤如下:

  1. 使用db.Create插入记录
  2. 执行sql语句SELECT LAST_INSERT_ID(),获取Id

实例代码:

//定义一个用户,并初始化数据
u := User{...忽略初始化代码...} 
//插入记录
db.Create(u)
//获取插入记录的Id
var id []int
db.Raw("select LAST_INSERT_ID() as id").Pluck("id", &id)

//因为Pluck函数返回的是一列值,返回结果是slice类型,我们这里只有一个值,所以取第一个值即可。
fmt.Println(id[0])

提示:如果gorm设置了数据库连接池,那么每次执行数据库查询的时候都会从数据库连接池申请一个数据库连接,那么上述代码必须使用数据库事务,确保插入数据和查询自增id两条sql语句是在同一个数据库连接下执行,否则在高并发场景下,可能会查询不到自增id,或者查询到错误的id。

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"
}

使用gorm链式操作函数查询数据

gorm查询主要由以下几个部分的函数组成,这些函数可以串起来组合sql语句,使用起来类似编写sql语句的习惯。

1.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表示有错误发生。

例子:
if err := db.Take(&food).Error; err != nil {
    fmt.Println("查询失败", err)
}

错误特例:
查询不到数据, gorm也会当成错误处理, 如果查询不到数据, 上面的例子会打印 查询失败 record not found

在实际开发中查询不到数据,我们不一定会当成错误处理, gorm库提供了下面两种办法检测Error是不是查询不到数据.

  • gorm.IsRecordNotFoundError
例子:
err := db.Take(&food).Error
if gorm.IsRecordNotFoundError(err) {
    fmt.Println("查询不到数据")
} else if err != nil {
//如果err不等于record not found错误,又不等于nil,那说明sql执行失败了。
	fmt.Println("查询失败", err)
}
  • db.RecordNotFound
例子:
//链式操作,先查询,然后检测查询结果
if db.Take(&food).RecordNotFound {
    fmt.Println("查询不到数据")
}

where

上面的例子都没有指定where条件,这里介绍下如何设置where条件,主要通过db.Where函数设置条件.
函数说明:
db.Where(query interface{}, args ...interface{})

参数说明:

参数名 说明
query sql语句的where子句, where子句中使用问号(?)代替参数值,则表示通过args参数绑定参数
args where子句绑定的参数,可以绑定多个参数
//等价于: 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

设置排序语句,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)

count

Count函数,直接返回查询匹配的行数。

//例子:
total := 0
//等价于: SELECT count(*) FROM `foods` 
//这里也需要通过model设置模型,让gorm可以提取模型对应的表名
db.Model(Food{}).Count(&total)
fmt.Println(total)

分组

设置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函数一起使用

直接执行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)

GORM更新数据

gorm更新记录常用方法

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)

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)

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)

更新表达式

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"))

GORM删除数据

 删除模型数据

删除模型数据一般用于删除之前查询出来的模型变量绑定的记录。
用法: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)

根据Where条件删除数据

用法:db.Where(条件表达式).Delete(空模型变量指针)

//等价于:DELETE from `foods` where (`type` = 5);
db.Where("type = ?", 5).Delete(&Food{})

提示:这里Delete函数需要传递一个空的模型变量指针,主要用于获取模型变量绑定的表名。 不能传递一个非空的模型变量,否则就变成删除指定的模型数据,自动在where语句加上类似id = 2这样的主键约束条件。

在开发中经常需要数据库事务来保证多个数据库写操作的原子性。例如电商系统中的扣减库存和保存订单。
gorm事务用法:

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

//在事务中执行数据库操作,使用的是tx变量,不是db。

//库存减一
//等价于: UPDATE `foods` SET `stock` = stock - 1  WHERE `foods`.`id` = '2' and stock > 0
//RowsAffected用于返回sql执行后影响的行数
rowsAffected := tx.Model(&food).Where("stock > 0").Update("stock", gorm.Expr("stock - 1")).RowsAffected
if rowsAffected == 0 {
    //如果更新库存操作,返回影响行数为0,说明没有库存了,结束下单流程
    //这里回滚作用不大,因为前面没成功执行什么数据库更新操作,也没什么数据需要回滚。
    //这里就是举个例子,事务中可以执行多个sql语句,错误了可以回滚事务
    tx.Rollback()
    return
}
err := tx.Create(保存订单).Error

//保存订单失败,则回滚事务
if err != nil {
    tx.Rollback()
} else {
    tx.Commit()
}

猜你喜欢

转载自blog.csdn.net/youandme520/article/details/113871054