Go框架三件套详解(Web/RPC/ORM)(一) | 青训营笔记

98 阅读7分钟

这是我参与「第五届青训营」伴学笔记的第4天
阅前提示:因为关于这三个框架之前都没有太接触过,需要点时间去了解,所以就将这一节课的笔记分了两部分进行。

三件套的使用

Gorm的基础使用

gorm框架是go的一个数据库连接以及交互框架,一般用于连接关系型数据库

安装gorm

使用命令安装

  • go get -u -v github.com/jinzhu/gorm

连接数据库

格式:**用户名:密码@连接方式(ip:port)/数据库名?参数**

  • 用户名:连接数据库用户名
  • 密码:连接数据库密码
  • 连接方式:或称为访问协议,一般为tcp
  • ip:数据库所在服务器地址
  • port:数据库监听端口
  • 数据库名:数据库中对应要连接库名
  • 参数:附加数据库参数,根据数据库不同内容而不同

例子:

dsn := "root:123456@tcp(127.0.0.1:3306)/community?charset=utf8mb4&parseTime=True&loc=Local"
db, err = gorm.Open(mysql.Open(dsn), &gor)

表操作

创建表

gorm创建表基于结构体,所以创建表前要先用结构体规划表结构,这里简单写一个结构体,创建表时默认结构体第一个字段作为主键:

 //创建结构体映射表结构
 type User struct {
      Id int       `gorm:"primarykey"`
      Name string
      Data string
 }

写好结构体后,调用创建表语句直接按照结构体结构创建表:

 // 直接创建表
 dbConn.CreateTable(&User{})
 // 自定义表名创建表
 dbConn.Table("user").Create(&User{})

当直接创建表时,表名遵循规范:

1、大写字母全部小写,所有位于中间的大写字母都变成小写字母并且前面加下划线,例如UserInfo结构体创建为表名称为user_infos。

2、上面最后的那个s是因为直接创建表时,名称最后都会加上一个s。所以上面结构体User创建表后名称为users。

gorm标签

声明 model 时,tag 是可选的,GORM 支持以下 tag:
(tag 名大小写不敏感,但建议使用 camelCase 风格)

标签名说明
column指定 db 列名
type列数据类型,推荐使用兼容性好的通用类型,例如:所有数据库都支持 bool、int、uint、float、string、time、bytes 并且可以和其他标签一起使用,例如:not null、size, autoIncrement… 像 varbinary(8) 这样指定数据库数据类型也是支持的。在使用指定数据库数据类型时,它需要是完整的数据库数据类型,如:MEDIUMINT UNSIGNED not NULL AUTO_INSTREMENT
size指定列大小,例如:size:256
primaryKey指定列为主键
unique指定列为唯一
default指定列的默认值
precision指定列的精度
scale指定列大小
not null指定列为 NOT NULL
autoIncrement指定列为自动增长
embedded嵌套字段
embeddedPrefix嵌入字段的列名前缀
index根据参数创建索引,多个字段使用相同的名称则创建复合索引,查看 索引 获取详情
uniqueIndex与 index 相同,但创建的是唯一索引
check创建检查约束,例如 check:age > 13,查看 约束 获取详情
<-设置字段写入的权限, <-:create 只创建、<-:update 只更新、<-:false 无写入权限、<- 创建和更新权限
>-设置字段读的权限,->:false 无读权限
-忽略该字段,- 无读写权限

删除表

 // 按照结构体删除,即默认表名
 dbConn.DropTable(&User{})
 //按照表名删除,可为自定义的表名
 dbConn.DropTable("user")

增删改查

插入数据

插入数据,插入数据方法结构使用Create方法:

 // 默认表名插入数据
 dbConn.Create(&User{Id: 1, Name: "awda", Data: "ssss"})
 // 自定义表名插入数据
 dbConn.Table("user").Create(&User{Id: 1, Name: "awda", Data: "ssss"})
查询数据

查询第一条找到的数据,使用First,该方法返回值仍然为db对象,所以接收数据时我们需要先定义一个对应表结构的结构体接收数据:

注:用First时,需要注意查询不到数据会返回ErrRecordNotFound
 //定义user的结构体对象
 var user User
 // 还是两种操作方式
 //其中&user代表上面定义的结构体对象用于接收数据,使用时通过上面变量直接操作
 //默认查询的话相当于查询第一条数据
 dbConn.First(&user)
 dbConn.Table("user").First(&user)
 //输出内容
 fmt.Print(user.Id)
 //带条件查询
 //只传入一个参数时代表默认查询主键
 dbConn.First(&user, 1)
 dbConn.Table("user").First(&user, 1)
 //也可以指定查询条件,条件书写格式与普通sql语句并无不同,其中?代表占位符,First方法是动态参数方法,可以传入任意参数,将按照?占位符逐个传入。
 dbConn.First(&user, "name=? and id = ?", "awda", 1)
 dbConn.Table("user").First(&user, "name=? and id = ?", "awda", 1)
 //使用结构体查询,但是gorm只会查询非零值字段,那么也就意味着,如果字段为“0”,“false”或者其他零值,会被忽略
 dbConn.Where(&User{Name:"jinzhu", Id:1}).First(&user)
 //使用map可以解决这个问题
 dbConn.Where(map[string]interface{}{"Name":"jinzhu", "Id":1}).First(&user)

有First就有Last,同First调用格式一致。

批量查询,批量查询使用Find方法,其余参数结构同First或Last相同,只不过传参时一般传入数组参数,当然,First或Last方法也可以传入数组参数,但得到的数组只有一个数据,批量查询操作:

 //定义结构体数组接收批量数据
 var user[] User
 //其余操作与之前相同,不再过度演示
 dbConn.Find(&user)
 dbConn.Table("user").Find(&user)
更新数据
 //根据Where语句更新
 //更新单个字段
 dbConn.Table("user").Where("id=?", 1).Update("id", 1234)
 //更新批量字段
 dbConn.Table("user").Where("id=?",2).Updates(&User{Id: 123, Name: "awdawd1231"})
 //按主键更新,传入结构体对象,根据对应主键更新相应内容,但是只会更新非零字段,如果要更新零值可以使用Map更新或者使用Select选择字段
 dbConn.Table("user").Save(&user1)
 //根据map更新字段
 dbConn.Model(&User{Id:1}).Updates(map[string]interface{}{...})
 //更新选定字段
 dbConn.Model(&User{Id:1}).Select("name").Updates(map[string]interface{}{...})
 //SQL 表达式更新
 dbConn.Model($User{Id:1}).Update("age",gorm.Expr("age * ? + ?",2,100))
删除内容

物理删除

 //按照主键删除
 dbConn.Table("user").Delete(&user1)
 ​
 //指定条件删除,后面需要指定一个空指针,因为Delete方法必须有参数
 dbTable("user").Where(条件表达式).Delete(&User{})
 ​
 //传入空指针代表指定结构,按照默认表名去删除其中内容
 db.Where(条件表达式).Delete(&User{})

软删除: gorm使用DeletedAt标注,实现软删
此后调用Delete,记录通过正常的查询无法获得,而使用Unscoped可以查询到,如下:

 //创建结构体映射表结构
 type User struct {
      Id int       `gorm:"primarykey"`
      Name string
      Data string
      Deleted gorm.DeletedAt
 }

GORM事务

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

事务概述

数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。 事务由事务开始与事务结束之间执行的全部数据库操作组成。

数据库事务必须具备的四个特性:

原子性(Atomicity) 事务是数据库的逻辑工作单位,事务中包括的诸操作要么全做,要么全不做。 一致性(Consistency) 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。 隔离性(Isolation) 一个事务的执行不能被其他事务干扰。 持续性/永久性(Durability) 一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。

事务操作

要在事务中执行一系列操作,一般流程如下: Gorm提供Transaction方法用于自动提交事务,避免用户漏写Commit、Rollback

type User struct {
	gorm.Model
	Name string
}

func main() {
	db.AutoMigrate(&User{})
	db.Transaction(func(tx *gorm.DB) error {
		// 在事务中执行一些 db 操作(从这里开始,应该使用 'tx' 而不是 'db')
		if err := tx.Create(&User{Name: "Giraffe"}).Error; err != nil {
			// 返回任何错误都会回滚事务
			return err
		}

		if err := tx.Create(&User{Name: "Lion"}).Error; err != nil {
			return err
		}

		// 返回 nil 提交事务
		return nil
	})
}

Hook

GORM提供CURD的Hook能力
Hook是在增删改查等操作之前、之后自动调用的函数
如果任何Hook返回错误,GORM将停止后续的操作并回滚事务

func (u *User) BeforeCreate(tx *gorm.DB) (err error){
   if u.Age < 0 {
   return errors.New("...")
   }
   return
}
func (u *User) AfterCreate(tx *gorm.DB) (err error){
   return tx.Create(&Email{....}).Error
}

GORM提供的hook

//gorm/callbacks/interface.go
type BeforeCreateInterface interface {
	BeforeCreate(*gorm.DB) error
}

type AfterCreateInterface interface {
	AfterCreate(*gorm.DB) error
}

type BeforeUpdateInterface interface {
	BeforeUpdate(*gorm.DB) error
}

type AfterUpdateInterface interface {
	AfterUpdate(*gorm.DB) error
}

type BeforeSaveInterface interface {
	BeforeSave(*gorm.DB) error
}

type AfterSaveInterface interface {
	AfterSave(*gorm.DB) error
}

type BeforeDeleteInterface interface {
	BeforeDelete(*gorm.DB) error
}

type AfterDeleteInterface interface {
	AfterDelete(*gorm.DB) error
}

type AfterFindInterface interface {
	AfterFind(*gorm.DB) error
}

GORM性能提高

对于(创建,更新,删除),GORM默认开启事务,这样会降低性能,使用SkipDefaultTransaction关闭默认事务
使用PrepareStmt缓存预编译语句可以提高后续调用的速度

db, err := gorm.Open(mysql.Open("username:password@tcp(localhost:3306)/database?charset=utf8"),&gorm.Config{
              SkipDefaultTransaction: true,//关闭默认事务
              PrepareStat:            true,//缓存预编译语句
})