gorm的基本使用
安装gorm
首先需要初始化mod文件
go mod init
这里直接拉取gorm依赖可能会拉取不到,我们可以使用一个可用的国内代理,先设置goproxy,在系统环境变量中设置
变量名:GOPROXY
接着尝试拉取gorm依赖
go get -u gorm.io/gorm
拉取成功可以看到mod文件中出现依赖名称,如果ide中mod文件的依赖名称爆红的话,需要打开设置勾选enable go modules integration
使用gorm连接数据库我们还需要安装一个mysql驱动
go get -u gorm.io/driver/mysql
到这里准备工作结束
gorm连接mysql数据库
package main
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// Product 定义gorm model,类似Java中的实体类
type Product struct {
Code string
Price uint
}
// TableName 为这个实体类添加一个方法返回这个实体类对应数据库的表名。
/*(p Product)函数名前面的括号内容是用来限定该函数运行在那种类型的对象上,之后调用该方法只能通过“对象.函数名”的方式来调用*/
//tableName是gorm的一个接口我们需要为这个model提供一个表名,猜测gorm会通过我们提供的这个表名来找到数据库中对应的表并且将这个表和我们定义的结构体做关联
func (p Product) TableName() string {
return "product"
}
func main() {
//设置连接数据库参数,用户名:数据库密码@tcp(数据库地址:端口号)/数据库名?初始化参数(设置字符串,是否解析时间,时区设置)
dsn := "root:123456@tcp(localhost:3306)/jdbc?charset=utf-8mb4&parseTime=true&loc=Local"
//这里使用gorm.Open来连接数据库,参数我们需要传递一个某个数据库的驱动
//这里我们使用mysql驱动,传入一个dsn也就是一个mysql的数据库连接
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) //这里可以通过gorm.Config来传递一些自定义配置
//判断连接数据库是否出现异常, err!=nil是出现异常err==nil则为无异常
if err != nil {
//panic recover是go中处理错误的方式,panic可以停止程序并直接开始执行defer,然后可以使用recover来恢复程序执行
panic("failed to connect database")
}
//到此就初始化了一个gorm的对象,这个对象可以帮助我们做数据库的基本操作
}
gorm定义model(模型)
gorm中模型就是用来管理数据库中的表。类似Java中的实体类,定义一个模型需要使用到结构体
// Product 定义gorm model,类似Java中的实体类
type Product struct {
Code string
Price uint
}
gorm约定
gorm也是约定大于配置的,在默认情况下模型中默认ID为主键,使用结构体名的 蛇形复数 作为表名(蛇形命名法:使用全小写来命名,在需要分隔的地方使用下划线_),字段名的 蛇形 作为列名,默认使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间。gorm官方认为定义一个标注的model应该包含字段ID、CreatedAt、UpdatedAt、DeletedAt。当传入的参数为0值的时候,这两个时间字段会自动更改
嵌入结构体
对于匿名字段(也就是gorm.Model),GORM 会将其字段包含在父结构体中
type User struct {
gorm.Model
Name string
}
// 等效于
type User struct {
ID uint `gorm:"primaryKey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
Name string
}
对于正常结构体字段,可以使用标签embedded嵌入
type Author struct {
Name string
Email string
}
type Blog struct {
ID int
Author Author `gorm:"embedded"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
Name string
Email string
Upvotes int32
}
在正常结构体中可以使用标签embeddedPrefix来为引用的字段添加前缀以此来区别不同的结构体中相同名字的字段
type Blog struct {
ID int
Author Author `gorm:"embedded;embeddedPrefix:author_"`
Upvotes int32
}
// 等效于
type Blog struct {
ID int64
AuthorName string
AuthorEmail string
Upvotes int32
}
配置gorm的约定配置
当然有的时候我们并不希望gorm把ID作为主键,那么我们可以自定义主键。go的tab标签可以修改我们希望的字段作为主键。使用单反引号gorm:"primaryKey"写在字段后
// 将 `UUID` 设为主键
type Animal struct {
ID int64
UUID string `gorm:"primaryKey"`
Name string
Age int64
}
同样的也可以修改model的默认表名
// TableName 为这个实体类添加一个方法返回这个实体类对应数据库的表名。
/*(p Product)函数名前面的括号内容是用来限定该函数运行在那种类型的对象上,之后调用该方法只能通过“对象.函数名”的方式来调用*/
//tableName是gorm的一个接口我们需要为这个model提供一个表名,猜测gorm会通过我们提供的这个表名来找到数据库中对应的表并且将这个表和我们定义的结构体做关联
func (p Product) TableName() string {
return "product"
}
gorm基本CRUD
//创建数据
//添加单条数据
db.Create(&Product{Code: "sa", Price: 12})
//添加多条数据
db.Create(&[]Product{
{Code: "12", Price: 121},
{Code: "121", Price: 1212},
})
//查询数据
var product Product
//查询单条记录
db.First(&product, 1) //根据整型主键查找
db.First(&product, "code = ?", "12") //根据某个字段(code)进行查找
//查询多条记录
db.Find(&product)
//更新数据update
db.Model(&product).Update("price", 202)
//使用结构体更新多个字段updates
db.Model(&product).Updates(Product{Code: "12", Price: 121}) //在gorm中更新多个字段的情况下只更新非0字段
//使用map更新多个字段
db.Model(&product).Updates(map[string]interface{}{"price": 200, "code": "12"})
//删除数据
db.Delete(&product, 1) //通过id删除数据
创建数据
创建单条数据
//增
//通过指针向数据库传递参数,并且这里可以使用匿名变量。该函数返回值有2个属性
result := db.Create(&User{ID: 1, Name: "Jinzhu", Age: 18})
//如果通过给变量赋值,再使用变量的指针的方式来传递参数,我们可以使用变量名.ID来获得主键id
//可以通过数据库插入数据的函数返回值获得一个err来判断是否插入成功
fmt.Println(result.Error)
//并且可以拿到影响的行数
fmt.Println(result.RowsAffected)
result := db.Create(&User{Name: "Jinzhu11", Age: 18})//当表的主键设置了自增属性并且显示的定义primarykey时,我们传递的结构体可以不用传递ID,它会自动帮助我们填入主键ID
//并且如果我们有结构体变量的时候,可以用过变量来拿到自动填入的ID,因为go会回填主键属性,通过变量可以拿到
批量创建数据
//批量插入数据
db.Create(&[]User{{ID: 2, Name: "dsa", Age: 22}, {ID: 3, Name: "222", Age: 22}})
查询数据
GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。它的底层实现就是会自动在sql中拼接 LIMIT 1 条件。在没有找到记录时,它会返回 ErrRecordNotFound 错误
model定义
type User struct {
ID uint `gorm:"primarykey"`
Name string
Email *string
Age uint8
Birthday *time.Time //数据库格式为date
MemberNumber sql.NullString
ActivatedAt sql.NullTime //数据库格式为datetime
CreatedAt time.Time //数据库格式为datetime
UpdatedAt time.Time //数据库格式为datetime
}
查询单条数据
//用来存放查询出来的数据的变量
user := User{}
res := db.First(&user) //查询第一条记录(按照主键升序)
db.Take(&user) //不指定排序,查询一条记录
db.Last(&user) //查询最后一条记录(按照主键降序)
fmt.Println(res.Error) //检查查询是否成功
fmt.Println(res.RowsAffected) // 查询到的记录数
//根据主键搜索数据
db.First(&user,10)
搜索全部数据
result := db.Find(&users)
更新数据
使用update接口去更新数据的时候,需要我们主动的在链式编程中设置表名,因为update没有去拿到表名。另外他只能更新单个列
//db.Model(&User{})获得表名,Where("age>?", 18)拼接where找到需要更新的那一行数据
db.Model(&User{}).Where("age>?", 18).Update("name", "hello")
更新多个列
//更新多个列
db.Model(&User{ID: 111}).Updates(User{Name: "hello", Age: 18}) //使用结构体
db.Model(&User{ID: 111}).Updates(map[string]interface{}{"name": "hello", "age": 18}) //使用map更新避免0值问题
更新选定的字段
//更新选定的字段
db.Model(&User{ID: 111}).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18})
删除数据
物理删除
//物理删除
db.Delete(&User{}, 10) //删除主键为10的记录
db.Delete(&User{}, []int{1, 2, 3}) //删除主键为1、2、3的记录
//条件表达式
db.Delete(User{}, "email like ?", "%zhujiao%")
db.Where("name like ?", "121").Delete(&User{})
gorm软删除
声明了软删除字段的model调用delete的时候,gorm不会帮我们真正的从数据库中删除记录,它会将deleted设置成当前时间也就是你删除这条数据的时间,并且你不能通过正常的查询方法找到被标记为删除的记录。如果需要查询被标记为软删除的数据,我们需要在链式编程的sql执行函数之前调用Unscoped
type User struct {
ID int `gorm:"primarykey"`
Name string
Age int
Deleted gorm.DeletedAt //gorm提供了一种实现软删除的方式,结构体中需要声明格式为gorm.DeleteAt
}
gorm的一些约定
当结构体中出现ID的字段的时候,gorm会把这个字段自动的当作是数据库的主键
当没有定义TableName函数的时候,gorm会把结构体名的小写复数当作关联的表名,使用字段名的蛇形作为列名使用CreatedAt、UpdatedAt字段作为创建和更新时间
Gorm支持的数据库
gorm会通过数据库的驱动连接数据库,目前支持Mysql、sqlserver等
gorm事务
gorm提供了三种api来支持事务,Begin、Commit、Rollback。
由于go底层对数据库使用的是连接池,每次执行不同的sql都会使用不同的连接,所以我们开启事务会返回一个固定连接的数据库对象,我们需要通过返回的这个对象来执行事务内的sql,建议返回的数据库对象名为tx
/*事务*/
tx := db.Begin() //开启事务
//这里直接在if中执行sql并获取err
if err = tx.Create(&User{Name: "name"}).Error; err != nil {
tx.Rollback() //发生错误则回滚
return
}
//第二条数据
if err = tx.Create(&User{Name: "name1"}).Error; err != nil {
tx.Rollback()
return
}
//两条sql执行成功,提交事务
tx.Commit()