使用GORM连接数据库并实现增删改查操作 | 青训营

156 阅读6分钟

使用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指定列的精度
scalespecifies column scale
not null指定列 NOT NULL
autoIncrement指定列为自增列
autoIncrementIncrementauto 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 约定使用 CreatedAtUpdatedAt 追踪创建/更新时间。如果定义了这种字段,GORM 在创建、更新时会自动填充当前时间。

要使用不同名称的字段,可以配置 autoCreateTimeautoUpdateTime 标签。

嵌入结构体

可以通过标签 embedded 将一个结构体嵌入另一个结构体。可以使用标签 embeddedPrefix 来为 db 中的字段名添加前缀。

创建hook【创建钩子】

GORM 允许用户定义的钩子有 BeforeSave, BeforeCreate, AfterSave, AfterCreate 创建记录时将调用这些钩子方法。

默认值

对于声明了默认值的字段,像 0''false 等零值是不会保存到数据库。需要使用指针类

三,使用步骤

​ 一般而言对于数据库的使用步骤如下

  1. 创建连接。这个步骤一般会有引入一个数据库驱动的概念,我们的代码就是通过这个驱动去操作底层的数据库。
  2. 利用连接执行sql语句,操作数据库。
  3. 获取并解析结果。
  4. 关闭连接。

四,项目中CRUD的实现

  1. 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
    }
    
    
  2. 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
    }
    
  3. 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
    }
    
  4. 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
    }
    
  5. 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
    }
    
  6. 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保护数据的安全,比如说结构体删除了某个字段,原来在数据库中的表不会删那个字段。结构体指定改了表名,原来生成的表不会被删除。其将有数据安全风险的可能全部规避掉,交由开发人员手动去筛查。这也是一大优势所在