gorm数据库操作

安装

go get -u gorm.io/gorm
go get -u gorm.io/driver/sqlite sqlite官方驱动使用C实现,所以要在目标平台编译
github.com/glebarez/sqlite //如果要交叉编译可以使用纯go实现的sqlite

简易示范

在项目下创建/models/inti.go初始化gorm:

package models  //定义在models包下

import (
    "gorm.io/gorm"
    "gorm.io/driver/sqlite"
    //"github.com/glebarez/sqlite" //要交叉编译可以使用纯go实现的sqlite
)

//定义一个常量用来操作数据库,常量就是初始化后返回的db
var DB = Init()

//初始化数据库连接
func Init() *gorm.DB {
    //如果要sqlite支持外键,使用sqlite.Open("test.db?_fk=1")参数启用数据库层外键约束
    db, err := gorm.Open(sqlite.Open("test.db"),&gorm.Config{})
    if err != nil {
        panic("无法连接数据库")
    }
    // 设置连接池,如果不设置默认就是2个空连接
    sqlDB, err := db.DB()
    if err != nil {
        panic("无法设置连接池")
    }
    sqlDB.SetMaxIdleConns(5) // 设置最大空闲连接数
    sqlDB.SetMaxOpenConns(20) // 设置最大打开连接数
    sqlDB.SetConnMaxIdleTime(time.Minute * 1) // 设置连接空闲时间
    sqlDB.SetConnMaxLifetime(time.Hour) // 设置连接的最大存活时间
    // 自动创建表
    db.AutoMigrate(&User{})
    return db
}

创建/models/model.go文件用于声明模型:

package models  //定义在models包下

import (
    "time"
)

//定义一个User的数据库模型
type User struct {
    ID           uint  `gorm:"primaryKey"`//id
    Name         string  //用户名
    Email        string  //邮箱
    Password	string  //密码
    CreatedAt time.Time  //创建时间
  }

在其他地方调用:

import (
    "GinDemo/models"  //假设GinDemo是项目名,导入models目录下的包
    "github.com/gin-gonic/gin" 
)

//grom写入数据
func Grom_xie(c *gin.Context){
    models.DB.Create(&models.User{Name: "测试",Email: "13320807@qq.com", Password:"124566",CreatedAt: time.Now()})
    c.String(200,"写入成功")
}

//grom读取数据
func Grom_du(c *gin.Context) {
    // //定义一个变量为要查询的结构体,只能接收一个结果
    // var user models.User
    name := c.Param("name") //获取路由参数name的值
    // //First方法查询一条数据
    // jieguo := models.DB.First(&user,"name = ?",name)  //搜索条件为name字段,搜索值为传递的路由参数
    
    // if jieguo.Error != nil {  //如果error不为空,则获取失败,可能查询到空值
    // 	c.String(200,"查询失败")  //返回查询失败
    // 	return
    // }

    // c.JSON(200,user)  //查询到数据后以json返回整个结构
    // c.String(200,user.Name)  //返回数据的单个字段

    var user2 []models.User  //定义一个能接收多条数据的数组变量
    // //查询user表的所有数据
    // models.DB.Find(&user2)  //Find方法用来查询所有数据

    // Where方法指定条件查询数据,后面跟随Find来获取多条数据,跟随First则获取一条数据,都是返回数组
    models.DB.Where("name = ?", name).Find(&user2) 

    c.JSON(200,user2)  //查询到数据后以json返回整个结构

}

模型定义gorm参数

用法:gorm:"unique;not null;default:0"
column 指定 db 列名
type 列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null、size, autoIncrement… 像 varbinary(8),gorm:"type:char(60); 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INCREMENT

primaryKey 将列定义为主键
unique 将列定义为唯一键
uniqueIndex 与 index 相同,但创建的是唯一索引
uniqueIndex:idx_foreign_key_kami 复合唯一索引,就是多个字段不能重复,例如itemID字段和kami两个字段,对于同一个itemID中的kami不能重复,反之则可以,:idx_foreign_key_kami是索引名字,把这个代码添加到想要的字段中去即可
not null 指定列为 NOT NULL,默认允许空,我在sqlite下如果不传递该字
default 定义列的默认值
size 定义列数据类型的大小或长度,例如 size: 256
precision 指定列的精度
scale 指定列大小

段也会写入成功,其他库自测
autoIncrement 指定列为自动增长
autoIncrementIncrement 自动步长,控制连续记录之间的间隔
embedded 嵌套字段
embeddedPrefix 嵌入字段的列名前缀
autoCreateTime 创建时更新时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nano
autoUpdateTime 创建/更新时追踪同上
index 根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情

serializer 指定将数据序列化或反序列化到数据库中的序列化器, 例如: serializer:json/gob/unixtime

check 创建检查约束,例如 check:age > 13,查看 约束 获取详情
<- 设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限
-> 设置字段读的权限,->:false 无读权限
- 忽略该字段,- 表示无读写,-:all 表示无读写迁移权限
-:migration 不迁移,但可访问,在多语言环境下有奇效
comment 迁移时为字段添加注释

复合唯一索引示范

type Ziyingkamis struct {
    ID       uint   `gorm:"primaryKey"`       //自增id
    SkusID  uint   `gorm:"index;not null;uniqueIndex:idx_foreign_key_kami"`//对应sku
    Kahao string `gorm:"size:1000"` //卡号
    Kami string `gorm:"not null;size:1000;uniqueIndex:idx_foreign_key_kami"` //卡密
    Daoqi_date *time.Time `gorm:"column:daoqi_date"`//到期时间
    CreatedAt time.Time `gorm:"autoCreateTime"`  //创建时间,就是写入时间
}

写入

db.Create(&User{Name: "测试",Email: "13320807@qq.com", Password:"124566",}) //直接写入一条数据

//也可以这样写

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user) // 通过数据的指针来创建
user.ID             // 返回插入数据的主键
result.Error        // 返回 error

//插入多条数据

users := []*User{
    User{Name: "Jinzhu", Age: 18, Birthday: time.Now()},
    User{Name: "Jackson", Age: 19, Birthday: time.Now()},
}

result := db.Create(users) // 传递切片以插入多行数据
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

只返回特定字段

//只返回id和Name_zh_hans字段
model.DB.Select("id", "Name_zh_hans").Find(&items)
//最多返回12条数据,和特定字段
model.DB.Select("id", "Name_zh_hans").Limit(12).Find(&items)

查询DB内联直接方法

//读数据需要先定义一个结构体接收变量
db.First() //返回一条数据
db.Find() //返回列表,使用此方法需要变量是数组
db.First(&user, 1) //直接根据主键查找
db.First(&user, "name = ?", "jinzhu") //根据条件查找
db.Find(&users, "name <> ? AND age > ?", "jinzhu", 20) //匹配多个字段条件,?是占位符,后面的参数依次代表占位符

查询DB.Where条件方法

db.Where("name = ?", "jinzhu").First(&user) //精准匹配
db.Where("name <> ?", "jinzhu").Find(&users) //不等于
db.Where("name IN ?", []string{"jinzhu", "jinzhu 2"}).Find(&users) //在列表中
db.Where("name LIKE ?", "%jin%").Find(&users) //模糊匹配
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users) //多个字段匹配
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users) //比较范围匹配,只能是时间,数值可比较大小的类

查询Or方法

db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users) //两个字段,符合一个

分页查询

db.Limit(pageSize).Offset(offset).Find(&users) //Limit是每页显示的条数,Offset是从哪条开始

查询结果字段重命名,并只返回指定字段

//查询只返回中文字段,并将name_zh_hans映射到name,返回12条数据
    model.DB.Select("id", "name_zh_hans as name").Limit(12).Find(&items)
    log.Print(items)

查询多个值,或

//查询zhuangtai为"df"或"yf"
result := model.DB.Where("users_id = ? AND zhuangtai IN (?)", userid,[]string{"df", "yf"}).Find(&orders)

根据结果查询,返回指定条数数据,并设置排除条件

var results []YourModel
// 查询class字段值为game,并排除 id=2 的记录,返回4条数据
db.Where("class = ?", "game").Not("id", 2).Limit(4).Find(&results)

升序降序

//按id字段升序排序
DB.Order("id").First(&tron, "usdt_zhanyong = ?", true)

//按id降序排序
DB.Order("id DESC").First(&tron, "usdt_zhanyong = ?", true)

查询列表并统计和计算

var carts []model.Carts //购物车变量
var allNumber int64 //接收统计结果

//查询某用户的购物车,并统计所有购物车的number字段的和,将结果保存在allNumber
model.DB.Model(&carts).Where("users_id = ?", userid).Select("sum(number)").Row().Scan(&totalNumber)
log.Print("总购物车商品数:",allNumber)

更新数据

//更新数据需要先定义一个结构体接收变量
db.Model(&user).Where("id = ?", 5).Update("name", "小明") //直接根据条件更新一个字段的数据
db.Model(&user).Where("id = ?", 5).Updates(User{Name: "hello", Age: 18, Active: false}) //更新多个字段数据

//先查询再更新

db.First(&user, 1)  //根据条件查询到一条数据
user.Name = "jinzhu 2"  //修改这条数据的某字段
user.Age = 100  //修改字段2
db.Save(&user)  //保存修改


//查询是否有可用ERC20-USDC钱包
//先查询一条数据
var btc model.Btc_qianbaos
result := model.DB.Order("id").First(&btc,"zhanyong = ?",true)
//如果结果是0则代表没有地址可用
if result.RowsAffected == 0 {
    log.Print("USDT-Polygon钱包不足:",result.Error)
    return "", result.Error
}

//占用钱包地址
//然后再更新这条数据
if err := model.DB.Model(&btc).Update("zhanyong", false).Error; err != nil {
    log.Print("USDT-Polygon钱包占用失败:",err)
    return "",err
}

删除数据

db.Where("name = ?", "小明").Delete(&user) //根据条件直接删除一条数据,此方法需要先定义一个结构体接收变量
db.Delete(&User{}, 10) //根据主键直接删除一条数据,无需定义变量

db.Delete(&cart, "users_id = ?", userid) //删除查询到的所有数据
//先查询再删除

db.First(&user, "name = ?", "jinzhu")  //根据条件查询到一条数据,需要定义结构体变量
db.Delete(&user)  //删除查询到的数据

事务

db.Transaction(func(tx *gorm.DB) error {
  // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // 返回任何错误都会回滚事务
    return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    return err
  }

  // 返回 nil 提交事务,返回err是必要条件,如果没有也要返回空
  return nil
})

外键

//模型中定义外键:

//定义一个User的数据库模型
type User struct {
    ID           uint  `gorm:"primaryKey"`//id
    Name         string  //用户名
    Email        string  `gorm:"unique;not null"`//邮箱,唯一,允许空
    Password	string  //密码
    CreatedAt time.Time  //创建时间,时间如果不指定,则会自动填充当前时间
    Order []Order  //一对多关联,如果是一对一,此处不需要
  }

//定义一个用户对多个订单的外键模型
type Order struct {
    ID   int `gorm:"primaryKey"`
    Spname string  //商品名
    Number int  //金额
    CreatedAt time.Time  //创建时间
    UserID int  //主键ID
    //指定为User模型的外键,该字段在创建数据时不用提交数据
    //一对一关联才需要这段
    // User   User  `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;"`  //指定外键约束,更新和删除,若主键删除则将它指定为空
    // UserEmail string
    // User   User `gorm:"foreignKey:UserEmail"` //使用UserEmail为外键
}

//查询时引用外键数据

var user User //定义一个查询用户的结构体
db.Preload("Order").First(&user, 2) //查询用户2的时候预取它的order的所有数据
//然后可以用user.Order来访问它的订单对象了,要当心是列表还是单个

db.Preload("Order", "spname = ?", "商品名").First(&user,2)//查询外键条件:spname是商品名的所有订单

Preload可以使用多次来获取多个外键,其结果都会返回在json字段中
他会返回类似下面的一个对象:
//其中ID2就是查询的用户id2,而order单独以一个数组字段的类型存在,里面包含了它的订单字段信息

{
    "ID": 2,
    "Name": "测试",
    "Email": "13121@556.com",
    "Password": "1233",
    "CreatedAt": "2023-11-10T23:55:58.3833548+08:00",
    "Order": [{
        "ID": 1,
        "Spname": "测试商品",
        "Number": 50,
        "CreatedAt": "2023-11-10T23:58:12.9218222+08:00",
        "UserID": 2
    }, {
        "ID": 2,
        "Spname": "测试商品",
        "Number": 50,
        "CreatedAt": "2023-11-10T23:58:15.532919+08:00",
        "UserID": 2
    }]
}

此时已经可以通过user.Order返回它的外键了,返回类型是一个数组

//关联查询外键

指针,

将字段设置为指针可留空,如果没有传入值就会默认留空

//SKU购物车数据库模型
type Carts struct {
    ID       uint   `gorm:"primaryKey"`       //id
    UsersID  uint   `gorm:"not null"`//对应用户
    SkusID          *uint   `gorm:"skus_id"`//对应sku
    ItemsID          *uint   `gorm:"items_id"`//对应商品
    Skus   Skus   `gorm:"foreignKey:SkusID"`//反查sku
    Items   Items   `gorm:"foreignKey:ItemsID"`//反查sku
    Number  uint16 `gorm:"not null"` //sku名
    Jieshouzhanghao string `gorm:"size:255"` //接收账号
    Dginfo *Dginfo `gorm:"type:jsonb"`//反查dginfo
}

//假设这是某函数内
//因为数据模型对应的是指针,这里传入的也是指针的值
model.Carts{SkusID: &sku.ID, Number: number, UsersID: userid,Jieshouzhanghao:jieshouzhanghao}

连接池

gorm默认是自己管理连接池,也可以手动设置它的参数:

// 获取通用数据库对象 sql.DB ,然后使用其提供的功能
    sqlDB, err := db.DB()
    if err != nil {
        panic("连接池失败")
    }
    //允许空闲连接最大数量
    sqlDB.SetMaxIdleConns(2)

    //打开数据库连接的最大数量。
    sqlDB.SetMaxOpenConns(50)
    //连接可复用的最大时间
    sqlDB.SetConnMaxLifetime(5 * time.Minute) //5分钟

修改并创建数据

修改一条数据,如果没有则会创建它

var user model.Captcha

err := model.DB.Where(model.Captcha{Email: emailjson.Email}).Assign(model.Captcha{Code: service.Captcha(6),Expires: newTime}).FirstOrCreate(&user).Error // 如果不存在名为 "email" 的记录,则创建一个新的记录,并赋值给 user 变量
    if err != nil {
        log.Print("验证码数据库写入失败:",err)
    }

查找一条数据,如果没有则创建它

db.Where(User{Name: "jinzhu"}).Attrs(User{Age: 20}).FirstOrCreate(&user)

事务

db.Transaction(func(tx *gorm.DB) error {
  // 在事务中执行一些 db 操作(从这里开始,您应该使用 'tx' 而不是 'db')
  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
    // 返回任何错误都会回滚事务
    return err
  }

  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
    return err
  }

  // 返回 nil 提交事务
  return nil
})