# 简单介绍

GORM (opens new window) 官方支持的数据库类型有: MySQL, PostgreSQL, SQlite, SQL Server

# 使用 GORM

  • 安装
go get -u gorm.io/gorm
go get -u gorm.io/driver/mysql

# 使用流程

  • 在 models 下面新建 core.go ,建立数据库链接
package models

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

var DB *gorm.DB
var err error

func init() {
	dsn := "root:123456@tcp(127.0.0.1:3306)/gin?charset=utf8mb4&parseTime=True&loc=LLocal"
	DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
		// false(默认):SELECT *  ; true:SELECT id, name, age, ...
		QueryFields: true,
		// 禁用事务将获得大约 30%+ 性能提升
		SkipDefaultTransaction: true,
	})
	db = db.Debug() // 查看执行的sql
	if err != nil {
		fmt.Println(err)
	}
}
  • 定义操作数据库的模型
// 结构体的名称必须首字母大写 ,并和数据库表名称对应
// 结构体中的字段名称首字母必须大写,并和数据库表中的字段一一对应
// 默认情况表名是结构体名称的复数形式。如果结构体名称定义成User,则模型默认操作的是 users 表
type User struct {
  ID           uint           
  Name         string         
  Email        *string        
  Age          uint8          
  Birthday     *time.Time     
  MemberNumber sql.NullString 
  ActivatedAt  sql.NullTime   
  CreatedAt    time.Time      
  UpdatedAt    time.Time      
  ignored      string         
}

// 可以使用结构体中的自定义方法 TableName 改变结构体的默认表名称
// 使用匿名接收器,方法内部无法访问接收器对应的值,即不能使用 User 类型的字段或方法
func (User) TableName() string {
	return "user" // 把 User 结构体默认操作的表改为 user 表
}
  • 预定义的结构体 gorm.Model
// 可以直接在您的结构体中嵌入 gorm.Model
type Model struct {
  ID        uint           
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt gorm.DeletedAt
}

# CURD

# 增加、修改、删除

  • 增加成功后会返回刚才增加的记录
func (con UserController) Add(c *gin.Context) {
	user := models.User{
		Username: "sylone", 
		Age: 18, 
		Email: "123@qq.com", 
		AddTime: int(time.Now().Unix()), 
	}
	result := models.DB.Create(&user) // 通过数据的指针来创建
	if result.RowsAffected > 1 {
		fmt.Print(user.Id)
	}
	fmt.Println(result.RowsAffected)
	fmt.Println(user.Id)
	c.String(http.StatusOK, "add 成功")
}
  • 修改
func (con UserController) Edit(c *gin.Context) {
	user := models.User{Id: 7}
	models.DB.Find(&user)
	user.Username = "gin gorm" user.Age = 1
	models.DB.Save(&user)
	c.String(http.StatusOK, "Edit")
}
  • 删除
func (con UserController) Delete(c *gin.Context) {
	user := models.User{Id: 8}
	models.DB.Delete(&user)
	c.String(http.StatusOK, "Delete")
}
  • 批量删除
db.Where("email LIKE ?", "%jinzhu%").Delete(Email{})
// DELETE from emails where email LIKE "%jinzhu%";
db.Delete(Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";

func (con UserController) DeleteAll(c *gin.Context) {
	user := models.User{}
	models.DB.Where("id>9").Delete(&user)
	c.String(http.StatusOK, "DeleteAll")
}
  • 修改
func (con UserController) Edit(c *gin.Context) {
	user := models.User{Id: 7}
	models.DB.Find(&user)
	user.Username = "gin gorm" user.Age = 1
	models.DB.Save(&user)
	c.String(http.StatusOK, "Edit")
}

# 查询语句

  • 查找全部
func (con UserController) Index(c *gin.Context) {
	user := []models.User{}
	models.DB.Find(&user)
	c.JSON(http.StatusOK, gin.H{ 
		"success": true, 
		"result": user, 
	})
}
  • 指定条件查找
user := []models.User{}
models.DB.Where("username=?", "王五").Find(&user)
c.JSON(http.StatusOK, gin.H{ 
	"success": true, 
	"result": user, 
})

var n = 5
nav := []models.Nav{}
models.DB.Where("id>?", n).Find(&nav)
c.JSON(http.StatusOK, gin.H{ 
	"success": true, 
	"result": nav, 
})

var n1 = 3
var n2 = 9
nav := []models.Nav{}
models.DB.Where("id > ? AND id < ?", n1, n2).Find(&nav)
c.JSON(http.StatusOK, gin.H{ 
	"success": true, 
	"result": nav, 
})

nav := []models.Nav{}
models.DB.Where("id in (?)", []int{3, 5, 6}).Find(&nav)
c.JSON(http.StatusOK, gin.H{ 
	"success": true, 
	"result": nav, 
})

nav := []models.Nav{}
models.DB.Where("title like ?", "%会%").Find(&nav)
c.JSON(http.StatusOK, gin.H{ 
	"success": true, 
	"result": nav, 
})

nav := []models.Nav{}
models.DB.Where("id between ? and ?", 3, 6).Find(&nav)
c.JSON(http.StatusOK, gin.H{ 
	"success": true, 
	"result": nav, 
})
  • Or 条件
nav := []models.Nav{}
models.DB.Where("id=? OR id=?", 2, 3).Find(&nav)

nav := []models.Nav{}
models.DB.Where("id=?", 2).Or("id=?", 3).Or("id=4").Find(&nav)
  • 选择字段查询
nav := []models.Nav{}
models.DB.Select("id, title,url").Find(&nav)
  • 排序 Limit 、Offset
nav := []models.Nav{}
models.DB.Where("id>2").Order("id Asc").Find(&nav)

nav := []models.Nav{}
models.DB.Where("id>2").Order("sort Desc").Order("id Asc").Find(&nav)

nav := []models.Nav{}
odels.DB.Where("id>1").Limit(2).Find(&nav)

// 跳过 2 条查询 2 条
nav := []models.Nav{}
models.DB.Where("id>1").Offset(2).Limit(2).Find(&nav)
  • 获取总数
nav := []models.Nav{}
var num int
models.DB.Where("id > ?", 2).Find(&nav).Count(&num)
  • Distinct:从模型中选择不相同的值
nav := []models.Nav{}
models.DB.Distinct("title").Order("id desc").Find(&nav)
c.JSON(200, gin.H{ "nav": nav, })
  • 将查询结果直接映射到指定的变量或结构体:Scan
# Find 需要基于模型(Model)操作,默认会映射到模型的全部字段
# Scan 更灵活,可以任意定义目标结构体,不依赖模型,适合复杂查询或非模型数据
// 原生 SQL 扫描到自定义结构体
type Result struct {
	Name string
	Age int
}
var result Result
db.Table("users").Select("name", "age").Where("name = ?", "Antonio").Scan(&result)

// 原生 SQL
db.Raw("SELECT name, age FROM users WHERE name = ?", "Antonio").Scan(&result)
var result []models.User
models.DB.Raw("SELECT * FROM user").Scan(&result)
fmt.Println(result)

# 原生SQL

  • 使用原生 sql 删除 user 表中的一条数据
result := models.DB.Exec("delete from user where id=?", 3)
fmt.Println(result.RowsAffected)
  • 使用原生 sql 修改 user 表中的一条数据
result := models.DB.Exec("update user set username=? where id=2", "哈哈")
fmt.Println(result.RowsAffected)
  • 查询 uid=2 的数据
var result models.User
models.DB.Raw("SELECT * FROM user WHERE id = ?", 2).Scan(&result)
fmt.Println(result)
  • 查询 User 表中所有的数据
var result []models.User
models.DB.Raw("SELECT * FROM user").Scan(&result)
fmt.Println(result)
  • 统计 user 表的数量
var count int
row := models.DB.Raw("SELECT count(1) FROM user").Row(&count )
row.Scan(&count)

# 关联查询

# 一对一

type Article struct {
	Id int `json:"id"` 
	Title string `json:"title"` 
	Description int `json:"description"` 
	CateId string `json:"cate_id"` // 如果是ArticleCateId可以不用以下设置
	State int `json:"state"` 
	// foreignkey 指定当前表的外键、references 指定关联表中和外键关联的字段
	ArticleCate ArticleCate `gorm:"foreignKey:CateId;references:Id"`
}

type ArticleCate struct {
	Id int `json:"id"` 
	Title string `json:"title"` 
	State int `json:"state"` 
}
  • 查询所有文章以及文章对应的分类信息
var articleList []models.Article
models.DB.Preload("ArticleCate").Limit(2).Find(&articleList)
c.JSON(200, gin.H{ "result": articleList, })
  • 查询所有文章以及文章对应的分类信息 指定条件
var articleList []models.Article
models.DB.Preload("ArticleCate").Where("id>=?", 4).Find(&articleList)
c.JSON(200, gin.H{ "result": articleList, })

# 一对多

type ArticleCate struct {
	Id int `json:"id"` 
	Title string `json:"title"` 
	State int `json:"state"` 
	Article []Article `gorm:"foreignKey:CateId"` 
}
type Article struct {
	Id int `json:"id"` 
	Title string `json:"title"` 
	Description int `json:"description"` 
	CateId string `json:"cate_id"`
	State int `json:"state"`
}
  • 查找所有分类以及分类下面的文章信息
var articleCateList []models.ArticleCate
models.DB.Preload("Article").Find(&articleCateList)
c.JSON(200, gin.H{ "result": articleCateList, })
  • 查找所有分类以及分类下面的文章信息 指定条件
var articleCateList []models.ArticleCate
models.DB.Preload("Article").Where("id>0")Limit(1).Find(&articleCateList)
c.JSON(200, gin.H{ "result": articleCateList, })

# 多对多

type Lesson struct {
	Id int `json:"id"`
	Name string `json:"name"`
	Student []*Student `gorm:"many2many:lesson_student"`
}
type Student struct {
	Id int
	Number string
	Password string
	ClassId int
	Name string
	Lesson []*Lesson `gorm:"many2many:lesson_student"`
}
type LessonStudent struct {
	LessonId int // 建议这样命名,否则很麻烦
	StudentId int // 建议这样命名,否则很麻烦
}
  • 查询学生信息的时候获取学生的选课信息
studentList := []models.Student{}
models.DB.Preload("Lesson").Find(&studentList)
c.JSON(http.StatusOK, studentList)
  • 查询张三选修了哪些课程
studentList := []models.Student{}
models.DB.Preload("Lesson").Where("id=1").Find(&studentList)
c.JSON(http.StatusOK, studentList)
  • 课程被哪些学生选修了
lessonList := []models.Lesson{}
models.DB.Preload("Student").Find(&lessonList)
c.JSON(http.StatusOK, lessonList)
  • 计算机网络被那些学生选修了
lessonList := []models.Lesson{}
models.DB.Preload("Student").Where("id=1").Find(&lessonList)
c.JSON(http.StatusOK, lessonList)
  • 查询数据指定条件
lessonList := []models.Lesson{}
models.DB.Preload("Student").Offset(1).Limit(2).Find(&lessonList)
c.JSON(http.StatusOK, lessonList)

# 关联查询指定子集的筛选条件

  • 张三被开除了 查询课程被哪些学生选修的时候要去掉张三
lessonList := []models.Lesson{}
models.DB.Preload("Student", "id!=1").Find(&lessonList)
c.JSON(http.StatusOK, lessonList)

lessonList := []models.Lesson{}
models.DB.Preload("Student", "id not in (1,2)").Find(&lessonList)
c.JSON(http.StatusOK, lessonList)

# 自定义预加载 SQL

  • 查看课程被哪些学生选修 要求:学生 id 倒叙输出
lessonList := []models.Lesson{}
models.DB.Preload("Student", func(db *gorm.DB) *gorm.DB {
	return models.DB.Where("id>3").Order("id DESC")
}).Find(&lessonList)
c.JSON(http.StatusOK, lessonList)

# 使用事务

  • 事务(手动控制)
// 开启事务
tx := db.Begin()
// 在事务中做一些数据库操作 (这里应该使用 'tx' ,而不是 'db')
tx.Create(...)
// ... // 有错误时,手动调用事务的 Rollback()
tx.Rollback()
// 无错误时,手动调用事务的 Commit()
tx.Commit()
  • 张三给李四转账
type TransitionController struct {
	BaseController
}
func (con TransitionController) Index(c *gin.Context) {
	tx := models.DB.Begin()
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
			con.Error(c)
		}
	}()
	if err := tx.Error; err != nil {
		fmt.Println(err)
		con.Error(c)
	}
	// 张三账户减去 100
	u1 := models.Bank{Id: 1}
	tx.Find(&u1)
	u1.Balance = u1.Balance - 100
	if err := tx.Save(&u1).Error; err != nil {
		tx.Rollback()
		con.Error(c)
	}
	// panic("遇到了错误")
	// 李四账户增加 100
	u2 := models.Bank{Id: 2}
	tx.Find(&u2)
	u2.Balance = u2.Balance + 100
	// panic("失败")
	if err := tx.Save(&u2).Error; err != nil {
		tx.Rollback()
		con.Error(c)
	}
	tx.Commit()
	con.success(c)
}