使用GORM连接数据库并实现增删改查操作
一,简介
grom官方文档
gorm是面向golang语言的一种ORM(持久层)框架,支持多种数据库的接入,例如MySQL,PostgreSQL,SQLite,SQL Server,Clickhouse。此框架的特点,弱化了开发者对于sql语言的掌握程度,使用提供的API进行底层数据库的访问。
为什么选择grom?
- 全功能 ORM
- 关联 (Has One,Has Many,Belongs To,Many To Many,多态,单表继承)
- Create,Save,Update,Delete,Find 中钩子方法
- 支持 Preload、Joins 的预加载
- 事务,嵌套事务,Save Point,Rollback To Saved Point
- Context、预编译模式、DryRun 模式
- 批量插入,FindInBatches,Find/Create with Map,使用 SQL 表达式、Context Valuer 进行 CRUD
- SQL 构建器,Upsert,数据库锁,Optimizer/Index/Comment Hint,命名参数,子查询
- 复合主键,索引,约束
- Auto Migration
- 自定义 Logger
- 灵活的可扩展插件 API:Database Resolver(多数据库,读写分离)、Prometheus…
- 每个特性都经过了测试的重重考验
- 开发者友好
我觉得gorm一个非常吸引我的特点在于:
- 约定大于配置大于编码: GORM 倾向于约定,而不是配置。默认情况下,GORM 使用 ID 作为主键,使用结构体名的 蛇形复数 作为表名,字段名的 蛇形 作为列名,并使用 CreatedAt、UpdatedAt 字段追踪创建、更新时间。遵循 GORM 已有的约定,可以减少配置和代码量。如果约定不符合需求,GORM 允许自定义配置它们。
二,tag标签
| 标签名 | 说明 |
|---|---|
| column | 指定 db 表列名 |
| type | 列数据类型,推荐使用兼容性好的通用类型 |
| serializer | 指定如何序列化/反序列化到数据库, e.g: serializer:json/gob/unixtime |
| size | 指定列的数据大小/长度, e.g: size:256 |
| primaryKey | 指定列作为主键 |
| unique | 指定列唯一 |
| default | 指定列为默认值 |
| precision | 指定列的精度 |
| scale | specifies column scale |
| not null | 指定列 NOT NULL |
| autoIncrement | 指定列为自增列 |
| autoIncrementIncrement | auto increment step, controls the interval between successive column values |
| embedded | 嵌入字段 |
| embeddedPrefix | 嵌入字段的列名前缀 |
| autoCreateTime | 记录创建时间 |
| autoUpdateTime | 记录创建/更新时间 |
| index | 根据选项创建索引 |
| uniqueIndex | 唯一索引 |
| check | 创建检查约束, eg: check:age > 13, refer Constraints |
| <- | 设置字段的写权限<-:create create-only field,<-:update update-only field,<-:false no write permission,<- create and update permission |
| -> | 设置字段读权限->:false no read permission |
| - | 忽略当前字段- no read/write permission,-:migration no migrate permission,-:all no read/write/migrate permission |
| comment | 【迁移时】为字段添加注释 |
字段权限
可导出的字段在使用 GORM 进行 CRUD 时拥有全部的权限,此外,GORM 允许用标签控制字段级别的权限。这样就可以让一个字段的权限是只读、只写、只创建、只更新或者被忽略。
创建、更新时间
GORM 约定使用 CreatedAt、UpdatedAt 追踪创建/更新时间。如果定义了这种字段,GORM 在创建、更新时会自动填充当前时间。
要使用不同名称的字段,可以配置 autoCreateTime、autoUpdateTime 标签。
嵌入结构体
可以通过标签 embedded 将一个结构体嵌入另一个结构体。可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀。
创建hook【创建钩子】
GORM 允许用户定义的钩子有 BeforeSave, BeforeCreate, AfterSave, AfterCreate 创建记录时将调用这些钩子方法。
默认值
对于声明了默认值的字段,像 0、''、false 等零值是不会保存到数据库。需要使用指针类
三,使用步骤
一般而言对于数据库的使用步骤如下
- 创建连接。这个步骤一般会有引入一个数据库驱动的概念,我们的代码就是通过这个驱动去操作底层的数据库。
- 利用连接执行sql语句,操作数据库。
- 获取并解析结果。
- 关闭连接。
四,项目中CRUD的实现
-
mysql相关配置(yaml文件)
mysql: username: **** password: ****** ipaddress: ***.*.*.* port: **** dbname: ****type Mysql struct { Username string `yaml:"username"` Password string `yaml:"password"` Ipaddress string `yaml:"ipaddress"` Port string `yaml:"port"` Dbname string `yaml:"dbname"` } //其他配置 var C Config func ConfInit() error { yamlFile, err := os.ReadFile("./config/config.yaml") if err != nil { fmt.Println(err.Error()) return err } // 将读取的yaml文件解析为响应的 struct err = yaml.Unmarshal(yamlFile, &C) if err != nil { fmt.Println(err.Error()) return err } return nil } -
dao层相关代码(user例)
type User struct { ID int64 `gorm:"column:user_id" json:"id"` Name string `gorm:"column:name" json:"name"` FollowCount int64 `gorm:"column:follow_count" json:"follow_count"` FollowerCount int64 `gorm:"column:follower_count" json:"follower_count"` Password string `gorm:"column:password" json:"-"` IsFollow bool `gorm:"column:is_follow" json:"is_follow" ` Avatar string `gorm:"column:avatar" json:"avatar"` BackGroundImage string `gorm:"column:background_image" json:"background_image"` Signature string `gorm:"column:signature" json:"signature"` TotalFavorite int64 `gorm:"column:total_favorited" json:"total_favorited"` WorkCount int64 `gorm:"work_count" json:"work_count"` FavoriteCount int64 `gorm:"column:favorite_count" json:"favorite_count"` VideoLieLists []Video `gorm:"many2many:like;" json:"-"` } func (User) TableName() string { //表名 return "user" } type UserDao struct { } var userDao *UserDao // 单例 接口 表示只创建一次对象 var userOnce sync.Once func GetUserInstance() *UserDao { //创建单例 userOnce.Do(func() { userDao = &UserDao{} }) return userDao } -
crud相关操作(增加)
// AddUser 增加user func (UserDao) AddUser(user *User) error { tx := db.Begin() //开启事务 res := tx.Create(user) err := res.Error if err != nil { tx.Rollback() //事务回滚 return err } tx.Commit() //事务提交 return nil } -
crud相关操作(删除)
ps : 软删除【重点】 如果model包含gorm.deleted_at 字段(gorm.Model 已经包含了该字段),它将自动获得软删除的能力!拥有软删除能力的模型调用 Delete 时,记录不会从数据库中被真正删除。GORM 会将 DeletedAt 置为当前时间,并且不能再通过普通的查询方法找到该记录。(例子中没有体现)
// DeleteUser 删除user func (UserDao) DeleteUser(user *User) error { tx := db.Begin() //开启事务 res := tx.Delete(user) err := res.Error if err != nil { tx.Rollback() //事务回滚 return err } tx.Commit() //事务提交 return nil } -
crud相关操作(修改)
// UpdateFollowCount 更新关注数量 func (UserDao) UpdateFollowCount(userId, count int64) error { tx := db.Begin() //开启事务 err := tx.Model(&User{}).Where("user_id = ?", userId).UpdateColumn("follow_count", gorm.Expr("follow_count + ?", count)).Error if err != nil { tx.Rollback() //事务回滚 return err } tx.Commit() //事务提交 return nil } -
crud相关操作(查询)
查询指定记录 GORM 提供了 First、Take、Last 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误。
如果想避免ErrRecordNotFound错误,可以使用Find,比如db.Limit(1).Find(&user),Find方法可以接受struct和slice的数据。
// QueryUserByID user_id查找用户 func (UserDao) QueryUserByID(userID int64) (*User, error) { var user User tx := db.Begin() //开启事务 err := tx.Where("user_id = ?", userID).Find(&user).Error if err != nil { tx.Rollback() //事务回滚 log.Println(err.Error()) return nil, err } tx.Commit() //事务提交 return &user, nil }
五,service层中的调用
(例)User中的service相关实现
func UserLogin(username string, password string) (*UserRegisInfo, error) {
var err error
var token string
var user *dao.User
//进行md5加密
password = utils.Md5Encryption(password)
user, err = dao.GetUserInstance().QueryUserByName(username)
//判断用户是否存在
if user.ID == 0 {
err = errors.New("user not exists")
return nil, err
}
if err != nil {
return nil, err
}
//验证密码是否正确
if password != user.Password {
err = errors.New("password is wrong")
return nil, err
}
//生成token
token, err = utils.GenerateToken(username, user.ID)
//成功返回
return &UserRegisInfo{Token: token, UserID: user.ID}, nil
}
总结
在Go语言开发中使用GORM带给我最直观的感受是:我们只需关系结构体,操作结构体,无需关注如何操作数据库。
- 优点:提高开发效率
- 缺点:使用反射牺牲性能,牺牲灵活性
GORM保护数据的安全,比如说结构体删除了某个字段,原来在数据库中的表不会删那个字段。结构体指定改了表名,原来生成的表不会被删除。其将有数据安全风险的可能全部规避掉,交由开发人员手动去筛查。这也是一大优势所在