Golang标准库database/sql与GORM | 青训营笔记

606 阅读9分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第3篇笔记。介绍了go语言的数据库操作,什么是ORM框架,以及go语言的GORM的基本使用。

一、database/sql标准库

通常应用程序与数据库服务器的交互过程是通过database/sql标准库。以MySQL为例,使用方法如下:

import "database/sql"
func main() {
    db, err := sql.Open("mysql", user:password@tcp(127.0.0.1:3306)/hello")
    if err != nil {
        ...
    }
    rows, err = db.Query("select id,name from users where id=?", 1)
    if err != nil{
        ...
    }
    defer func(){
        err = rows.Close()
    }
    var users []User
    for rows.Next() {
    var user User
    err := rows.Scan(&user.ID, &user.Name)
    if err != nil {
        ...
        }
    users = append(users, user)
    }
    if rows.Err != nil {
        ...
    }
}

二、ORM框架

1.什么是ORM?

对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。ORM框架是连接数据库的桥梁,只要提供了持久化类与表的映射关系,ORM框架在运行时就能参照映射文件的信息,把对象持久化到数据库中。

什么是“持久化”?

持久(Persistence),即把数据(如内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内存中的数据存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。

ORM是持久化设计中的最重要也最复杂的技术,也是目前业界热点技术。它使得我们在具体的操作数据库的时候,不需要再去和复杂的SQL语句打交道,只要像平时操作对象一样操作它就可以了。

2.为什么使用ORM?

当我们实现一个应用程序时(不使用O/R Mapping),我们可能会写特别多数据访问层的代码,从数据库保存、删除、读取对象信息,而这些代码都是重复的。而使用ORM则会大大减少重复性代码。对象关系映射(Object Relational Mapping,简称ORM),主要实现程序对象到关系数据库数据的映射。

优点:(1) 提高开发效率,降低开发成本

(2) 使开发更加对象化

(3) 可移植

(4) 可以很方便地引入数据缓存之类的附加功能

缺点:会在一定程度上牺牲程序的执行效率

三、GORM

1.简介

“设计简单、功能强大、自由扩展的全功能ORM”

采用GORM之后,开发者只需要定义好 Struct 数据结构,由 GORM 来完成 Struct 到 ModelStruct 的转换(结构体映射为 Table,结构体成员映射为 Column),并提供了标准的方法进行数据操作,例如:Create、Update、Delete、Where 等。

2.基本操作

Github地址

中文文档

2.1连接数据库
import (
    "gorm.io/gorm"
    "gorm.io/driver/mysql"
)
func main() {
    db, err := gorm.Open(
        mysql.Open("user:password@tcp(127.0.0.1:3306)/hello")
    )
    if err != nil {
        panic(err)
    }
    defer db.Close() //关闭连接
}
2.2 CRUD

GORM 支持使用链式 API 来完成对 Table 的 CURD 操作,所有链式方法都会创建并克隆一个新的 db 对象 (共享一个连接池),GORM 在多 Goroutine 中是并发安全的。任何时候都可以不断的基于 db 对象调用函数来完成预期的操作,包括调用 db.Error 就能获取到错误信息。

//User 用户信息
type User struct {
    ID int
    Name string
    Age int
    Birthday time.Time
}                                                

db.Create—插入

调用 db.Create 方法即可完成数据库表记录的插入。需要注意的是,GORM 生成的 SQL 语句会排除零值字段:所有字段的零值,比如 0、’’、false,都不会保存到数据库内。将记录插入到数据库后,Gorm会从数据库加载那些字段的值到变量对象。

//插入
user := User{Name: "username", Age: 18, Birthday: time.Now()}
result := db.Create(&user)
​
user.ID         //返回主键
result.Error    //返回error
result.RowAffected  //返回影响的行数

db.Finddb.Firstdb.Take—查询

// 根据主键查询第一条记录
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;// 随机获取一条记录
db.Take(&user)
// SELECT * FROM users 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;

db.Where—选择

当通过结构体进行 WHERE 过滤查询时,GORM 只会通过其中的非零值字段进行查询,例如:0、’’、false 或者其他零值将不会被用于构建查询条件。

// 获取第一个匹配的记录
db.Where("name = ?", "username").First(&user)
// SELECT * FROM users WHERE name = 'username' limit 1;
​
// 获取所有匹配的记录
db.Where("name = ?", "username").Find(&users)
// SELECT * FROM users WHERE name = 'username';
​
// <>
db.Where("name <> ?", "uaername").Find(&users)
 SELECT * FROM users WHERE name <> 'username';
​
// IN
db.Where("name IN (?)", []string{"u1", "u2"}).Find(&users)
// SELECT * FROM users WHERE name in ('u1','u2');
​
// LIKE
db.Where("name LIKE ?", "%u1%").Find(&users)
// SELECT * FROM users WHERE name LIKE '%u1%';
​
// AND
db.Where("name = ? AND age >= ?", "u1", "22").Find(&users)
// SELECT * FROM users WHERE name = 'u1' AND age >= 22;
​
// Time
db.Where("updated_at > ?", lastWeek).Find(&users)
// SELECT * FROM users WHERE updated_at > '2000-01-01 00:00:00';
​
// BETWEEN
db.Where("created_at BETWEEN ? AND ?", lastWeek, today).Find(&users)
// SELECT * FROM users WHERE created_at BETWEEN '2000-01-01 00:00:00' AND '2000-01-08 00:00:00';
​
//数据结构的查询
// Struct
db.Where(&User{Name: "u1", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "u1" AND age = 20 ORDER BY id LIMIT 1;
​
// Map
db.Where(map[string]interface{}{"name": "u1", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "u1" AND age = 20;
​
// 主键切片
db.Where([]int64{20, 21, 22}).Find(&users)
// SELECT * FROM users WHERE id IN (20, 21, 22);
​
//db.Or
db.Where("role = ?", "admin").Or("role = ?", "super_admin").Find(&users)
// SELECT * FROM users WHERE role = 'admin' OR role = 'super_admin';
​
// Struct
db.Where("name = 'u1'").Or(User{Name: "u2"}).Find(&users)
// SELECT * FROM users WHERE name = 'u1' OR name = 'u2';
​
// Map
db.Where("name = 'u1'").Or(map[string]interface{}{"name": "u2"}).Find(&users)
// SELECT * FROM users WHERE name = 'u1' OR name = 'u2';

db.Order——排序

// 多字段排序
db.Order("age desc").Order("name").Find(&users)
 SELECT * FROM users ORDER BY age desc, name;

db.Limit—限量

db.Limit(3).Find(&users)
//SELECT * FROM users LIMIT 3;// -1 取消 Limit 条件
db.Limit(10).Find(&users1).Limit(-1).Find(&users2)
//SELECT * FROM users LIMIT 10; (users1)
//SELECT * FROM users; (users2)

db.Update—更新

更新修改字段

// 更新单个属性,如果它有变化
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 更新时,GORM只会更新那些非零值的字段
// 对于下面的操作,不会发生任何更新,"", 0, false 都是其类型的零值
//db.Model(&user).Updates(User{Name: "", Age: 0, Actived: false})

更新所有字段

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

更新选定字段

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;

db.Delete—删除

db.Delete(&user)

软删除:如果 model 有 DeletedAt 字段,那么该 model 具有软删除的功能。当调用 Delete 方法时,记录不会真正的从数据库中被删除,只会将DeletedAt 字段的值会被设置为当前时间,表示 “已删除”。

3.表定义

3.1模型定义

模型一般为Golang结构体:

type User struct {
    gorm.Model
    Name    string
    Age     sql.NullInt64
    Birthday *time.Time
    Email   string  `gorm:"type:varchar(100);unique_index"`
    Num     int     `gorm:"AUTO_INCREMENT"` //设置自增
    Address string  `gorm:"index:addr"`     //给Address创建一个名为 `addr` 的索引
    Ignore  int     `gorm:"-"`              //忽略这个字段
}

常见的结构标签如下表所示:

Tag说明
Column指定列的名称
Type指定列的类型
Size指定列的大小,默认255
PRIMARY_KEY指定一个列作为主键
UNIQUE指定一个唯一的列
DEFAULT指定一个列的默认值
PRECISION指定列的数据的精度
NOT NULL指定列的数据不为空
AUTO_INCREMENT指定一个列的数据是否自增
INDEX创建带或不带名称的索引,同名创建复合索引
UNIQUE_INDEX创建一个唯一的索引
EMBEDDED将 struct 设置为 embedded
EMBEDDED_PREFIX设置嵌入式结构的前缀名称
-忽略这些字段

gorm.Model是一个包含一些基本字段的结构体,可以将其嵌入到模型中:

//gorm.Model 定义
type Model struct {
  ID        uint `gorm:"primary_key"`   //主键,如果为数字,则为自增主键
  CreatedAt time.Time                   //创建时,保存当前时间
  UpdatedAt time.Time                   //创建更新时,保存当前时间
  DeletedAt gorm.DeleteAt   `gorm:"index"`  //默认开启soft delete模式
}
3.2表定义

GORM 的表定义很简单,应用了 Golang 的 Struct Tag 和 Reflect(反射)机制。在定义好 Model Struct 之后,直接调用 db.CreateTable 函数就可以创建表了。一般情况下:

1.Struct Name就是Table Name,表名默认为小写字母的复数形式

2.Struct Members就是表的字段。

3.Struct Tag使用gorm:"xxx"进行表列的属性定义,具体可选如上面表格所示。

3.3表操作

db.HashTable—表是否存在

//检查模型User的表是否存在
db.HashTable(&User{})
​
//检查表 users 是否存在
db.HashTable("users")

db.CreateTable—创建表

// 为模型 `User` 创建表
db.CreateTable(&User{})
​
//指定表名进行数据操作
//用 `User` 结构体创建 `student_users` 表
db.Table("student_users").CreateTable(&User{})
​
//SELECT * FROM student_users;
var student_users []User
db.Table("student_users").Find(&student_users)
​
//DELETE FROM student_users WHERE name = 'username';
db.Table("student_users").Where("name = ?", "username").Delete()

db.DropTable—删除表

// 删除模型 `User` 的表
db.DropTable(&User{})
​
// 删除表 `users`
db.DropTable("users")

db.ModifyColumn— 修改列

// 修改模型 `User` 的 description 列的类型为 `text` 
db.Model(&User{}).ModifyColumn("description", "text")

db.DropColumn—删除列

// 删除模型 `User` 的 description 列
db.Model(&User{}).DropColumn("description")

db.AddIndex—添加索引

// 为 `name` 列添加名为 `idx_user_name` 的普通索引
db.Model(&User{}).AddIndex("idx_user_name", "name")
​
// 为 `name` 和 `age` 两列添加名为 `idx_user_name_age` 的普通索引
db.Model(&User{}).AddIndex("idx_user_name_age", "name", "age")
​
// 添加唯一索引
db.Model(&User{}).AddUniqueIndex("idx_user_name", "name")
​
// 为多列添加唯一索引
db.Model(&User{}).AddUniqueIndex("idx_user_name_age", "name", "age")

db.AddForeign—添加外键

// 第一个参数: 外键字段
// 第二个参数:目标表名(字段)
// 第三个参数:删除时
// 第四个参数: 更新时
db.Model(&User{}).AddForeignKey("city_id", "cities(id)", "RESTRICT", "RESTRICT")

db.RemoveForeignKey—删除外键

db.Model(&User{}).RemoveForeignKey("city_id", "cities(id)")

时间戳跟踪

db.Create(&user) // 将设置 `CreatedAt` 为当前时间
// 可以使用 `Update` 方法来更改默认时间
db.Model(&user).Update("CreatedAt", time.Now())
​
db.Save(&user) // 将设置 `UpdatedAt` 为当前时间
db.Model(&user).Update("name", "username") // 将设置 `UpdatedAt` 为当前时间