区块链Two——数据库(3)—Gorm
这应该是学习的第四天了,简单来说一下博客的顺序,也就是我学习的顺序:go语言->区块链(因为实习就知道会用到这个技术就直接学了,其实可以再这之前接触一下以太坊)->Gorm(在学习区块链的时候,用到数据库了所以学习了一下gorm相关知识,因为java有过ORM的基础所以个人认为还是比较好理解的)->区块链(又回到区块链,继续学习,其实在这之前简单学习了一下Rabbimq)
整体来说我的学习路程感觉杂乱无章,我也不知道对不对。明天可能就会简单看一下项目代码,那具体有什么情况可能再去学习的话再来写一写文章。
CRUD:增删改查
1.创建
(1)创建记录
user := User{Name:"Jinzhu", Age:18, Birthday: time.Now()}
db.NewRecord(user) //主键为空返回true
db.Create(&user)db.NewRecord(user) //创建user后返回false
(2)默认值(在gorm tag中定义)
插入SQL会忽略有默认值的字段,并且其值为空。记录插入数据库之后,gorm从数据库加载这些字段的值
type Animal struct{
ID int64
Name string `gorm:"default:'默认值'"` //设置默认值
Age int64
}
上述会将所有的0值字段全部赋成默认值,如果你想避免这种情况可以使用指针或者Scanner/Valuer接口,例如:
<1>使用指针:
type User struct {
ID int64
Name *string `gorm:"default:'默认值'"`
Age int64
}
user := User{Name: new(string), Age: 18))}
db.Create(&user) //此时数据库中该条记录name字段的值就是‘ ’
<2>使用 Scanner/Valuer:
type User struct { // 使用 Scanner/Valuer
ID int64
Name sql.NullString `gorm:"default:'默认值'"` // sql.NullString 实现了Scanner/Valuer接口
Age int64
}
user := User{Name: sql.NullString{"", true}, Age:18}
db.Create(&user) // 此时数据库中该条记录name字段的值就是‘ ’
(3)在Callbacks(回调函数)中设置主键
func (user *User) BeforeCreate (scope *gorm.Scope) error{
scope.SetColumn("ID", uuid.New()) //设置主键
return nil
}
(4)扩展创建选项(有则更新,无则插入)
db.Set("gorm:insert_option","ON CONFLICT").Create(&product)
2.查询
(1)基本查询
db.First(&user) //获取第一条记录,按主键排序
db.Last(&user) //获取最后一条记录,按主键排序
db.Find(&users) //获取所有记录
db.First(&user,10) //使用主键获取记录,id=10
(2)Where查询条件
<1>简单SQL
db.Where("name = ?","memgyu").First(&user) //查询第一个匹配的数据
db.Where("name = ?","mengyu").Find(&users) //查询所有匹配的数据
db.Where("name <> ?","mengyu").Find(&users) //查询name不是mengyu的数据
db.Where("name in (?)", []string{"mengyu1","mengyu 2"}).Find(&users) //查询出name在(mengyu1,mengyu2)所有数据
db.Where("name LIKE ?","%jin%").Find(&users) //模糊查询
db.Where("name = ? AND age >= ?","mengyu","22").Find(&users) //查询name=mengyu并且age>=22的数据
db.Where("updated_at > ?", lastWeek).Find(&users) //查询上一周的数据
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users) //查询上一周到今天的数据
<2>Struct & Map(Struct查询只查询有值的字段)
db.Where(&User{Name:"mengyu", Age:20}).First(&user) //Struct:查询name=mengyu,age=20,从第一条数据开始查
db.Where(map[string]interface{}{"name":"mengyu","age":20}).Find(&users) //Map:查询name=mengyu,age=20的数据
db.Where([]int64{20,21,22}).Find(&users) //多主键slice查询:查询id=20,21,22的数据(数据量小的时候查询速度比map快)
(3)Not条件查询
db.Not("name","mengyu").First(&user) //查询name≠mengyu的第一条数据
db.Not("name", []string{"mengyu1","mengyu2"}).Find(&users) //查询name≠memgyu1和mengyu2的数据
db.Not([]int64{1,2,3}).First(&user) //查询id为1、2、3的数据
db.Not([]int64{}).First(&user) //查询所有数据
db.Not("name = ?","mengyu").First(&user) //查询name≠mengyu的数据
db.Not(User{Name:"mengyu"}).First(&user) //查询name≠mengyu的数据
(4)带内联条件的查询
db.First(&user,23) //查询id=23的第一条数据
db.Find(&user,"name = ?","mengyu") //查询name=mengyu的数据
db.Find(&users,"name <> ? AND age > ?","mengyu",20) //查询name≠mengyu并且age=20的数据
db.Find(&users, User{Age:20}) //struct查询:查询age=20的数据
db.Find(&users,map[string]interface{}{"age":20}) //Map查询:查询age=20的数据
(5)Or条件查询
db.Where("role = ?","admin").Or("role = ?","super_admin").Find(&users) //查询角色为admin或者super_admin的角色
db.Where("name = 'mengyu1'").Or(User{Name:"mengyu2"}).Find(&users) ///struct查询:查询name=mengyu1或mengyu2的数据
db.Where("name = 'mengyu1'").Or(map[string]interface{}{"name":"mengyu 2"}).Find(&users) //Map查询:查询name=mengyu1或mengyu2的数据
(6)查询链(使用gorm自带的api)
db.Where("条件","条件的值").Where/Or/Not(...).Find(&表名)
(7)扩展查询选项
db.Set("gorm:query_option","FOR UPDATE").First(&user,10) // 若id=10有数据,则row lock(行锁)
(8)FirstOrInit(只适用Struct和Map)
db.FirstOrInit(&user, User{Name: "non_existing"}) //没查询到匹配的数据则初始化
db.FirstOrInit(&user,map[string]interface{}{"name":"mengyu"}) //获取匹配到的第一条数据
(9)Attrs
db.Where(User{Name:"mengyu"}).Attrs(User{Age:18}).FirstOrInit(&user)//没找到数据则初始化,找到则显示
(10)Assign
db.Where(User{Name:"mengyu"}).Assign(User{Age:20}).FirstOrInit(&user) //将age=20分配给name=mengyu的数据(不论找没找到)
(11)FirstOrCreate(只适用Struct和Map)
db.FirstOrCreate(&user, User{Name:"mengyu"}) //没有获取到,则创建新信息
db.Where(User{Name:"mengyu"}).FirstOrCreate(&user) //找到了则获取第一条name=mengyu的数据
(12)Select
db.Table("users").Select("COALESCE(age,?)",42).Rows() //返回第一个age=42并且非空的值
(13)Order
db.Order("age desc").Order("name").Find(&users) //按照年龄降序(asc为升序;desc为降序)查询数据的age和name
(14)Limit
db.Limit(10).Find(&users1).Limit(-1).Find(&users2) //查询user1的前10条数据;查询user2所有数据(Limit(-1)用来取消之前的限制条件)
(15)Offset
db.Offset(10).Find(&users1).Offset(-1).Find(&users2) ///查询user1的跳过前10条数据;查询user2所有数据(Offset(-1)用来取消之前的限制条件)
(16)Count
db.Model(&User{}).Where("name = ?","mengyu").Count(&count) //查询name=mengyu的数据有多少条
(17)还有一部分关键字,我没怎么太接触,打算以后遇到了再仔细研究。想看的小伙伴可以看原版
(17)指定表名
db.Table("users1").CreateTable(&User{}) //使用user结构体定义创建users1表
var users1 []User //来一个user1
db.Table("users1").Where("name = ?","mengyu").Delete() //查询name=mengyu的数据
3.预加载
(1)预加载
预加载是指暂时下载,是储存在你电脑的隐藏文件夹里的,当它的储存量超过一定数值时,会抵消最开始的缓存
db.Preload("Orders","state NOT IN (?)","cancelled").Find(&users) //从表Orders找到id=1,2,3,4并且列(state)≠cancelled进行预加载
(2)自定义预加载SQL
可以通过传递func(db *gorm.DB) *gorm.DB来自定义预加载SQL
db.Preload("Orders",func(db *gorm.DB)*gorm.DB{returndb.Order("orders.amount DESC")}).Find(&users) //从表Orders查询user_id=1,2,3,4并且降序排列进行预加载
(3)嵌套加载
db.Preload("Orders.OrderItems").Find(&users)
db.Preload("Orders","state = ?","paid").Preload("Orders.OrderItems").Find(&users)
4.更新
(1)更新全部字段
db.First(&user) //获取第一条信息
user.Name ="mengyu" //更新name=mengyu
user.Age =100 //更新age=100
db.Save(&user) //更新全部字段,即使有些字段没有更改
(2)更新更改字段
db.Model(&user).Update("name","hello") //更新name=hello(单个属性)
db.Model(&user).Where("active = ?",true).Update("name","hello") //使用组合条件更新单个属性
db.Model(&user).Updates(map[string]interface{}{"name":"hello","age":18,"actived":false}) //使用`map`更新多个属性,只会更新这些更改的字段
db.Model(&user).Updates(User{Name:"hello", Age:18}) //使用`struct`更新多个属性,只会更新这些更改的和非空白字段
db.Model(&user).Updates(User{Name:"", Age:0, Actived:false}) //struct是不会更新 “ ”、0、false的,因为他们属于类型的空白值。struct不会更新空白值
(3)更新选择的字段
db.Model(&user).Select("name").Updates(map[string]interface{}{"name":"hello","age":18,"actived":false}) //也可以将Select改为Omit,再更新时更新或者忽略
(4)更新更改字段但不进行Callbacks
前面所说的更新操作会执行模型的BeforeUpdate, AfterUpdate方法,更新其UpdatedAt时间戳,在更新时保存它的Associations,如果不想调用它们,可以使用UpdateColumn, UpdateColumns
db.Model(&user).UpdateColumn("name","hello") //更新单/多个属性时与update类似
(5)Batch Updates 批量更新
Callbacks(回调函数在最后面做了笔记)在批量更新时不会运行
db.Table("users").Where("id IN (?)", []int{10,11}).Updates(map[string]interface{}{"name":"hello","age":18}) //就正常更新
db.Model(User{}).Updates(User{Name:"hello", Age:18}) //Struct更新:适用非零值,也可以用map[string]interface{}
db.Model(User{}).Updates(User{Name:"hello", Age:18}).RowsAffected //RowsAffected获取更新条数
(6)在Callbacks中更改更新值
如果要使用BeforeUpdate, BeforeSave更改回调中的更新值,可以使用scope.SetColumn,例如
func(user *User)BeforeSave(scope *gorm.Scope)(err error){
if pw, err := bcrypt.GenerateFromPassword(user.Password,0); err ==nil{
scope.SetColumn("EncryptedPassword", pw)
}
}
(7)额外更新选项
db.Model(&user).Set("gorm:update_option", "OPTION (OPTIMIZE FOR UNKNOWN)").Update("name, "hello") //添加额外的SQL
5.删除/软删除
(1)单个删除
删除记录时,需要确保其主要字段有值,GORM将使用主键删除记录,如果主要字段为空,GORM将删除模型的所有记录
db.Delete(&email) //删除存在的记录
(2)批量产出
db.Where("email LIKE ?","%mengyu%").Delete(Email{}) //删除所有email=mengyu的数据
db.Delete(Email{},"email LIKE ?","%jinzhu%") //也可以这么表达
(3)软删除
如果模型有DeletedAt字段,它将自动获得软删除功能。在调用Delete时不会从数据库中永久删除,只将字段DeletedAt的值设置为当前时间。
db.Where("age = ?",20).Delete(&User{}) //软删除了所有age=20的数据
db.Where("age = 20").Find(&user) //查询软删除(查询结果为null),软删除的数据查询时候被忽略
db.Unscoped().Where("age = 20").Find(&users) // Unscoped查找软删除记录
db.Unscoped().Delete(&order) //Unscoped永久删除
6.关联
默认情况下,当创建/更新记录时,GORM将保存其关联,如果关联具有主键,GORM将调用Update来保存它,否则将被创建。
(1)tag设置跳过保存关联
type User struct{
gorm.Model
Name string
CompanyID uint
Company Company `gorm:"save_associations:false"` //跳过保存关联
}
type Company struct{
gorm.Model
Name string
}
7.Callbacks
回调方法:模型结构的指针,在创建,更新,查询,删除时将被调用,如果任何回调返回错误,gorm将停止未来操作并回滚所有更改。
(1)创建对象过程中可用的回调
// begin transaction
开始事物BeforeSaveBeforeCreate
//save before associations 保存前关联
//update timestamp `CreatedAt`, `UpdatedAt` 更新`CreatedAt`, `UpdatedAt`时间戳
//save self 保存自己
//reload fields that have default value and its value is blank 重新加载具有默认值且其值为空的字段
//save after associations 保存后关联
AfterCreate
AfterSave
//commit or rollback transaction 提交或回滚事务
(2)更新对象过程中可用的回调
//begin transaction
开始事物BeforeSaveBeforeUpdate
//save before associations 保存前关联
//update timestamp `UpdatedAt` 更新`UpdatedAt`时间戳
//save self 保存自己
//save after associations 保存后关联
AfterUpdate
AfterSave
//commit or rollback transaction 提交或回滚事务
(3)删除对象过程中可用的回调
//begin transaction
开始事物BeforeDelete
//delete self 删除自己
AfterDelete
//commit or rollback transaction 提交或回滚事务
(4)查询对象过程中可用的回调
//load data from database 从数据库加载数据
//Preloading (edger loading) 预加载(加载)
AfterFind
(5)举个栗子~
func (u *User) BeforeUpdate() (err error){
if u.readonly() {
err = errors.New("read only user")
}
return
}
func (u *User) AfterCreate() (err error) { //如果用户ID大于1000,则回滚插入
if(u.Id >1000) {
err = errors.New("user id is already greater than 1000")
}
return
}
gorm中的保存/删除操作正在事务中运行,因此在该事务中所做的更改不可见,除非提交。 如果要在回调中使用这些更改,则需要在同一事务中运行SQL。 所以你需要传递当前事务到回调,像这样:
func (u *User) AfterCreate (tx *gorm.DB) (err error) {
tx.Model(u).Update("role","admin")
return
}
func (u *User) AfterCreate (scope *gorm.Scope) (err error) {
scope.DB().Model(u).Update("role","admin")
return
}