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;