GORM框架学习笔记

简介

gorm是面向golang语言的一种ORM(持久层)框架,支持多种数据库的接入,例如MySQL,PostgreSQL,SQLite,SQL Server,Clickhouse。此框架的特点,弱化了开发者对于sql语言的掌握程度,使用提供的API进行底层数据库的访问。

笔记将会使用mysql数据库作为示例进行演示

使用步骤

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

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

连接数据库

其中 dsn 中的 user,pass,dbname 分别替换成你自己的数据库连接账号,密码,以及默认连接的哪个数据库。ip,port 则替换成数据库实例的 ip地址与端口号。

基本用法

import (
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
)

func main() {
  // ex: root:rootpass@tcp(127.0.0.1:3306)/mydb?charset=utf8mb4&parseTime=True&loc=Local&timeout=10s
  dsn := "user:pass@tcp(ip:port)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
  db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
}

进阶用法 - 支持各种高级配置,以及自定义数据库驱动

import (
  "my_mysql_driver"
  "gorm.io/gorm"
)

func main() {
    db, err := gorm.Open(mysql.New(mysql.Config{
      DSN: "gorm:gorm@tcp(127.0.0.1:3306)/gorm?charset=utf8&parseTime=True&loc=Local", // DSN data source name
      DefaultStringSize: 256, // string 类型字段的默认长度
      DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持
      DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引
      DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列
      SkipInitializeWithVersion: false, // 根据当前 MySQL 版本自动配置
      DriverName: "my_mysql_driver",
    }), &gorm.Config{})
}

连接池

数据库操作都是通过连接去执行的,频繁创建与销毁连接,是需要花费较大代价的,因此一般都采用连接池对连接进行复用。GORM 使用 database/sql 维护连接池

sqlDB, err := db.DB()

// SetMaxIdleConns 设置空闲连接池中连接的最大数量
sqlDB.SetMaxIdleConns(10)

// SetMaxOpenConns 设置打开数据库连接的最大数量。
sqlDB.SetMaxOpenConns(100)

// SetConnMaxLifetime 设置了连接可复用的最大时间。
sqlDB.SetConnMaxLifetime(time.Hour)

模型定义

模型定义就是将数据库中的表结构映射为代码层面的model
例如数据库表 user

CREATE TABLE `sys_user_info` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `user_id` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户id',
  `user_name` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '用户名',
  `user_addr` varchar(255) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '住址',
  `user_age` int NOT NULL COMMENT '年龄',
  `user_sex` tinyint NOT NULL DEFAULT '0' COMMENT '性别0男1女',
  `sys_ctime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `sys_utime` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  `is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

对应的model

type BaseModel struct {
   Id       int64     `gorm:"primary_key"`
   SysCtime time.Time `gorm:"autoCreateTime"` //在新增记录时可以自动填充当前时间
   SysUtime time.Time `gorm:"autoUpdateTime"` //在新增和更新记录时可以自动填充当前时间
   IsDelete int8
}

type SysUserInfo struct {
   BaseModel
   UserID   string
   UserName string
   UserAddr string
   UserAge  int16
   UserSex  int8
}

func (SysUserInfo) TableName() string {
   //实现TableName接口,以达到结构体和表对应,如果不实现该接口,并未设置全局表名禁用复数,gorm会自动扩展表名为sys_user_infos(结构体+s)
   return "sys_user_info"
}

SQL操作

新增记录

常规指针创建

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

result := db.Create(&user) // 通过数据的指针来创建

user.ID             // 返回插入数据的主键
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

指定某些字段插入

//指定插入某些字段插入
db.Select("Name", "Age", "CreatedAt").Create(&user)
//指定某些字段不插入
db.Omit("Name", "is_delete", "CreatedAt").Create(&user)
//针对,创建时间与更新时间,也可以使用模型定义tag来定义默认值

删除记录

  • 删除操作可以在 model 上绑定一些hook函数,做前置检查,例如不能删除 admin账号等
  • 另外可以在模型定义的时候指定软删除字段,这样在执行删除操作的时候会对应转换为 update 操作
// 根据主键删除 DELETE FROM `sys_user_info` WHERE `sys_user_info`.`id` = 4
config.Db.Delete(&model.SysUserInfo{}, 4)
// 根据其它条件删除 DELETE FROM `sys_user_info` WHERE `user_name` = '猪八戒'
config.Db.Where("user_name", c.Query("user_name")).Delete(&model.SysUserInfo{})

修改记录

  • 修改操作同样可以在model上绑定一些hook函数,例如 BeforeUpdate BeforeSave
  • UpdateColumn/UpdateColumns 可跳过hook 函数与自动更新时间,类似于原生的sql操作
  • 注意在指定修改的模型时,如果将主键字段已经赋值的话一定会加入到where语句中
  • Updates使用的参数是model的话则不会更新默认值,如果需要默认值需要改成map[string]interface{},其中key为数据库中更新的列名称

根据主键修改

user := &model.SysUserInfo{}
user.ID = 1
// UPDATE `sys_user_info` SET `sys_utime`='2021-08-08 16:46:15.752',`user_name`='小麻皮',`user_addr`='深圳' WHERE `id` = 1
config.Db.Model(&user).Updates(model.SysUserInfo{UserName: "小麻皮", UserAddr: "深圳"})

根据其它条件修改

// 根据主键更新多列
user := &model.SysUserInfo{}
user.ID = 1
// 根据其它条件更新 UPDATE `sys_user_info` SET `sys_utime`='2021-08-08 16:46:15.754',`user_name`='小皮球',`user_addr`='深圳' WHERE user_addr = 'shenzhen'
config.Db.Model(&model.SysUserInfo{}).Where("user_addr = ?", "shenzhen").Updates(model.SysUserInfo{UserName: "小皮球", UserAddr: "深圳"})
// Omit 指定忽略Updates中map的指定的那些key更新 UPDATE `sys_user_info` SET `user_age`=18,`sys_utime`='2021-08-08 16:46:15.755' WHERE `id` = 1
config.Db.Model(&user).Omit("user_name").Updates(map[string]interface{}{"user_name": "小小飞", "user_age": 18})
// Select 指定 Updates 中的map只有哪些key更新 UPDATE `sys_user_info` SET `sys_utime`='2021-08-08 16:46:15.756',`user_name`='new_name',`user_age`=47 WHERE `id` = 1
config.Db.Model(&user).Select("user_name", "user_age").Updates(model.SysUserInfo{UserName: "new_name", UserAge: 47})

查询记录

  • scan类似Find都是用于执行查询语句,然后把查询结果赋值给结构体变量,区别在于scan不会从传递进来的结构体变量提取表名。使用 Scan 方法的时候需要我们显示指定数据库的表名。另外回调函数注册的不一样,Find 函数支持更多的 Callback 注入。

普通查询

var userList []*model.SysUserInfo
//返回的是全部字段
config.Db.Where("user_name = ?", userName).Find(&userList)
// 原生 SQL
db.Raw("SELECT * FROM sys_user_info WHERE name = ?", userName).Scan(& userList)

返回指定字段

var userList []*model.SysUserInfo
config.Db.Select("user_name", "id").Where("user_name = ?", userName).Find(&userList)

排序

var userList []*model.SysUserInfo
// 查询后排序
config.Db.Where("user_name = ?", c.Query("user_name")).Order("user_age").Order("sys_ctime desc").Find(&userList)

分页

var userList []*model.SysUserInfo
// 分页查询 SELECT * FROM `sys_user_info` WHERE user_age > 10 LIMIT 3 OFFSET 2;
config.Db.Where("user_age > 10").Offset(2).Limit(3).Find(&userList)

group 与 Having

type Result struct {
   Sex   int8
   Count int
}
var result []*Result
// Model 一定要指定,否则会以默认规则 results 为表名查找,或者 struct 绑定方法 TableName
config.Db.Model(&model.SysUserInfo{}).Select("user_sex as Sex", "count(*) as count").Group("user_sex").Having("count(*) > 1").Find(&result)

distinct

// distinct SELECT DISTINCT `user_name` FROM `sys_user_info` WHERE user_sex = 1
config.Db.Where("user_sex = 1").Distinct("user_name").Find(&userList)

join

var userList []*model.SysUserInfo
// join SELECT a.user_name,b.user_age FROM sys_user_info a left join sys_user_info b on a.user_id = b.user_id WHERE `a`.`user_name` = '猪八戒'
config.Db.Table("sys_user_info a").Select("a.user_name", "b.user_age").Joins("left join sys_user_info b on a.user_id = b.user_id").Where("a.user_name", c.Query("user_name")).Scan(&userList)

调试sql

有时候只是想知道当前的数据库操作的最终执行sql是什么样的,却不想直接影响数据库的数据可以使用如下方式

// 需要开启 DryRun: true 配置选项 开启这个配置所有sql都不会执行
DB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{DryRun: true})
statement := DB.Where("user_age", 25).Delete(&model.SysUserInfo{}).Statement

log.Printf("del statement dryRun 只打印sql,不真正执行:%+v\n", statement.SQL.String())

总结

掌握这些基本用法,日常开发已经足够,还有许多进阶用法参考官网 中文文档 更多例子请参考工程 代码

你可能感兴趣的:(golang,数据库,mysql,go,go语言,orm)