设计模式之DatabaseSQL与GORM|青训营笔记

275 阅读7分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第6篇笔记

1.理解database/sql

image.png database sql包为关系型数据库提供通用的接口

1>import driver实现

使用 driver+DSN 初始化DB连接

2>执行一条SQL,通过rows取回返回的数据。处理完毕,需要释放连接,防止资源泄露

3>数据,错误处理

rows是游标,next会不断获取到下一条数据 rows在最后会自动close,+defer close是因为担心在scan的过程中有任何异常,容易造成rows泄露,服务卡死 通过rows.next()进行关闭可能会漏掉错误

4>处理rows.Err()错误

设计原理 image.png

连接池管理

image.png

image.png

返回一个连接

image.png

注册全局driver

image.png

image.png

大佬理解: DSN是很长的字符串,参数复杂,处理复杂 可能会忘记import ...

解决方法:

通过结构体传入 image.png

DB连接的直接类型

1.Conn

2.Preparestatement 在执行同样的sql时,会先Prepare一下,生成一个PreParestatement,根据PrepareStatement reference id进行执行过程,不需要把原来sql传过去,减少网络传输时间和解析sql时间

3.事务

image.png

处理返回数据的几种方式:

row通过行的方式返回数据,row会自动关闭,而rows需要手动关闭

image.png

rows实现

image.png

2.GORM使用简介

image.png

1.GORM基本用法

原: image.png GORM:

image.png

CRUD

image.png

image.png

2.模型定义

包括:

  1. 基本类型
  2. value scanner接口类型 image.png

3.惯例约定

约定优于配置,一切都可配置 image.png

4.关联

image.png

关联模式 批量模式 image.png

Preload/Joins预加载 避免在查询用户时,每个用户都查询他相关的关联,产生一个n+1的sql操作 Preload Joins image.png 误区:1条sql不一定比3条sql性能更好,缓存可能对性能有加速,使用时需要根据场景判断

级联删除->保证没有孤儿数据

方法1:数据库约束自动删除(字节禁止使用) 方法2:通过select,不依赖数据库约束及软删除 image.png

3.GORM设计原理

怎么通过一行配置迅速提高服务性能? image.png

1.SQL生成

DB对象执行时都会生成一个GORM STATEMENT对象

子句由表达式构成 image.png

Chain Method GORM子句 Finisher Method 决定Statement类型 & 执行方法

image.png

将当前参数翻译成GORM子句,添加到Statement中 image.png

image.png

2.插件扩展

为什么?

image.png

不同数据库甚至不同版本的数据库支持的SQL不同->灵活扩展

image.png

扩展子句

image.png

选择子句

image.png

插件工作过程 一共有6种Callbacks image.png

3.ConnPool

4.Dialector

4.GORM最佳实践

0.预习

ORM

  1. 连接MYSQL
import (
  "github.com/jinzhu/gorm"
    //数据库驱动,没有直接用到,前面+_
  _ "github.com/jinzhu/gorm/dialects/mysql"
)

func main() {
  db, err := gorm.Open("mysql", "user:password@(localhost)/dbname?charset=utf8mb4&parseTime=True&loc=Local")
  
  if err!= nil{
	panic(err)
  }
  defer db.Close()
}
  1. 定义模型
// User用户信息
type User struct {
	ID uint
	Name string
	Gender string
	Hobby string
}
  1. 把模型和数据库中的表对应起来——自动迁移

如果没有这个表就会自动创建,没有创建相应字段就会增加字段(不会删除原有,防止破坏数据)

db.AutoMigrate(&UserInfo{})
  1. 判断是否为空

为空创建结构体对象db.Create(&u) (建议传指针,减少拷贝消耗

user := User{Name: "q1mi", Age: 18}

db.NewRecord(user) // 主键为空返回`true`
db.Create(&user)   // 创建user
db.NewRecord(user) // 创建`user`后返回`false`

5.增删改查

将数据表中记录映射成结构体实例

(一)查

注意:First只有主键是数字类型才能这么用

写法一:

var user User
//go函数中,参数一定是拷贝
//user是空指针
// 根据主键查询第一条记录
db.First(&user)
//SELECT * FROM users ORDER BY id LIMIT 1;

写法二:

user := new(User)
db.First(user)

make和new区别

make专门是给channal,slice,map

new给基本数据结构,返回对应类型的指针

注意:

当通过结构体进行查询时,GORM将会只通过非零值字段查询,这意味着如果你的字段值为0,'',false或者其他零值时,将不会被用于构建查询条件

db.Where(&User{Name: "jinzhu", Age: 0}).Find(&users)
//// SELECT * FROM users WHERE name = "jinzhu";

解决方法:

  1. 实现 Scanner/Valuer接口
  2. 指针

内联条件:

作用与Where查询类似,当内联条件与多个立即执行方法一起使用时, 内联条件不会传递给后面的立即执行方法。

mmediate methods ,立即执行方法是指那些会立即生成SQL语句并发送到数据库的方法, 他们一般是CRUD方法,比如:

Create, First, Find, Take, Save, UpdateXXX, Delete, Scan, Row, Rows…

(四)删

注意:Delete 必须主键有值,如果主键为空,会删除该model所有记录

如果有deleteat字段,删除是软删除,会保护数据

import (
  "github.com/jinzhu/gorm"
    //数据库驱动,没有直接用到,前面+_
  _ "github.com/jinzhu/gorm/dialects/mysql"
)

func main() {
  db, err := gorm.Open("mysql", "user:password@(localhost)/dbname?charset=utf8mb4&parseTime=True&loc=Local")
  defer db.Close()
}
package main

import (
	"fmt"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/mysql"
)

// UserInfo 用户信息
type UserInfo struct {
	ID uint
	Name string
	Gender string
	Hobby string
}


func main() {
	db, err := gorm.Open("mysql", "root:root1234@(127.0.0.1:13306)/db1?charset=utf8mb4&parseTime=True&loc=Local")
	if err!= nil{
		panic(err)
	}
	defer db.Close()

	//创建表user_infos,自动迁移(把结构体和数据表进行对应
	db.AutoMigrate(&UserInfo{})
    //创建数据行
	u1 := UserInfo{1, "七米", "男", "篮球"}
	u2 := UserInfo{2, "沙河娜扎", "女", "足球"}
	// 创建记录
	db.Create(&u1)
	db.Create(&u2)
	// 查询
	var u = new(UserInfo)
    //要传指针,不然没法修改
	db.First(&u)
	fmt.Printf("%#v\n", u)
	
	var uu UserInfo
	db.Find(&uu, "hobby=?", "足球")
	fmt.Printf("%#v\n", uu)

	// 更新
	db.Model(&u).Update("hobby", "双色球")
	// 删除
	db.Delete(&u)
}
//查看Mysql表字段
desc user_infos;

GORM Model

在使用ORM工具时,通常我们需要在代码中定义模型(Models)与数据库中的数据表进行映射。

好处:查询出的数据可以映射成结构体实例,不用通过反射到结构体

在GORM中模型(Models)通常是正常定义的结构体、基本的go类型或它们的指针。 同时也支持sql.Scanner及driver.Valuer接口(interfaces)。

// gorm.Model 定义
type Model struct {
  ID        uint `gorm:"primary_key"`
  CreatedAt time.Time
  UpdatedAt time.Time
  DeletedAt *time.Time
}

go语言结构体支持嵌套,你可以将它嵌入到你自己的模型中:

// 将 `ID`, `CreatedAt`, `UpdatedAt`, `DeletedAt`字段注入到`User`模型中
type User struct {
  gorm.Model
  Name string
}

当然也可以完全自己定义模型:

// 不使用gorm.Model,自行定义模型
type User struct {
  ID   int
  Name string
}

模型定义示例

type User struct {
  gorm.Model//内嵌gorm.Model
  Name         string
  Age          sql.NullInt64//零值
  Birthday     *time.Time
    					//结构体标记tag
  Email        string  `gorm:"type:varchar(100);unique_index"`
  Role         string  `gorm:"size:255"` // 设置字段大小为255
  MemberNumber *string `gorm:"unique;not null"` // 设置会员号(member number)唯一并且不为空
  Num          int     `gorm:"AUTO_INCREMENT"` // 设置 num 为自增类型
  Address      string  `gorm:"index:addr"` // 给address字段创建名为addr的索引
  IgnoreMe     int     `gorm:"-"` // 忽略本字段
}

主键、表名、列名的约定

主键:GORM 默认会使用名为ID的字段作为表的主键/tag设定

列名:列名由字段名称进行下划线分割来生成

type User struct {
  ID        uint      // column name is `id`
  Name      string    // column name is `name`
  Birthday  time.Time // column name is `birthday`
  CreatedAt time.Time // column name is `created_at`
}

可以使用结构体tag指定列名:

type Animal struct {
  AnimalId    int64     `gorm:"column:beast_id"`         // set column name to `beast_id`
  Birthday    time.Time `gorm:"column:day_of_the_beast"` // set column name to `day_of_the_beast`
  Age         int64     `gorm:"column:age_of_the_beast"` // set column name to `age_of_the_beast`
}

表名:表名默认就是结构体名称的复数,也可以通过Table()指定表名:

时间戳跟踪

CreatedAt

db.Create(&user) // `CreatedAt`将会是当前时间

// 可以使用`Update`方法来改变`CreateAt`的值
db.Model(&user).Update("CreatedAt", time.Now())

UpdatedAt

db.Save(&user) // `UpdatedAt`将会是当前时间

db.Model(&user).Update("name", "jinzhu") // `UpdatedAt`将会是当前时间

DeletedAt

注意:如果模型有DeletedAt字段,调用Delete删除该记录时,将会设置DeletedAt字段为当前时间,而不是直接将记录从数据库中删除。

tag指定默认值

type User struct {
  ID   int64
  Name string `gorm:"default:'小王子'"`
  Age  int64
}
var user = User{Name: "", Age: 99}
db.Create(&user)

不指定默认值name字段值为空

指定后

问题: 创建时设定字段为零值(0,false,"")时还是会为默认值

eg.创建Name="",结果Name=默认值

解决方法:

  1. 可以考虑使用指针
// 使用指针
type User struct {
  ID   int64
  Name *string `gorm:"default:'小王子'"`
  Age  int64
}
user := User{Name: new(string), Age: 18))}
db.Create(&user)  // 此时数据库中该条记录name字段的值就是''
  1. 实现 Scanner/Valuer接口

// 使用 Scanner/Valuer
type User struct {
	ID int64
	Name sql.NullString `gorm:"default:'小王子'"` // sql.NullString 实现了Scanner/Valuer接口
	Age  int64
}
user := User{Name: sql.NullString{"", true}, Age:18}
db.Create(&user)  // 此时数据库中该条记录name字段的值就是''