Go-MySQL(三)GORM
gorm是一个全自动的对象关系映射框架,和Java技术栈中的spring data JPA类似,能实现Java实体映射到数据库实体,通过面向对象的相关方法在代码层面实现对数据的增删改查
准备工作
安装:
go get -u github.com/jinzhu/gorm
使用
模型定义
ORM框架操作数据库都需要预先定义模型,模型可以理解成数据模型,作为操作数据库的媒介
- 从数据库读取到的数据都会先保存到预先定义的模型对象,通过模型对象得到想要的数据
- 插入数据到数据库也是先新建一个模型对象,然后把数据保存到模型对象,再把模型对象保存到数据库
gorm负责将对模型的读写操作翻译成sql语句,然后gorm再把数据库执行sql语句后返回的结果转化为我们定义的模型对象。
在golang中gorm模型定义是通过struct实现的,这样我们就可以通过gorm库实现struct类型和mysql表数据的映射。
在GORM中是使用标签的形式进行模型和struct的映射定义:
先看一个小例子:
// 模型定义
type Student struct {
ID int64 `gorm:"Column:id;type:bigint;PRIMARY_KEY;AUTO_INCREMENT"`
StuName string `gorm:"Column:name;type:varchar(255);INDEX:idx_name;NOT NULL"`
Email string `gorm:"Column:email;type:varchar(255);UNIQUE:uniq_email;NOT NULL"`
Sex int64 `gorm:"Column:sex;type:bigint;NOT NULL"`
CreateTime int64 `gorm:"Column:created_at;type:bigint;NOT NULL"`
UpdateTime int64 `gorm:"Column:updated_at;type:bigint;NOT NULL"`
}
数据表定义:
CREATE TABLE `tb_student` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`sex` bigint(20) NOT NULL,
`email` varchar(255) NOT NULL,
`created_at` bigint(20) NOT NULL,
`updated_at` bigint(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_email` (`email`),
KEY `idx_name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8;
标签定义:标签之间通过分号隔开
`gorm:标签A;标签B;....`
常用标签:
column: 数据库列名
PRIMARY_KEY: 主键定义
AUTO_INCREMENT: 自增定义
index: 普通索引,可以不加名字,gorm会自动生成
UNIQUE: 唯一索引
NOT_NULL:非空约束
标签上起始就是表定义,需要跟数据库的表定义保持一致
如果你的结构体中有些字段不需要参与gorm的数据操作,则可以使用这个标签
Sum int `gorm:"-"` // Sum字段不需要参与数据操作
自动建表
GORM 的AutoMigrate函数,仅支持建表,不支持修改字段和删除字段,避免意外导致丢失数据。通过AutoMigrate函数可以快速建表,如果表已经存在不会重复创建。
// 根据User结构体,自动创建表结构.
db.AutoMigrate(&User{})
// 一次创建User、Product、Order三个结构体对应的表结构
db.AutoMigrate(&User{}, &Product{}, &Order{})
// 可以通过Set设置附加参数,下面设置表的存储引擎为InnoDB
db.Set("gorm:table_options", "ENGINE=InnoDB").AutoMigrate(&User{})
// 快速建表
func CreateTable(db *gorm.DB) {
db.AutoMigrate(&User{})
}
连接数据库
GORM中连接数据库:
- 配置DSN (Data Source Name)
- 使用gorm.Open连接数据库
其实就是基于底层sql.Open()进行了一层封装
func ConnectDB() (*gorm.DB,error){
//配置MySQL连接参数,一般放在配置文件
username := "root" //账号
password := "123" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DbName := "test" //数据库名
timeout := "10s" //连接超时,10秒
// 配置dsn
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local&timeout=%s",username,password,host,port,DbName,timeout)
db,err := gorm.Open("mysql",dsn)
if err != nil{
return nil, err
}
return db,nil
}
插入数据
数据插入这块还是比较简单的,通过数据对象进行操作
// 数据插入
func InsertData(db *gorm.DB) {
// 构造数据模型
stu := Student{
StuName: "test_stu",
Email: "test@qq.com",
Sex: 1,
CreateTime: time.Now().Unix(),
UpdateTime: time.Now().Unix(),
}
// 新增数据库
// 插入数据时,自增主键一般不需要在声明对象时赋值,在gorm插入时需要传递地址
// 解决:https://blog.csdn.net/u013536232/article/details/105513072
if err := db.Create(&stu).Error;err != nil{
HandlerError(err)
}else {
log.Print("插入成功")
}
}
gorm不会返回自增主键的值,那么如何获取自增主键的值:
ids := []int64{}
db.Raw("SELECT LAST_INSERT_ID() as id").Pluck("id",&ids)
fmt.Println("自增主键值:",ids[0])
查询数据
数据查询这块gorm提供了比较多的API,gorm查询数据本质上就是提供一组函数,帮我们快速拼接sql语句,尽量减少编写sql语句的工作量。
gorm查询结果我们一般都是保存到结构体(struct)变量,所以在执行查询操作之前需要根据自己想要查询的数据定义结构体类型。
gorm提供了以下几个查询函数:
查询单列数据,使用Take()
// 查一列数据
if err := db.Where("id = ?",6).Take(&stu).Error;err != nil{
HandlerError(errors.New("查询单列数据失败"))
}else{
fmt.Println("查询单列数据:",stu)
}
条件通过where()函数指定,参数用?占位,完全可以将sql语句中的where子句粘贴上去,十分易读
当查不到数据也是会抛出异常的,但是也可以通过这种方式判断是否查到相关记录
if flag := db.Where("id = ?",6).Take(&stu).RecordNotFound();flag{
fmt.Println("找不到相关数据")
}
查询单列数据,根据主键排序的第一条记录
// 查询单列数据,根据主键排序,取第一
if err := db.Where("sex = ?",1).First(&stu).Error;err != nil{
HandlerError(errors.New("查询单列数据失败"))
}else{
fmt.Println("查询单列数据:",stu)
}
查询单列数据,根据主键排序的最后一条记录
// 查询单列数据,根据主键排序,取最后
if err := db.Where("sex = ?",1).Last(&stu).Error;err != nil{
HandlerError(errors.New("查询单列数据失败"))
}else{
fmt.Println("查询单列数据:",stu)
}
查询多列数据,使用切片接收
stu := []Student{}
// 查询多列数据,查询不到数据也会抛出panic
if err := db.Where("name = ?","test_stu").Find(&stu).Error; err != nil{
HandlerError(errors.New("查询多列数据失败"))
}else{
fmt.Println("查询多列数据:",stu)
}
如果我需要查某几个字段,那么就需要使用Pluck()
var stuName []string
// 查询某个字段数据,接收应该使用切片
if err := db.Model(Student{}).Where("email = ?","test@qq.com").Pluck("name",&stuName).Error;err != nil{
fmt.Println(err)
}
fmt.Println("student_name: ",stuName)
// 如果查询不到也可以使用链式函数调用的方式
rowNotFound := db.Model(Student{}).Where("id = ?",6).Pluck("name",&stuName).RecordNotFound()
if rowNotFound{
fmt.Println("没有找到对应记录")
}
fmt.Println("student_name: ",stuName)
如果是多字段的话,就需要多次Pluck()
var id []string
// select也需要绑定表,查询多个可以直接pluck多次
if err := db.Model(Student{}).Where("id = ?",6).Pluck("name",&stuName).Pluck("id",&id).Error;err != nil{
fmt.Println(err)
}
需要绑定模型,不然不知道具体查哪个表,绑定模型通过db.Model即可
其他类似分页,排序,记录数等查询都有相应的API
// order + 分页,find返回的是一个切片
db.Where("sex in (?)",[]int{1,0}).Order("id desc").Limit(10).Offset(0).Find(&stu1)
fmt.Println(stu1)
// count,也需要绑定数据表
count := 0
db.Model(Student{}).Where("sex in (?)",[]int{1,0}).Order("id desc").Limit(10).Offset(0).Count(&count)
fmt.Println(count)
删除数据
指定条件通过Delete()
func DeleteData(db *gorm.DB) {
// 绑定模型
db.Where("id = ?",7).Delete(&Student{})
}
或者先查出来在delete()
//先查询一条记录, 保存在模型变量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)
更新数据
有三种方法,我觉得save()最好用,最简单直接
// 第一种更新方法:save
stu := Student{}
db.Where("id = ?",6).First(&stu)
直接修改想要更新的字段
stu.Sex = 2
stu.UpdateTime = 1111
stu.CreateTime = 111
db.Save(stu)
还有两种,单字段更新用update,多字段用updates
// 更新数据
func UpdateDate(db *gorm.DB) {
// 第一种更新方法:save
stu := Student{}
db.Where("id = ?",6).First(&stu)
直接修改想要更新的字段
//stu.Sex = 2
//stu.UpdateTime = 1111
//stu.CreateTime = 111
//db.Save(stu)
// 第二种:update
// 单个字段更新,需要绑定模型
db.Model(&stu).Update("updated_at",1111111)
// 第三种:updates,多参数更新,支持struct和map[string]interface{}
fmt.Println(db.Model(&stu).Updates(map[string]interface{}{"name":"updates","sex":1}).Error.Error())
}
事务操作
在开发中经常需要数据库事务来保证多个数据库写操作的原子性。例如电商系统中的扣减库存和保存订单。
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()
}
完整代码
package go_mysql
import (
"errors"
"fmt"
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/mysql"
"log"
"time"
)
// 模型定义
type Student struct {
ID int64 `gorm:"Column:id;type:bigint;PRIMARY_KEY;AUTO_INCREMENT"`
StuName string `gorm:"Column:name;type:varchar(255);INDEX:idx_name;NOT NULL"`
Email string `gorm:"Column:email;type:varchar(255);UNIQUE:uniq_email;NOT NULL"`
Sex int64 `gorm:"Column:sex;type:bigint;NOT NULL"`
CreateTime int64 `gorm:"Column:created_at;type:bigint;NOT NULL"`
UpdateTime int64 `gorm:"Column:updated_at;type:bigint;NOT NULL"`
Sum int `gorm:"-"` // -:不参数gorm数据读写
}
func (s Student) TableName() string{
return "tb_student"
}
func TestORM() {
db,err := ConnectDB()
HandlerError(err)
// 数据插入
// InsertData(db)
// 数据查询
// SelectData(db)
// UpdateDate(db)
// DeleteData(db)
CreateTable(db)
}
// 连接数据库
func ConnectDB() (*gorm.DB,error){
//配置MySQL连接参数,一般放在配置文件
username := "root" //账号
password := "123" //密码
host := "127.0.0.1" //数据库地址,可以是Ip或者域名
port := 3306 //数据库端口
DbName := "test" //数据库名
timeout := "10s" //连接超时,10秒
// 配置dsn
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=True&loc=Local&timeout=%s",username,password,host,port,DbName,timeout)
db,err := gorm.Open("mysql",dsn)
if err != nil{
return nil, err
}
return db,nil
}
// 数据插入
func InsertData(db *gorm.DB) {
// 构造数据模型
stu := Student{
StuName: "test_stu",
Email: "test@qq.com",
Sex: 1,
CreateTime: time.Now().Unix(),
UpdateTime: time.Now().Unix(),
}
// 新增数据库
// 插入数据时,自增主键一般不需要在声明对象时赋值,在gorm插入时需要传递地址
// 解决:https://blog.csdn.net/u013536232/article/details/105513072
if err := db.Create(&stu).Error;err != nil{
HandlerError(err)
}else {
log.Print("插入成功")
}
ids := []int64{}
db.Raw("SELECT LAST_INSERT_ID() as id").Pluck("id",&ids)
fmt.Println("自增主键值:",ids[0])
}
// 查询数据
func SelectData(db *gorm.DB) {
stu := []Student{}
// 查询多列数据,查询不到数据也会抛出panic
if err := db.Where("name = ?","test_stu").Find(&stu).Error; err != nil{
HandlerError(errors.New("查询多列数据失败"))
}else{
fmt.Println("查询多列数据:",stu)
}
// 查询单列数据,根据主键排序,取第一
if err := db.Where("sex = ?",1).First(&stu).Error;err != nil{
HandlerError(errors.New("查询单列数据失败"))
}else{
fmt.Println("查询单列数据:",stu)
}
// 查询单列数据,根据主键排序,取最后
if err := db.Where("sex = ?",1).Last(&stu).Error;err != nil{
HandlerError(errors.New("查询单列数据失败"))
}else{
fmt.Println("查询单列数据:",stu)
}
// 查一列数据
if err := db.Where("id = ?",6).Take(&stu).Error;err != nil{
HandlerError(errors.New("查询单列数据失败"))
}else{
fmt.Println("查询单列数据:",stu)
}
if flag := db.Where("id = ?",6).Take(&stu).RecordNotFound();flag{
fmt.Println("找不到相关数据")
}
var stuName []string
// 查询某个字段数据,接收应该使用切片
if err := db.Model(Student{}).Where("email = ?","test@qq.com").Pluck("name",&stuName).Error;err != nil{
fmt.Println(err)
}
fmt.Println("student_name: ",stuName)
// 如果查询不到也可以使用链式函数调用的方式
rowNotFound := db.Model(Student{}).Where("id = ?",6).Pluck("name",&stuName).RecordNotFound()
if rowNotFound{
fmt.Println("没有找到对应记录")
}
fmt.Println("student_name: ",stuName)
var id []string
// select也需要绑定表,查询多个可以直接pluck多次
if err := db.Model(Student{}).Where("id = ?",6).Pluck("name",&stuName).Pluck("id",&id).Error;err != nil{
fmt.Println(err)
}
fmt.Println(id)
fmt.Println(stuName)
stu1 := []Student{}
// order + 分页,find返回的是一个切片
db.Where("sex in (?)",[]int{1,0}).Order("id desc").Limit(10).Offset(0).Find(&stu1)
fmt.Println(stu1)
// count,也需要绑定数据表
count := 0
db.Model(Student{}).Where("sex in (?)",[]int{1,0}).Order("id desc").Limit(10).Offset(0).Count(&count)
fmt.Println(count)
}
// 删除数据
func DeleteData(db *gorm.DB) {
// 绑定模型
db.Where("id = ?",7).Delete(&Student{})
}
// 更新数据
func UpdateDate(db *gorm.DB) {
// 第一种更新方法:save
stu := Student{}
db.Where("id = ?",6).First(&stu)
直接修改想要更新的字段
//stu.Sex = 2
//stu.UpdateTime = 1111
//stu.CreateTime = 111
//db.Save(stu)
// 第二种:update
// 单个字段更新,需要绑定模型
db.Model(&stu).Update("updated_at",1111111)
// 第三种:updates,多参数更新,支持struct和map[string]interface{}
fmt.Println(db.Model(&stu).Updates(map[string]interface{}{"name":"updates","sex":1}).Error.Error())
}
type User struct {
ID int64 `gorm:"Column:id;type:bigint;PRIMARY_KEY;AUTO_INCREMENT"`
StuName string `gorm:"Column:name;type:varchar(255);INDEX:idx_name;NOT NULL"`
}
// 快速建表
func CreateTable(db *gorm.DB) {
db.AutoMigrate(&User{})
}
// 异常处理
func HandlerError(err error) {
if err != nil{
panic(err)
}
}