GORM的学习与使用 | 青训营

133 阅读10分钟

gorm框架

Gorm是一个已经迭代了10年+的功能强大的ORM框架,拥有非常丰富的开源扩展。 Gorm 是 Golang 的一个 orm 框架。ORM 是通过实例对象的语法,完成关系型 数据库的操作,是"对象-关系映射"(Object/Relational Mapping) 的缩写。使用 ORM 框架可以让我们更方便的操作数据库。

Gorm官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server

作者是中国人,中文文档齐全,对开发者友好,支持主流数据库。

什么是orm

跟python的peewee一样的概念

peewee框架:blog.csdn.net/the_shy_fak…

ORM全称是:Object Relational Mapping(对象关系映射),其主要作用是在编程中,把面向对象的概念跟数据库中表的概念对应起来。举例来说就是,我定义一个对象,那就对应着一张表,这个对象的实例,就对应着表中的一条记录。 对于数据来说,最重要最常用的是表∶表中有列,orm就是将一张表映射成一个类,表中的列映射成类中的一个类。java .、 python,但是针对go语言而言,struct,就是列如何映射,是因为列可以映射成struct中的类型,int->int,但是有另一个问题?就是数据库中的列具备很好的描述性,但是struct有tag。执行sql,需要我们有足够的sql语句基础、需要我们懂得不同的数据的sql。

orm的优缺点

优点:

  • 提高了开发效率。
  • 屏蔽sql细节。可以自动对实体Entity对象与数据库中的Table进行字段与属性的映射;不用直接SQL编码
  • 屏蔽各种数据库之间的差异 缺点:
  • orm会牺牲程序的执行效率和会固定思维模式
  • 太过依赖orm会导致sql理解不够
  • 对于固定的orm依赖过重,导致切换到其他的orm代价高

特征:

  • 全功能ORM
  • 关联(包含一个,包含多个,属于,多对多)
  • Callbacks(创建/保存/更新/删除/查找前后回调)
  • 预加载
  • 事务
  • 复合主键
  • SQL Builder(执行原生sql)
  • 自动迁移
  • 日志

gorm的基本使用

  1. 定义gorm model

    第二个为详细定义,格式固定为gorm:"column : 设置/值",感觉已经赶得上建表语句了。

    type Product struct{
        Code string
        Price uint
    }
    ​
    type User struct {
      gorm.Model
      Name string  `gorm:"size:255"` //string默认长度255,size重设长度
      Age int `gorm:"column:my_age"` //设置列名为my_age
      Num int  `gorm:"AUTO_INCREMENT"` //自增
      IgnoreMe int `gorm:"-"` // 忽略字段
      Email string `gorm:"type:varchar(100);unique_index"//type设置sql类型,unique_index为该列设置唯一索引`
      Address string `gorm:"not null;unique"` //非空
      no string `gorm:"index:idx_no"` // 创建索引并命名,如果有其他同名索引,则创建组合索引
      code string `gorm:"index:idx_no"
      `
      Profile Profile `gorm:"ForeignKey:ProfileID;AssociationForeignKey:Refer"` //设置外键
      ProfileID int
      remark string `gorm:"default:'test'"` //默认值
    }
    
  2. 给model绑定表名

    func (p Product) TableName() string{
        return "product"
    }
    
  3. 连接数据库

    db, err := gorm.Open(
        mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"),
        &gorm.Config{}
    )
    if err != nil{
        panic("failed to connect database")
    }
    
  4. 增删改查

    //增加
    db.Create(&Product{
        Code : "666",
        Price : 20
    })
    ​
    //查找
    db.First(&Product, 1)       //根据整形主键查找
    db.First(&Product, "code = ? " , "D42")
    //修改
    db.Model(&Product).Update("Price", 200)
    db.Model(&Product).Updates(Product{Price : 200, Code : "666"})
    db.Model(&Product).Updates(map[string]interface{}{"Price":200, "Code" : "666"})
    ​
    //删除
    db.delete(&Product, 1)
    

唯有create方法需要&符。

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
​
db.NewRecord(user) // => 主键为空返回`true`
​
db.Create(&user)
​
db.NewRecord(user) // => 创建`user`后返回`false`

创建多条:

users := []*User{{Name: "Jin2zhu", Age: 18, Birthday: time.Now()},{Name: "Jin1zhu", Age: 18, Birthday: time.Now()}}
​
db.Create(users)

删除

// 删除存在的记录
db.Delete(&email)
//// DELETE from emails where id=10;
批量删除
db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
//// DELETE from emails where email LIKE "%jinhu%";
​
db.Delete(Email{}, "email LIKE ?", "%jinzhu%")
//// DELETE from emails where email LIKE "%jinhu%";
软删除

如果模型有DeletedAt字段,删除时是软删除

db.Delete(&user)
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE id = 111;
​
// 批量删除
db.Where("age = ?", 20).Delete(&User{})
//// UPDATE users SET deleted_at="2013-10-29 10:23" WHERE age = 20;
​
// 软删除的记录将在查询时被忽略
db.Where("age = 20").Find(&user)
//// SELECT * FROM users WHERE age = 20 AND deleted_at IS NULL;
​
// 使用Unscoped查找软删除的记录
db.Unscoped().Where("age = 20").Find(&users)
//// SELECT * FROM users WHERE age = 20;
​
// 使用Unscoped永久删除记录
db.Unscoped().Delete(&order)
//// DELETE FROM orders WHERE id=10;

更新全部字段 Save

db.First(&user)
​
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
​
//// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

更新更改字段 Update Updates

// 更新单个属性(如果更改)
db.Model(&user).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
​
// 使用组合条件更新单个属性
db.Model(&user).Where("active = ?", true).Update("name", "hello")
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111 AND active=true;
​
// 使用`map`更新多个属性,只会更新这些更改的字段
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
//// UPDATE users SET name='hello', age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
​
// 使用`struct`更新多个属性,只会更新这些更改的和非空白字段
db.Model(&user).Updates(User{Name: "hello", Age: 18})
//// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
​
// 警告:当使用struct更新时,FORM将仅更新具有非空值的字段
// 对于下面的更新,什么都不会更新为"",0false是其类型的空白值
db.Model(&user).Updates(User{Name: "", Age: 0, Actived: false})

更新选择字段

db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
//// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;
​
db.Model(&user).Omit("name").Updates(map[string]interface{}{"name": "hello", "age": 18, "actived": false})
//// UPDATE users SET age=18, actived=false, updated_at='2013-11-17 21:34:10' WHERE id=111;
使用sql表达式更新
DB.Model(&product).Update("price", gorm.Expr("price * ? + ?", 2, 100))
//// UPDATE "products" SET "price" = price * '2' + '100', "updated_at" = '2013-11-17 21:34:10' WHERE "id" = '2';
​
DB.Model(&product).Updates(map[string]interface{}{"price": gorm.Expr("price * ? + ?", 2, 100)})
//// UPDATE "products" SET "price" = price * '2' + '100', "updated_at" = '2013-11-17 21:34:10' WHERE "id" = '2';
​
DB.Model(&product).UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
//// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = '2';
​
DB.Model(&product).Where("quantity > 1").UpdateColumn("quantity", gorm.Expr("quantity - ?", 1))
//// UPDATE "products" SET "quantity" = quantity - 1 WHERE "id" = '2' AND quantity > 1;

// 获取第一条记录,按主键排序
db.First(&user)
//// SELECT * FROM users ORDER BY id LIMIT 1;// 获取最后一条记录,按主键排序
db.Last(&user)
//// SELECT * FROM users ORDER BY id DESC LIMIT 1;// 获取所有记录
db.Find(&users)
//// SELECT * FROM users;// 使用主键获取记录
db.First(&user, 10)
//// SELECT * FROM users WHERE id = 10;

First踩坑注意:

  • 使用 First时,需要注意查询不到数据会返回ErrRecordNotFound。
  • 使用Find查询多条数据,查询不到数据不会返回错误。
where条件查询

where简单sql

// 获取第一个匹配记录
db.Where("name = ?", "jinzhu").First(&user)
//// SELECT * FROM users WHERE name = 'jinzhu' limit 1;
​
// 获取所有匹配记录
db.Where("name = ?", "jinzhu").Find(&users)
//// SELECT * FROM users WHERE name = 'jinzhu';
​
db.Where("name <> ?", "jinzhu").Find(&users)
​
// IN
db.Where("name in (?)", []string{"jinzhu", "jinzhu 2"}).Find(&users)
​
// LIKE
db.Where("name LIKE ?", "%jin%").Find(&users)
​
// AND
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
​
// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
​
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)

where(struct & Map)

// Struct
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 LIMIT 1;
​
// Map
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
//// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
​
// 主键的Slice
db.Where([]int64{20, 21, 22}).Find(&users)
//// SELECT * FROM users WHERE id IN (20, 21, 22);
not条件查询
db.Not("name", "jinzhu").First(&user)
//// SELECT * FROM users WHERE name <> "jinzhu" LIMIT 1;
​
// Not In
db.Not("name", []string{"jinzhu", "jinzhu 2"}).Find(&users)
//// SELECT * FROM users WHERE name NOT IN ("jinzhu", "jinzhu 2");
​
// Not In slice of primary keys
db.Not([]int64{1,2,3}).First(&user)
//// SELECT * FROM users WHERE id NOT IN (1,2,3);
​
db.Not([]int64{}).First(&user)
//// SELECT * FROM users;
​
// Plain SQL
db.Not("name = ?", "jinzhu").First(&user)
//// SELECT * FROM users WHERE NOT(name = "jinzhu");
​
// Struct
db.Not(User{Name: "jinzhu"}).First(&user)
//// SELECT * FROM users WHERE name <> "jinzhu";
select

从数据库检索字段

db.Select("name, age").Find(&users)
//// SELECT name, age FROM users;
​
db.Select([]string{"name", "age"}).Find(&users)
//// SELECT name, age FROM users;
​
db.Table("users").Select("COALESCE(age,?)", 42).Rows()
//// SELECT COALESCE(age,'42') FROM users;
Order

从数据库检索记录时指定顺序

db.Order("age desc, name").Find(&users)
//// SELECT * FROM users ORDER BY age desc, name;
​
// Multiple orders
db.Order("age desc").Order("name").Find(&users)
//// SELECT * FROM users ORDER BY age desc, name;
​
// ReOrder
db.Order("age desc").Find(&users1).Order("age", true).Find(&users2)
//// SELECT * FROM users ORDER BY age desc; (users1)
//// SELECT * FROM users ORDER BY age; (users2)
Limit

指定要检索的记录数

db.Limit(3).Find(&users)
//// SELECT * FROM users LIMIT 3;// Cancel limit condition with -1
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
//// SELECT * FROM users LIMIT 10; (users1)
//// SELECT * FROM users; (users2)
Offset

指定在开始返回记录之前要跳过的记录数

db.Offset(3).Find(&users)
//// SELECT * FROM users OFFSET 3;// Cancel offset condition with -1
db.Offset(10).Find(&users1).Offset(-1).Find(&users2)
//// SELECT * FROM users OFFSET 10; (users1)
//// SELECT * FROM users; (users2)
Count

获取模型的记录数

db.Where("name = ?", "jinzhu").Or("name = ?", "jinzhu 2").Find(&users).Count(&count)
//// SELECT * from USERS WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (users)
//// SELECT count(*) FROM users WHERE name = 'jinzhu' OR name = 'jinzhu 2'; (count)
​
db.Model(&User{}).Where("name = ?", "jinzhu").Count(&count)
//// SELECT count(*) FROM users WHERE name = 'jinzhu'; (count)
​
db.Table("deleted_users").Count(&count)
//// SELECT count(*) FROM deleted_users;
Group & Having
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Rows()
for rows.Next() {
    ...
}
​
rows, err := db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Rows()
for rows.Next() {
    ...
}
​
type Result struct {
    Date  time.Time
    Total int64
}
db.Table("orders").Select("date(created_at) as date, sum(amount) as total").Group("date(created_at)").Having("sum(amount) > ?", 100).Scan(&results)
Join

指定连接条件

rows, err := db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Rows()
for rows.Next() {
    ...
}
​
db.Table("users").Select("users.name, emails.email").Joins("left join emails on emails.user_id = users.id").Scan(&results)
​
// 多个连接与参数
db.Joins("JOIN emails ON emails.user_id = users.id AND emails.email = ?", "jinzhu@example.org").Joins("JOIN credit_cards ON credit_cards.user_id = users.id").Where("credit_cards.number = ?", "411111111111").Find(&user)

原生sql构建

执行原生sql

db.Exec("DROP TABLE users;")
db.Exec("UPDATE orders SET shipped_at=? WHERE id IN (?)", time.Now, []int64{11,22,33})
​
// Scan
type Result struct {
    Name string
    Age  int
}
​
var result Result
db.Raw("SELECT name, age FROM users WHERE name = ?", 3).Scan(&result)

sql.Row & sql.Rows

row := db.Table("users").Where("name = ?", "jinzhu").Select("name, age").Row() // (*sql.Row)
row.Scan(&name, &age)
​
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
    ...
    rows.Scan(&name, &age, &email)
    ...
}
​
// Raw SQL
rows, err := db.Raw("select name, age, email from users where name = ?", "jinzhu").Rows() // (*sql.Rows, error)
defer rows.Close()
for rows.Next() {
    ...
    rows.Scan(&name, &age, &email)
    ...
}

3)迭代中使用sql.Rows的Scan

rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Select("name, age, email").Rows() // (*sql.Rows, error)
defer rows.Close()
​
for rows.Next() {
  var user User
  db.ScanRows(rows, &user)
  // do something
}

事务

Gorm提供了Begin、Commit、Rollback方法用于使用事务

func CreateAnimals(db *gorm.DB) err {
  tx := db.Begin()
  // 注意,一旦你在一个事务中,使用tx作为数据库句柄
​
  if err := tx.Create(&Animal{Name: "Giraffe"}).Error; err != nil {
     tx.Rollback()
     return err
  }
​
  if err := tx.Create(&Animal{Name: "Lion"}).Error; err != nil {
     tx.Rollback()
     return err
  }
​
  tx.Commit()
  return nil
}
  • 注意,在一个事务中,使用tx作为数据库句柄

Hook

GORM在提供了CURD的Hook 能力。

Hook是在创建、查询、更新、删除等操作之前、之后自动调用的函数。

如果任何Hook返回错误,GORM将停止后续的操作并回滚事务。

type User struct {
    ID      int64
    Name    string  'gorm : "default: galeone"`
    Age     int64   `gorm : "default:18"`
}
​
type Email struct {
    ID      int64
    Name    string  
    Email   string
}
​
func (u *User) BeforeCreate(tx *gorm.DB) (err error){
    if u.Age < 0{
        return errors.New("con't save invalid data")
    }
    return
}
​
func (u *User) AfterCreate(tx *gorm.DB) (err error){
    return tx.Create(&Email{ID : u.ID, Email : u.Name + "@***.com"}).Error
}

项目连接案例

gorm核心:用于初始化数据库连接。

package core
/*
GormLogger 包裹zap的SugaredLogger作为Gorm的Logger
*/
type GormLogger struct {
    Logger *zap.SugaredLogger
}
​
func (gl GormLogger) Printf(s string, i ...interface{}) {
    gl.Logger.Infof(strings.ReplaceAll(s, "\u001b", " "), i...)
}
​
/*
initGormMysql 初始化mysql连接
*/
func initGormMysql() {
    mysqlConfig := config.DefaultConfiguration.Datasource.Mysql
​
    //将zap的WriteSyncer作为Gorm的日志输出位置
    gormLogger := logger.New(GormLogger{Logger: global.Logger}, logger.Config{
        SlowThreshold:             200 * time.Millisecond,
        LogLevel:                  logger.Info,
        IgnoreRecordNotFoundError: false,
        //必须关闭,否则日志输出会乱码
        Colorful: false,
    })
​
    datasource, err := gorm.Open(mysql.Open(mysqlConfig.DSN), &gorm.Config{
        Logger: gormLogger,
    })
    if err != nil {
        panic(err)
    }
    mysqlDb, err := datasource.DB()
    mysqlDb.SetMaxIdleConns(mysqlConfig.MaxIdleConnections)
    mysqlDb.SetMaxOpenConns(mysqlConfig.MaxOpenConnections)
    mysqlDb.SetConnMaxLifetime(time.Duration(mysqlConfig.ConnectionMaxLifetime) * time.Second)
    if err = mysqlDb.Ping(); err != nil {
        panic(err)
    }
    global.Datasource = datasource
}

连接相关属性配置

package config
​
type Datasource struct {
    Mysql *Mysql
}
​
type Mysql struct {
    //DSN 为datasource name 格式为"user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
    DSN string
    //最大空闲连接数
    MaxIdleConnections int
    //最大开启连接数
    MaxOpenConnections int
    //连接最大存活时间 秒
    ConnectionMaxLifetime int
}
​
var DefaultDataSource = &Datasource{
    Mysql: &Mysql{
        DSN:                   "root:********@tcp(192.168.200.129:3306)/tiktok?charset=utf8mb4&parseTime=True&loc=Local",
        MaxIdleConnections:    10,
        MaxOpenConnections:    20,
        ConnectionMaxLifetime: 300,
    },
}

DAO层的使用:

func SaveVideoInfo(video *entity.Video) bool {
​
    res := global.Datasource.Table("video").Create(&video)
    if res.Error != nil {
        global.Logger.Error(res.Error)
        return false
    }
​
    return true
​
}

数据库相关概念

支持的数据库:MySQL、SQLServer、PostgreSQL、SQLite。

数据库的DSN:

是一种名称(数据来源名称),应用程序用来请求与 ODBC 数据源连接的名称,它会存储连接详细信息,例如数据库名称、目录、数据库驱动程序、UserID、密码等。

数据库建立好之后,要设定系统的 DSN(数据来源名称),才能让网页可以知道数据库所在的位置以及数据库相关的属性。