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: 256precision
指定列的精度scale
指定列大小
段也会写入成功,其他库自测autoIncrement
指定列为自动增长autoIncrementIncrement
自动步长,控制连续记录之间的间隔embedded
嵌套字段embeddedPrefix
嵌入字段的列名前缀autoCreateTime
创建时更新时间戳秒数,您可以使用 nano/milli 来追踪纳秒、毫秒时间戳,例如:autoCreateTime:nanoautoUpdateTime
创建/更新时追踪同上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
})