带你踩gorm v2的坑-模型关系
最近在学习golang, 使用gin框架搭了个网站,于是在网上找golang的orm
找到gorm使用了两天突然发现v2已经发布了,看更新日志说提高了性能修复了很多bug,于是赶紧换了v2,以下纪录我使用gorm v2 踩过的坑
我使用gorm连接的是mysql, 在使用automigrate根据关系模型创建表的时候总是创建失败
首先看官方文档,简单的关联关系,其实理解了官方案例问题就不大.
但是在复杂关系的时候创建就会报错(无法添加外键约束等).这里我提供我自己实验出的一种解决方式:
以我自己的项目为例,假设有User(用户),House(房间),Order(订单)三个表, 无关的属性已经去掉
pretype User struct {
gorm.Model // gorm自带, 会提供ID, craeteAt, updateAt, deleteAt四个属性
Property1 int
Property2 string
}
type House struct {
gorm.Model
Property1 int
Property2 string
}
type Order struct {
gorm.Model
Property1 int
Property2 string
精简了很多属性和关联不知道还会不会报错.
创建的时候先屏蔽所有的外键关系然后创建表, 注释之后如下
pretype User struct {
gorm.Model // gorm自带, 会提供ID, craeteAt, updateAt, deleteAt四个属性
Property1 int
Property2 string
}
type House struct {
gorm.Model
Property1 int
Property2 string
}
type Order struct {
gorm.Model
Property1 int
Property2 string
相当于每个表都只剩下了gorm.Model和两个Property, 请注意在Order表中我注释掉了UserID,HouseID
因为这两个属性虽然是基础类型属性,但是UserID是user表的外键, HouseID是下面House表的外键, 所以说这两个属性也要注释掉
然后你就可以执行db.AutoMigrate(User{},House{},Order{}) 来自动迁移创建你的表;
创建完成之后放开User表的Houses和House表的Tenants, 然后再次执行自动迁移, 就可以创建users表和houses表的多对多关联表;
然后再放开Order的HouseID 和House, 就可以创建orders表对houses表的一对一关系约束;
最后放开User的Orders和Order的UserID, 就可以创建users表对orders表的一对多关系约束;
这样分步创建就不会报错,如果报错的话请检查你的模型是否有问题, 多参考官方给出的案例
还有官网给出的项目 gin-vue-admin 也可以参考
gorm基本使用
gorm安装
gorm框架是go的一个数据库连接及交互框架,一般用于连接关系型数据库。
安装gorm,使用命令安装:
go get -u -v github.com/jinzhu/gorm
连接数据库
构建连接的目标为数据库中某一个库,同大多数框架一样使用url进行连接,url基本格式:
用户名:密码@连接方式(ip:port)/数据库名?参数
参数描述:
用户名 :连接数据库用户名
密码 :连接数据库密码
连接方式 :或称为访问协议,一般为tcp
ip :数据库所在服务器地址
port :数据库监听端口
数据库名 :数据库中对应要连接库名
参数 :附加数据库参数,根据数据库不同内容不同
确定url后,就可以调用gorm中参数进行连接数据库操作:
别忘记import语句导入包:
操作完数据库后不要忘了关闭连接:
表操作
创建表
gorm创建表基于结构体,所以创建表前要先用结构体规划表结构,这里简单写一个结构体,创建表时默认结构体第一个字段作为主键:
写好结构体后,调用创建表语句直接按照结构体结构创建表:
当直接创建表时,表名遵循规范:
1、大写字母全部小写,所有位于中间的大写字母都变成小写字母并且前面加下划线,例如UserInfo结构体创建为表名称为user_infos。
2、上面最后的那个s是因为直接创建表时,名称最后都会加上一个s。所以上面结构体User创建表后名称为users。
删除表
增删改查
插入数据
插入数据,插入数据方法结构使用Create方法:
查询数据
查询第一条找到的数据,使用First,该方法返回值仍然为db对象,所以接收数据时我们需要先定义一个对应表结构的结构体接收数据:
有First就有Last,同First调用格式一致。
批量查询,批量查询使用Find方法,其余参数结构同First或Last相同,只不过传参时一般传入数组参数,当然,First或Last方法也可以传入数组参数,但得到的数组只有一个数据,批量查询操作:
更新数据
删除内容
Go语言使用gorm对MySQL进行性能测试
之前写过了Go语言gorm框架MySQL实践,其中对gorm框架在操作MySQL的各种基础实践,下面分享一下如何使用gorm框架对MySQL直接进行性能测试的简单实践。
这里我使用了一个原始的Go语言版本的 FunTester 测试框架,现在只有一个基本的方法,实在是因为Go语言特性太强了。框架设计的主要思路之一就是利用Go语言的闭包和方法参数特性,将一个 func() 当做性能测试的主题,通过不断运行这个 func() 来实现性能测试。当然还有另外一个思路就是运行一个多线程任务类,类似 Java 版本的 com.funtester.base.constaint.ThreadBase 抽象类,这样可以设置一些类的属性,绑定一些测试资源,适配更多的测试场景。
下面演示select的性能测试,这里我用了随机ID查询的场景。
这里我使用从35开始递增的ID进行删除。
这里使用了select的用例部分,随机ID,然后更新name字段,随机10个长度的字符串。
这里用到了 FunTester 字段都是随机生成。
到这里可以看出,性能测试框架用到的都是gorm框架的基础API使用,这里MySQL连接池的管理工作完全交给了gorm框架完成,看资料说非常牛逼,我们只需要设置几个参数。这个使用体现很像 HttpClient 设置 HTTP 连接池类似,这里我们也可以看出这些优秀的框架使用起来都是非常简单的。
PS:关于gorm的基础使用的请参考上一期的文章Go语言gorm框架MySQL实践。
Golang database/sql源码分析
Gorm是Go语言开发用的比较多的一个ORM。它的功能比较全:
但是这篇文章中并不会直接看Gorm的源码,我们会先从database/sql分析。原因是Gorm也是基于这个包来封装的一些功能。所以只有先了解了database/sql包才能更加好的理解Gorm源码。
database/sql 其实也是一个对于mysql驱动的上层封装。”github.com/go-sql-driver/mysql”就是一个对于mysql的驱动,database/sql 就是在这个基础上做的基本封装包含连接池的使用
下面这个是最基本的增删改查操作
操作分下面几个步骤:
因为Gorm的连接池就是使用database/sql包中的连接池,所以这里我们需要学习一下包里的连接池的源码实现。其实所有连接池最重要的就是连接池对象、获取函数、释放函数下面来看一下database/sql中的连接池。
DB对象
获取方法
释放连接方法
连接池的实现有很多方法,在database/sql包中使用的是chan阻塞 使用map记录等待列表,等到有连接释放的时候再把连接传入等待列表中的chan 不在阻塞返回连接。
之前我们看到的Redigo是使用一个chan 来阻塞,然后释放的时候放入空闲列表,在往这一个chan中传入struct{}{},让程序继续 获取的时候再从空闲列表中获取。并且使用的是链表的结构来存储空闲列表。
database/sql 是对于mysql驱动的封装,然而Gorm则是对于database/sql的再次封装。让我们可以更加简单的实现对于mysql数据库的操作。
聊聊gorm的读写分离
本文主要研究一下gorm的读写分离
gorm.io/plugin/dbresolver@v1.1.0/dbresolver.go
gorm.io/plugin/dbresolver@v1.1.0/callbacks.go
gorm.io/plugin/dbresolver@v1.1.0/callbacks.go
gorm.io/plugin/dbresolver@v1.1.0/callbacks.go
gorm.io/plugin/dbresolver@v1.1.0/callbacks.go
gorm.io/plugin/dbresolver@v1.1.0/dbresolver.go
gorm.io/plugin/dbresolver@v1.1.0/dbresolver.go
gorm.io/plugin/dbresolver@v1.1.0/resolver.go
gorm.io/plugin/dbresolver@v1.1.0/policy.go
gorm的dbresolver实现了Plugin接口,它针对Create、Update、Delete方法注册了dr.switchSource;针对Query、Row注册了dr.switchReplica;switchSource及switchReplica方法在当前连接没有开启事务时动态判断是否Operation是Read还是Write,开启事务时执行dr.resolve(db.Statement, Write);resolver的resolve根据Operation来进行数据源的切换。