前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布

Go gorm

原创
作者头像
f1sh
发布2024-07-29 20:35:32
950
发布2024-07-29 20:35:32

Go gorm

这篇文章主要先简单总结一下gorm的crud,

什么是orm

在学习gorm之前,先了解一下什么是orm

在后端开发上,通常都要与资料库做操作(新增、修改、删除、查找),后端会撰写 SQL 语句,并且透过一些工具或套件(例如:pymysql)向 SQL 资料库来做沟通。而撰写原生 SQL 的缺点为:

  1. 不可维护性:代码难阅读且不易维护。
  2. 不可重用性:通常不容易被重用,每个 SQL 语句都需要独立编写并维护。
  3. 容易犯错:容易犯错,容易缺少引号、忘记加条件等。
  4. 容易被攻击:容易遭到 SQL Injection 攻击。
  5. 资料库迁移问题:针对 MySQL 开发的 SQL 语句就没办法直接应用到 Oracle 上的资料库。

为了解决上述问题,ORM 是再往上进行一层封装,而无需去编写原生的 SQL 语句,取而代之的是基于物件导向的思想去编写 Class、Object、Method 等。而 ORM 会再生成 SQL 语句再往下去执行

说大白话就是用类似orm.create来替代原有相对复杂的sql语句去对数据库进行操作。

ORM 优缺点

优点

提高开发效率

缺点

牺牲性能

牺牲灵活性

安装

接下来回到这篇文章的主体gorm上,这里先进行安装

代码语言:javascript
复制
 go get -u gorm.io/gorm
 go get -u gorm.io/driver/sqlite

演示

代码语言:javascript
复制
 package main
 ​
 import (
   "gorm.io/gorm"
   "gorm.io/driver/sqlite"
 )
 ​
 type Product struct {
   gorm.Model
   Code  string
   Price uint
 }
 ​
 func main() {
   db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
   if err != nil {
     panic("failed to connect database")
   }
 ​
   // 迁移 schema
   db.AutoMigrate(&Product{})
 ​
   // Create
   db.Create(&Product{Code: "D42", Price: 100})
 ​
   // Read
   var product Product
   db.First(&product, 1) // 根据整型主键查找
   db.First(&product, "code = ?", "D42") // 查找 code 字段值为 D42 的记录
 ​
   // Update - 将 product 的 price 更新为 200
   db.Model(&product).Update("Price", 200)
   // Update - 更新多个字段
   db.Model(&product).Updates(Product{Price: 200, Code: "F42"}) // 仅更新非零值字段
   db.Model(&product).Updates(map[string]interface{}{"Price": 200, "Code": "F42"})
 ​
   // Delete - 删除 product
   db.Delete(&product, 1)
 }

模型声明

user模型的示例

代码语言:javascript
复制
 type User struct {
   ID           uint           // Standard field for the primary key
   Name         string         // 一个常规字符串字段
   Email        *string        // 一个指向字符串的指针, allowing for null values
   Age          uint8          // 一个未签名的8位整数
   Birthday     *time.Time     // A pointer to time.Time, can be null
   MemberNumber sql.NullString // Uses sql.NullString to handle nullable strings
   ActivatedAt  sql.NullTime   // Uses sql.NullTime for nullable time fields
   CreatedAt    time.Time      // 创建时间(由GORM自动管理)
   UpdatedAt    time.Time      // 最后一次更新时间(由GORM自动管理)
 }

约定

主键:GORM 使用一个名为ID 的字段作为每个模型的默认主键。

表名:默认情况下,GORM 将结构体名称转换为 snake_case 并为表名加上复数形式。 例如,一个 User 结构体在数据库中的表名变为 users

列名:GORM 自动将结构体字段名称转换为 snake_case 作为数据库中的列名。

时间戳字段:GORM使用字段 CreatedAtUpdatedAt 来自动跟踪记录的创建和更新时间。

连接数据库

目前GORM 官方支持的数据库类型有:MySQL, PostgreSQL, SQLite, SQL Server 和 TiDB,基本把常用数据库都涵盖了。

mysql

代码语言:javascript
复制
 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{})
 }

PostgreSQL

代码语言:javascript
复制
 import (
   "gorm.io/driver/postgres"
   "gorm.io/gorm"
 )
 ​
 dsn := "host=localhost user=gorm password=gorm dbname=gorm port=9920 sslmode=disable TimeZone=Asia/Shanghai"
 db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})

SQLite

代码语言:javascript
复制
 import (
   "gorm.io/driver/sqlite" // Sqlite driver based on CGO
   // "github.com/glebarez/sqlite" // Pure go SQLite driver, checkout https://github.com/glebarez/sqlite for details
   "gorm.io/gorm"
 )
 ​
 // github.com/mattn/go-sqlite3
 db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{})

SQL Server

代码语言:javascript
复制
 import (
   "gorm.io/driver/sqlserver"
   "gorm.io/gorm"
 )
 ​
 // github.com/denisenkom/go-mssqldb
 dsn := "sqlserver://gorm:LoremIpsum86@localhost:9930?database=gorm"
 db, err := gorm.Open(sqlserver.Open(dsn), &gorm.Config{})

TiDB

TiDB 兼容 MySQL 协议。 因此你可以按照 MySQL一节来创建与 TiDB 的连接。

代码语言:javascript
复制
 import (
   "fmt"
   "gorm.io/driver/mysql"
   "gorm.io/gorm"
 )
 ​
 type Product struct {
   ID    uint `gorm:"primaryKey;default:auto_random()"`
   Code  string
   Price uint
 }
 ​
 func main() {
   db, err := gorm.Open(mysql.Open("root:@tcp(127.0.0.1:4000)/test"), &gorm.Config{})
   if err != nil {
     panic("failed to connect database")
   }
 ​
   db.AutoMigrate(&Product{})
 ​
   insertProduct := &Product{Code: "D42", Price: 100}
 ​
   db.Create(insertProduct)
   fmt.Printf("insert ID: %d, Code: %s, Price: %d\n",
     insertProduct.ID, insertProduct.Code, insertProduct.Price)
 ​
   readProduct := &Product{}
   db.First(&readProduct, "code = ?", "D42") // find product with code D42
 ​
   fmt.Printf("read ID: %d, Code: %s, Price: %d\n",
     readProduct.ID, readProduct.Code, readProduct.Price)
 }

创建

创建记录

代码语言:javascript
复制
user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

result := db.Create(&user) // 通过数据的指针来创建

user.ID             // 返回插入数据的主键
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

创建多项纪录

代码语言:javascript
复制
users := []*User{
    {Name: "Jinzhu", Age: 18, Birthday: time.Now()},
    {Name: "Jackson", Age: 19, Birthday: time.Now()},
}

result := db.Create(users) // pass a slice to insert multiple row

result.Error        // returns error
result.RowsAffected // returns inserted records count

你无法向 ‘create’ 传递结构体,所以你应该传入数据的指针.

用指定的字段创建记录

创建记录并为指定字段赋值

代码语言:javascript
复制
db.Select("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`name`,`age`,`created_at`) VALUES ("jinzhu", 18

创建记录并忽略传递给 ‘Omit’ 的字段值

代码语言:javascript
复制
db.Omit("Name", "Age", "CreatedAt").Create(&user)
// INSERT INTO `users` (`birthday`,`updated_at`) VALUES ("2020-01-01 

批量插入

要高效地插入大量记录,可以将切片传递给Create方法。 GORM 将生成一条 SQL 来插入所有数据,以返回所有主键值,并触发 Hook 方法。 当这些记录可以被分割成多个批次时,GORM会开启一个事务</0>来处理它们。

代码语言:javascript
复制
var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)

for _, user := range users {
  user.ID // 1,2,3
}

查询

查询单个对象

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误

代码语言:javascript
复制
// 获取第一条记录(主键升序)
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)

如果你想避免ErrRecordNotFound错误,你可以使用Find,比如db.Limit(1).Find(&user)Find方法可以接受struct和slice的数据。

对单个对象使用Find而不带limit,db.Find(&user)将会查询整个表并且只返回第一个对象,只是性能不高并且不确定的。

First and Last 方法会按主键排序找到第一条记录和最后一条记录 (分别)。 只有在目标 struct 是指针或者通过 db.Model() 指定 model 时,该方法才有效。 此外,如果相关 model 没有定义主键,那么将按 model 的第一个字段进行排序。 例如:

代码语言:javascript
复制
var user User
var users []User

// works because destination struct is passed in
db.First(&user)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// works because model is specified using `db.Model()`
result := map[string]interface{}{}
db.Model(&User{}).First(&result)
// SELECT * FROM `users` ORDER BY `users`.`id` LIMIT 1

// doesn't work
result := map[string]interface{}{}
db.Table("users").First(&result)

// works with Take
result := map[string]interface{}{}
db.Table("users").Take(&result)

// no primary key defined, results will be ordered by first field (i.e., `Code`)
type Language struct {
  Code string
  Name string
}
db.First(&Language{})
// SELECT * FROM `languages` ORDER BY `languages`.`code` LIMIT 1

根据主键检索

如果主键是数字类型,您可以使用 内联条件 来检索对象。 当使用字符串时,需要额外的注意来避免SQL注入;查看 Security 部分来了解详情。

代码语言:javascript
复制
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);

如果主键是字符串(例如像uuid),查询将被写成如下:

代码语言:javascript
复制
db.First(&user, "id = ?", "1b74413f-f3b8-409f-ac47-e8c062e3472a")
// SELECT * FROM users WHERE id = "1b74413f-f3b8-409f-ac47-e8c062e3472a";

当目标对象有一个主键值时,将使用主键构建查询条件,例如:

代码语言:javascript
复制
var user = User{ID: 10}
db.First(&user)
// SELECT * FROM users WHERE id = 10;

var result User
db.Model(User{ID: 10}).First(&result)
// SELECT * FROM users WHERE id = 10;

NOTE: 如果您使用 gorm 的特定字段类型(例如 gorm.DeletedAt),它将运行不同的查询来检索对象。

代码语言:javascript
复制
type User struct {
  ID           string `gorm:"primarykey;size:16"`
  Name         string `gorm:"size:24"`
  DeletedAt    gorm.DeletedAt `gorm:"index"`
}

var user = User{ID: 15}
db.First(&user)
//  SELECT * FROM `users` WHERE `users`.`id` = '15' AND `users`.`deleted_at` IS NULL ORDER BY `users`.`id` LIMIT 1

检索全部对象

代码语言:javascript
复制
// Get all records
result := db.Find(&users)
// SELECT * FROM users;

result.RowsAffected // returns found records count, equals `len(users)`
result.Error        // returns error

String 条件

代码语言:javascript
复制
// Get first matched record
db.Where("name = ?", "jinzhu").First(&user)
// SELECT * FROM users WHERE name = 'jinzhu' ORDER BY id LIMIT 1;

// Get all matched records
db.Where("name <> ?", "jinzhu").Find(&users)
// SELECT * FROM users WHERE name <> 'jinzhu';

// IN
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users)
// SELECT * FROM users WHERE name IN ('jinzhu','jinzhu 2');

// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%jin%';

// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;

// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';

// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';

如果对象设置了主键,条件查询将不会覆盖主键的值,而是用 And 连接条件。 例如: var user = User{ID: 10} db.Where("id = ?", 20).First(&user) // SELECT * FROM users WHERE id = 10 and id = 20 ORDER BY id ASC LIMIT 1 这个查询将会给出record not found错误 所以,在你想要使用例如 user 这样的变量从数据库中获取新值前,需要将例如 id 这样的主键设置为nil。

更新

保存所有字段

Save 会保存所有的字段,即使字段是零值

代码语言:javascript
复制
db.First(&user)

user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

保存 是一个组合函数。 如果保存值不包含主键,它将执行 Create,否则它将执行 Update (包含所有字段)。

代码语言:javascript
复制
db.Save(&User{Name: "jinzhu", Age: 100})
// INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")

db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
// UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE `id` = 1

NOTE不要将 SaveModel一同使用, 这是 未定义的行为

更新单个列

当使用 Update 更新单列时,需要有一些条件,否则将会引起ErrMissingWhereClause 错误,查看 阻止全局更新 了解详情。 当使用 Model 方法,并且它有主键值时,主键将会被用于构建条件,例如:

代码语言:javascript
复制
// 根据条件更新
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;

// User 的 ID 是 `111`
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;

// 根据条件和 model 的值进行更新
db.Model(&user).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;

更新多列

Updates 方法支持 structmap[string]interface{} 参数。当使用 struct 更新时,默认情况下GORM 只会更新非零值的字段

代码语言:javascript
复制
// 根据 `struct` 更新属性,只会更新非零值的字段
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;

// 根据 `map` 更新属性
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

注意 使用 struct 更新时, GORM 将只更新非零值字段。 你可能想用 map 来更新属性,或者使用 Select 声明字段来更新

更新选定字段

如果您想要在更新时选择、忽略某些字段,您可以使用 SelectOmit

代码语言:javascript
复制
// 选择 Map 的字段
// User 的 ID 是 `111`:
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello' WHERE id=111;

db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

// 选择 Struct 的字段(会选中零值的字段)
db.Model(&user).Select("Name", "Age").Updates(User{Name: "new_name", Age: 0})
// UPDATE users SET name='new_name', age=0 WHERE id=111;

// 选择所有字段(选择包括零值字段的所有字段)
db.Model(&user).Select("*").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

// 选择除 Role 外的所有字段(包括零值字段的所有字段)
db.Model(&user).Select("*").Omit("Role").Updates(User{Name: "jinzhu", Role: "admin", Age: 0})

删除

删除一条记录

删除一条记录时,删除对象需要指定主键,否则会触发 批量删除,例如:

代码语言:javascript
复制
// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;

// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

根据主键删除

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字(如以下例子。也可以使用字符串——译者注)。查看 查询-内联条件(Query Inline Conditions) 了解详情。

代码语言:javascript
复制
db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;

db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;

db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

钩子函数

对于删除操作,GORM 支持 BeforeDeleteAfterDelete Hook,在删除记录时会调用这些方法,查看 Hook 获取详情

代码语言:javascript
复制
func (u *User) BeforeDelete(tx *gorm.DB) (err error) {
    if u.Role == "admin" {
        return errors.New("admin user not allowed to delete")
    }
    return
}

批量删除

如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录

代码语言:javascript
复制
db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";

db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";

可以将一个主键切片传递给Delete 方法,以便更高效的删除数据量大的记录

代码语言:javascript
复制
govar users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
db.Delete(&users)
// DELETE FROM users WHERE id IN (1,2,3);

db.Delete(&users, "name LIKE ?", "%jinzhu%")
// DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3); 

到这里就总结的差不多了,这是国人做的项目,所以中文文档很齐全,非常非常推荐去看看官方文档

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Go gorm
    • 什么是orm
      • ORM 优缺点
        • 优点
        • 缺点
      • 安装
        • 演示
          • 模型声明
            • 约定
          • 连接数据库
            • mysql
            • PostgreSQL
            • SQLite
            • SQL Server
            • TiDB
          • 创建
            • 创建记录
            • 创建多项纪录
            • 用指定的字段创建记录
            • 批量插入
          • 查询
            • 查询单个对象
            • 根据主键检索
            • 检索全部对象
            • String 条件
          • 更新
            • 保存所有字段
            • 更新单个列
            • 更新多列
            • 更新选定字段
          • 删除
            • 删除一条记录
            • 根据主键删除
            • 钩子函数
            • 批量删除
        相关产品与服务
        云数据库 MySQL
        腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档