gorm字典

gorm的使用

gorm的安装

打开go运行的文件夹下的终端,创建main.go文件 执行下面的指令初始化mod

go mod init main.go

安装gorm
官方给的示例是

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite
但是我们这里要使用mysql数据库 所以就对第二个依赖进行一部分修改

got get -u gorm.io/driver/mysql

gorm的使用 增删改查

package main

import (
    "fmt"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

type Product struct {
    gorm.Model
    Code  string
    Price uint
}

func main() {
    dsn := "root:1296729980@tcp(127.0.0.1:3306)/go_db"
    db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        fmt.Println(err)
    }
    // //创建表
    db.AutoMigrate(&Product{})
    p := Product{
        Price: 200,
        Code:  "2",
    }
    //插入数据
    db.Create(&p)
    //查看数据
    var product Product
    // db.First(&product, 2) // 按照整形链查找
    db.First(&product, 1)
    fmt.Printf("product: %v\n", product)

    //改
    //这里有坑,修改单个字段使用的是update 修改多个使用到的是updates
    db.Model(&product).Update("Price", 300)
    //更新多个字段
    db.Model(&product).Updates(Product{Price:300,Code:"3"})
    db.Model(&product).Updates(map[string]interface{}{"Price": 300, "Code": "3"})
    //删
    db.Delete(&product, 1)
}

gorm 模型 model

gorm的模型更多的是一种约定,而不是配置

gorm.Model是grom的一种结构体代表的是

type Model struct{
    ID        uint           `gorm:"primaryKey"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt `gorm:"index"`
}

当我们生命gorm结构体时,如果不想直接写gorm.Model的话,将上面的这些字段加入到我们声明的结构体当中效果也是一样的。

字段权限控制

当我们对字段CRUD时,默认字段是具有全部的权限的

gorm允许我们使用使用标签来控制字段级的权限

type User struct {
  Name string `gorm:"<-:create"` // allow read and create
  Name string `gorm:"<-:update"` // allow read and update
  Name string `gorm:"<-"`        // allow read and write (create and update)
  Name string `gorm:"<-:false"`  // allow read, disable write permission
  Name string `gorm:"->"`        // readonly (disable write permission unless it configured)
  Name string `gorm:"->;<-:create"` // allow read and create
  Name string `gorm:"->:false;<-:create"` // createonly (disabled read from db)
  Name string `gorm:"-"`            // ignore this field when write and read with struct
  Name string `gorm:"-:all"`        // ignore this field when write, read and migrate with struct
  Name string `gorm:"-:migration"`  // ignore this field when migrate with struct
}

当使用gorm Migrator创建表时,不会创建被忽略的字段名

创建、修改、删除时间

gorm约定默认使用

    CreatedAt 
    UpdatedAt 
    DeletedAt

这三个字段名来分别记载创建时间,修改时间,删除时间
如果不想使用这三个字段可以使用标签进行更改

type User struct {
  CreatedAt time.Time // 在创建时,如果该字段值为零值,则使用当前时间填充
  UpdatedAt int       // 在创建时该字段值为零值或者在更新时,使用当前时间戳秒数填充
  Updated   int64 `gorm:"autoUpdateTime:nano"` // 使用时间戳填纳秒数充更新时间
  Updated   int64 `gorm:"autoUpdateTime:milli"` // 使用时间戳毫秒数填充更新时间
  Created   int64 `gorm:"autoCreateTime"`      // 使用时间戳秒数填充创建时间
}

gorm 连接数据库

gorm支持连接的数据库有以下几种MySQL, PostgreSQL, SQlite, SQL Server
我们主要介绍mysql
如果想要了解更多可以访问官方文档地址

官方链接mysql的约定格式为

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

func main() {
  // 参考 https://github.com/go-sql-driver/mysql#dsn-data-source-name 获取详情
  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{})
}

这只是个实例 我们在使用时dsn会这样写

    dsn := "root:1296729980@tcp(127.0.0.1:3306)/go_db"

root为名称,root后跟的是数据库密码,go_db是我们在root下创建的数据库

想要正确的处理 time.Time ,您需要带上 parseTime 参数, (更多参数) 要支持完整的 UTF-8 编码,您需要将 charset=utf8 更改为 charset=utf8mb4 查看 此文章 获取详情

如果想要使用更加精细的配置,官方也给了配置选项,以下表明的是默认配置选项

db, err := gorm.Open(mysql.New(mysql.Config{
  DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
  DefaultStringSize: 256, // string 类型字段的默认长度
  DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
  DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
  DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
  SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
}), &gorm.Config{})

示例:

package main

import (
    "fmt"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

func main() {
    dsn := "root:1296729980@tcp(localhost:3306)/go_db"
    d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        fmt.Println(err)
    }
    fmt.Println(d)
}

&{0x140001da630 <nil> 0 0x140002aa000 1}

我们会得到一个地址,代表链接成功

创建记录

创建一个记录

package main

import (
    "fmt"
    "time"

    "gorm.io/driver/mysql"
    "gorm.io/gorm"
)

var db *gorm.DB

type User struct {
    gorm.Model
    Name     string
    Age      uint
    Birthday time.Time
}

func init() {
    dsn := "root:1296729980@tcp(localhost:3306)/go_db"
    d, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
    if err != nil {
        fmt.Println(err)
    }
    db = d
}
func InsertValue() {
    user := User{Name: "小明", Age: 11, Birthday: time.Now()}
    result := db.Create(&user)
    fmt.Println("userID", user.ID) // 显示添加ID
    fmt.Println("Error", result.Error)  //错误信息
    fmt.Println("Rows", result.RowsAffected) //改变行数
}

func CreateTable() {
    db.AutoMigrate(&User{})
}

func main() {
    CreateTable()
    InsertValue()
}

⚠️注意,在命名结构体时,我们所要用到的所有字段必须首字母大写,这是一种约定,否则gorm将无法将字段名添加至数据库中

按照字段传值

func InsertValue() {
    user := User{Name: "小红", Age: 12, Birthday: time.Now()}
    db.Select("Name","Age").Create(&user)
    db.Omit("Name", "Age").Create(&user)
}

db.Select(column1,column).Create()指只添加我们查询到的字段名,其他的数据忽略
db.Omit(column1,column2).Create()指忽略我们传的字段名,其他字段名全部添加

|  4 | 2022-06-15 06:20:56.537 | 2022-06-15 06:20:56.537 | NULL       | 小红   |   12 | NULL                    |
|  5 | 2022-06-15 06:22:15.330 | 2022-06-15 06:22:15.330 | NULL       | NULL   | NULL | 2022-06-15 06:22:15.329 |

批量插入

批量插入就是传一个slice给Create(),grom会单独创建出一条sql语句,钩子在这时依然可以进行使用

user := []User{{Name: "小刚", Age: 11}, {Name: "小强", Age: 10}}

钩子

Hook 是在创建、查询、更新、删除等操作之前、之后调用的函数。

hook执行顺序

// 开始事务
BeforeSave
BeforeCreate
// 关联前的 save
// 插入记录至 db
// 关联后的 save
AfterCreate
AfterSave
// 提交或回滚事务

func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
  u.UUID = uuid.New()

  if !u.IsValid() {
    err = errors.New("can't save invalid data")
  }
  return
}

func (u *User) AfterCreate(tx *gorm.DB) (err error) {
  if u.ID == 1 {
    tx.Model(u).Update("role", "admin")
  }
  return
}

所有对数据库的操作和伴随的hook是一个事务,如果有一项报错,那么之前所有的操作都将被回滚

func (u *User) AfterCreate(tx *gorm.DB) (err error) {
    if u.ID == 1 {
        tx.Model(u).Update("role", "admin")
    }
    fmt.Println("AfterCreate")
    return
}

我们会发现在上传多个数据的时候,会执行多次hook

查询

检索单个对象

// 获取第一条记录(主键升序)
db.First(&user)
// 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;

result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error or nil

// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)

检索多条记录

检索多条记录时一定注意,传入的是一个切片的形式

    var user []User
    db.Find(&user)
    for _, u := range user {
        fmt.Printf("u: %v\n", u.ID)
    }

使用主键进行检索

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

根据结构体查字段

    var user []User
    db.Where(&User{Name: "小刚"}).Find(&user)
    for _, u := range user {
        fmt.Printf("u: %v\n", u.ID)
    }

根据结构体查询字段

var user User
    db.Select("name").First(&user)
    fmt.Printf("user: %v\n", user.Name)

通过这种方式我们可以检索到name

智能选择字段

新建一个结构体

type UserSelect struct {
    Name string
}

使用

    db.Model(&User{}).Limit(10).Find(&user)
    // SELECT `id`, `name` FROM `users` LIMIT 10
    fmt.Println(user)

我们可以得到表中每一行的name
同时gorm还支持子查询,form子查询以及group子查询

更新

保存所有字段

user.Name = "小小"
    user.Age = 10
    user.Birthday = time.Now()
    db.Save(&user)

db.Save()会保存所有的字段,即使是0值也会被保存
执行上面的语句我们在查询表的时候,可以发现表中多了一条名为小小的数据

更改一列

db.Select("name").Model(&user).Where("id=?", 15).Update("name", "小一")
    fmt.Printf("user.Name: %v\n", user.Name)

上述语句相当于sql语句

SELECT name from UPDATE users SET name='小一', updated_at='2022-06-15 09:45:39.965' WHERE id=15 AND active=true;

更改多列

使用struct更新属性,只能更新非零值

db.Select("name").Model(&user).Where("age=?", 10).Updates(&User{Name: "小三"})

使用map[string]更新属性

    db.Select("name").Model(&user).Where("age=?", 10).Updates(map[string]interface{}{"name":"小三"})

更新多列时,使用到的事updates而不再是update,并且需要传一个结构体或者是map类型的数据

更新选定的字段

我们先定义一个结构体

    var user User
    user.Name = "new_name"
    user.Age = 11
    user.Birthday = time.Now()

有时我们不想把所有的字段全部都进行更改,我们可以使用到select或者是omit

    db.Model(&user).Select("name").Where("age=?", 10).Updates(&user)
    db.Model(&user).Omit("name").Where("age=?", 10).Updates(&user)

select是对所有age为10的数据的name全部进行更改,而omit则是除了name以外的其他字段进行更改

阻止全局更新

db.Model(&user).Select("name").Updates(&user)

像是这种不加任何判断直接进行全局更新的情况,gorm一般是不会允许的,并抛出这样的错误

WHERE conditions required

对此我们必须加一些条件或者使用sql原生语句

db.Exec("UPDATE users SET name = ?", "jinzhu")
// UPDATE users SET name = "jinzhu"

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Model(&User{}).Update("name", "jinzhu")
// UPDATE users SET `name` = "jinzhu"

删除

db.Where("name = ?", "小红").Delete(&user)
// DELETE from emails where id = 10 AND name = "jinzhu";

直接删除所有名字为小红的数据

根据主键删除

删除主键为10的数据

db.Delete(&user,10)

删除主键为1,2,

db.Delete(&users, []int{1,2,3})

阻止全局删除

db.Exec("DELETE FROM users")
// DELETE FROM users

db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
// DELETE FROM users

软删除

我们引入了gorm.Model后,因为gorm.deletedat字段的存在,其实数据并没有被真正的删除,而是在deleted_at中加入了一个时间戳

查找被软删除的记录

db.Unscoped().Where("age = 20").Find(&users)
// SELECT * FROM users WHERE age = 20;

永久删除
直接将数据从数据表中删除

db.Unscoped().Delete(&order)
// DELETE FROM orders WHERE id=10;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容

  • 简介 gorm[https://gorm.io/] 是 go orm 实现之一,这篇文章将以 mysql 为例,带...
    PurelightMe阅读 11,769评论 0 3
  • [TOC] 说明 请先阅读官方文档[http://gorm.book.jasperxu.com/],由于官方文档对...
    小晚不晚阅读 1,648评论 0 1
  • Gorm for SQL Join 关联查询 Belongs To: http://gorm.io/docs/be...
    坚果jimbowhy阅读 3,741评论 0 0
  • 最近在想给这个小站增加点赞和评论功能,第三方系统又有各种限制,就想自己弄个后端,实现类似的功能,对于个人来说,数据...
    hatlonely阅读 52,514评论 3 21
  • 写在前面: GOrm版本: v1.9.11GORM是Go上的数据库Lib,简单易用,而且是国人开发的,中文文档也很...
    沉寂之舟阅读 6,164评论 0 3