王立跃 發表於 2019-11-28 14:26:00

go之gorm

<div>
<h3>1、简介</h3>
<p><strong>ORM</strong></p>
<p>Object-Relationl Mapping, 它的作用是映射数据库和对象之间的关系,方便我们在实现数据库操作的时候不用去写复杂的sql语句,把对数据库的操作上升到对于对象的操作。</p>
<p><strong>gorm</strong></p>
<p>gorm就是基于Go语言实现的ORM库。</p>
<p>类似于Java生态里大家听到过的Mybatis、Hibernate、SpringData等。</p>
<p><strong>Github</strong></p>
<p>https://github.com/jinzhu/gorm</p>
<p><strong>官方文档</strong></p>
<p>https://gorm.io/</p>
<h3>2、如何使用Gorm</h3>
<p>只要四步就能上手gorm,可以尽情的沉浸在毫无技术含量的CRUD世界。</p>
<h4>2.1 下载gorm库</h4>
<p>下载gorm库</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">github.com/jinzhu/gorm v1.9.10
</pre>
</div>
<p>  </p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">go get -u github.com/jinzhu/gorm</pre>
</div>
<pre class="line-numberslanguage-csharp"><br><button class="VJbwyy" type="button"></button></pre>
<p>这是比较原始的方式,现在有了go mod,我们可以更方便的配置,甚至不用配置。</p>
<p>写好代码,在文件下执行go build,go.mod会自动添加对于gorm的依赖包</p>
<pre class="line-numberslanguage-undefined"><br><button class="VJbwyy" type="button"></button></pre>
<p>当然,也可以手动添加这个依赖。</p>
<p>具体参见go-demo项目(https://github.com/DMinerJackie/go-demo)</p>
<h4>2.2 创建DB连接</h4>
<p>建立数据库连接</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 128, 1)"> 1</span> <span style="color: rgba(0, 0, 0, 1)">package main
</span><span style="color: rgba(0, 128, 128, 1)"> 2</span>
<span style="color: rgba(0, 128, 128, 1)"> 3</span> <span style="color: rgba(0, 0, 0, 1)">import (
</span><span style="color: rgba(0, 128, 128, 1)"> 4</span>   <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">github.com/jinzhu/gorm</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 5</span>   _ <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">github.com/jinzhu/gorm/dialects/mysql</span><span style="color: rgba(128, 0, 0, 1)">"</span>
<span style="color: rgba(0, 128, 128, 1)"> 6</span> <span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)"> 7</span>
<span style="color: rgba(0, 128, 128, 1)"> 8</span> <span style="color: rgba(0, 0, 0, 1)">func main() {
</span><span style="color: rgba(0, 128, 128, 1)"> 9</span>   <span style="color: rgba(0, 0, 255, 1)">var</span><span style="color: rgba(0, 0, 0, 1)"> err error
</span><span style="color: rgba(0, 128, 128, 1)">10</span>   db, connErr := gorm.Open(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">mysql</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">root:rootroot@/dqm?charset=utf8&amp;parseTime=True&amp;loc=Local</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">11</span>   <span style="color: rgba(0, 0, 255, 1)">if</span> connErr !=<span style="color: rgba(0, 0, 0, 1)"> nil {
</span><span style="color: rgba(0, 128, 128, 1)">12</span>         panic(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">failed to connect database</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">13</span> <span style="color: rgba(0, 0, 0, 1)">    }
</span><span style="color: rgba(0, 128, 128, 1)">14</span> <span style="color: rgba(0, 0, 0, 1)">    defer db.Close()
</span><span style="color: rgba(0, 128, 128, 1)">15</span>   db.SingularTable(<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
</span><span style="color: rgba(0, 128, 128, 1)">16</span> }</pre>
</div>
<p>&nbsp;</p>
<p>gorm支持很多数据源包括PostgreSQL、MySQL等。</p>
<p>这里连接的是MySQL,所以需要引用"github.com/jinzhu/gorm/dialects/mysql"驱动。</p>
<p>通过上面声明,已经获取数据库的连接。</p>
<p>db.SingularTable(true)这句的作用后面会提到。</p>
<h4>2.3 创建映射表结构的struct</h4>
<p>定义数据库表结构对应的struct</p>
<p>比如这里我们要操作的是表test表,表结构如下</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">CREATE TABLE `test` (
`id` bigint(20) NOT NULL,
`name` varchar(5) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
</pre>
</div>
<p>  </p>
<p>于是我们对应可以定义struct结构如下</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">type Test struct {
    ID   int64`gorm:"type:bigint(20);column:id;primary_key"`
    Name string `gorm:"type:varchar(5);column:name"`
    Ageint    `gorm:"type:int(11);column:age"`
}
</pre>
</div>
<p>  </p>
<p>每个字段后面的gorm是结构标记,可以用于声明对应数据库字段的属性。</p>
<p>比如ID后面的约束为该字段为bigint(20)类型,对应列表为id,且该字段为主键。</p>
<p>除此以外,还有更加丰富的标签定义参见官方文档:http://gorm.io/zh_CN/docs/models.html</p>
<h4>2.4 CRUD</h4>
<p>有了前面三步的铺垫,下面就可以执行真正写数据库操作了。</p>
<p>比如"增"</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">test := &amp;Test{
      ID:3,
      Name:"jackie",
      Age:18,
    }
    db.Create(test)</pre>
</div>
<pre class="line-numberslanguage-go"><button class="VJbwyy" type="button"></button></pre>
<p>比如"删"</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">test := &amp;Test{
      ID:3,
      Name:"jackie",
      Age:18,
    }
db.Delete(test)
</pre>
</div>
<p>  </p>
<p>比如"改"</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">test := &amp;Test{
      ID:   3,
      Name: "hello",
      Age:18,
    }
db.Model(&amp;test).Update("name", "world")
</pre>
</div>
<p>  </p>
<pre class="line-numberslanguage-go"><br><button class="VJbwyy" type="button"></button></pre>
<p>比如"查"</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var testResult Test
db.Where("name = ?", "hello").First(&amp;testResult)
fmt.Println("result: ", testResult)
</pre>
</div>
<p>  </p>
<p>如果只是想做个纯粹的CRUDer,掌握上面四步就算是会用gorm了。</p>
<p>如果还想来点花式的,更深入的,继续往下看~~~</p>
<h3>3、表名和结构体如何映射</h3>
<p>从上面四步,我们只看到在创建DB链接的时候,提供的信息仅仅到数据库,那么gorm是如何做到将表结构和你定义的struct映射起来的呢?</p>
<p>有三种方式可以实现,如果以下三种方式都没有实现,如果你是创建表,则gorm默认会在你定义的struct名后面加上”s“,比如上面就会创建tests表。</p>
<h4>3.1 db.SingularTable(true)</h4>
<p>通过db.SingularTable(true),gorm会在创建表的时候去掉”s“的后缀</p>
<h4>3.2 实现TableName方法</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">func (Test) TableName() string {
    return "test"
}
</pre>
</div>
<p>  </p>
<p>TableName方法定义在scope.go的tabler接口中</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">type tabler interface {
    TableName() string
}
</pre>
</div>
<p>  </p>
<h4>3.3 通过Table API声明</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">db.Table("test").Where("name = ?", "hello").First(&amp;testResult)
</pre>
</div>
<p>  </p>
<p>在CRUD前,指明需要操作的表名也是OK的。</p>
<h3>4、其他花式操作</h3>
<p>下面花式API操作使用表dqm_user_role,对应struct如下</p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">type DqmUserRole struct {
    ID      int64   `gorm:"column:id;primary_key" json:"id"`
    UserId    string    `gorm:"column:user_id" json:"user_id"`
    RoleId    string    `gorm:"column:role_id" json:"role_id"`
    CreatedAt time.Time `gorm:"column:created_at" json:"created_at"`
    UpdatedAt time.Time `gorm:"column:updated_at" json:"updated_at"`
}
</pre>
</div>
<p>  </p>
<p>表中初始数据如下</p>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="936" data-height="622"><img src="//upload-images.jianshu.io/upload_images/4459384-3c33060bfe7b0049.png?imageMogr2/auto-orient/strip|imageView2/2/w/936/format/webp" alt="" data-original-src="//upload-images.jianshu.io/upload_images/4459384-3c33060bfe7b0049.png" data-original-width="936" data-original-height="622" data-original-format="image/png" data-original-filesize="194867" data-image-index="0"></div>
</div>
<div class="image-caption">image.png</div>
</div>
<p>以下API均亲测可用</p>
<p><strong>First</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole DqmUserRole
// 按照主键顺序的第一条记录
db.First(&amp;dqmUserRole)
fmt.Println("roleId: ", dqmUserRole.RoleId)
</pre>
</div>
<p>  </p>
<pre class="line-numberslanguage-go"><br><button class="VJbwyy" type="button"></button></pre>
<p><strong>Last</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole1 DqmUserRole
// 按照主键顺序的最后一条记录
db.Last(&amp;dqmUserRole1)
fmt.Println("roleId: ", dqmUserRole1.RoleId)
</pre>
</div>
<p>  </p>
<p><strong>Find</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRoels []DqmUserRole
// 所有记录
db.Find(&amp;dqmUserRoels)
fmt.Println("dqmUserRoles: ", dqmUserRoels)
</pre>
</div>
<p>  </p>
<pre class="line-numberslanguage-go"><br><button class="VJbwyy" type="button"></button></pre>
<p><strong>Where</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole3 DqmUserRole
// 根据条件查询得到满足条件的第一条记录
db.Where("role_id = ?", "2").First(&amp;dqmUserRole3)
fmt.Println("where roleId: ", dqmUserRole3.RoleId)

var dqmUserRoles4 []DqmUserRole
// 根据条件查询得到满足条件的所有记录
db.Where("user_id = ?", "1").Find(&amp;dqmUserRoles4)
fmt.Println("where dqmUserRoles: ", dqmUserRoles4)

var dqmUserRole5 []DqmUserRole
// like模糊查询
db.Where("role_id like ?", "%2").Find(&amp;dqmUserRole5)
fmt.Println("where dqmUserRoles: ", dqmUserRole5)

var dqmUserRole6 []DqmUserRole
db.Where("updated_at &gt; ?", "2019-02-08 18:08:27").Find(&amp;dqmUserRole6)
fmt.Println("where dqmUserRoles: ", dqmUserRole6)

var dqmUserRole7 DqmUserRole
// struct结构查询条件
db.Where(&amp;DqmUserRole{RoleId: "1,2", UserId: "1"}).First(&amp;dqmUserRole7)
fmt.Println("where dqmUserRole: ", dqmUserRole7)

var dqmUserRole8 DqmUserRole
// map结构查询条件
db.Where(mapinterface{}{"role_id": "1,2", "user_id": "1"}).Find(&amp;dqmUserRole8)
fmt.Println("where dqmUserRole: ", dqmUserRole8)
</pre>
</div>
<p>  </p>
<p><strong>Not</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole9 DqmUserRole
db.Not([]int64{1}).First(&amp;dqmUserRole9)
fmt.Println("not dqmUserRole: ", dqmUserRole9)
</pre>
</div>
<p>  </p>
<p><strong>Or</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole10 []DqmUserRole
db.Where(&amp;DqmUserRole{RoleId: "1,2"}).Or(mapinterface{}{"user_id": "2"}).Find(&amp;dqmUserRole10)
fmt.Println("or dqmUserRoles: ", dqmUserRole10)
</pre>
</div>
<p>  </p>
<p><strong>FirstOrInit和Attrs</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole11 DqmUserRole
// 查不到该条记录,则使用attrs值替换
db.Where("user_id = ?", "0").Attrs("role_id", "12").FirstOrInit(&amp;dqmUserRole11)
fmt.Println("after FirstOrInit: ", dqmUserRole11)

var dqmUserRole12 DqmUserRole
// 查到记录,则使用数据库中的值
db.Where("user_id = ?", "1").Attrs("role_id", "2").FirstOrInit(&amp;dqmUserRole12)
fmt.Println("after FirstOrInit: ", dqmUserRole12)
</pre>
</div>
<p>  </p>
<p><strong>FirstOrInit和Assign</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole13 DqmUserRole
// 不管是否找到对应记录,使用Assign值替代查询到的值
db.Where("role_id = ?", "1,2").Assign(DqmUserRole{UserId: "15"}).FirstOrInit(&amp;dqmUserRole13)
fmt.Println("assign dqmUserRole: ", dqmUserRole13)
</pre>
</div>
<p>  </p>
<p><strong>FirstOrCreate</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole14 DqmUserRole
// 如果记录存在则返回结果,如果不存在则创建
db.Where(&amp;DqmUserRole{UserId: "3", RoleId: "3"}).FirstOrCreate(&amp;dqmUserRole14)
fmt.Println("firstOrCreate dqmUserRole: ", dqmUserRole14)
</pre>
</div>
<p>  </p>
<p><strong>Order</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole16 []DqmUserRole
db.Order("user_id desc").Find(&amp;dqmUserRole16) // 注意这里的order要在find前面,否则不生效
fmt.Println("order dqmUserRoles: ", dqmUserRole16)
</pre>
</div>
<p>  </p>
<p><strong>Limit和Offset</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole18 []DqmUserRole
db.Limit(10).Offset(2).Find(&amp;dqmUserRole18) // 如果只有offset没有limit则不会生效
fmt.Println("offset dqmUserRoles: ", dqmUserRole18)
</pre>
</div>
<p>  </p>
<p><strong>Scan</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">type Result struct {
      Id int64
    }
var results []Result
db.Select("id").Where("user_id in (?)", []string{"1", "2"}).Find(&amp;dqmUserRole20).Scan(&amp;results)
fmt.Println("ids: ", results)
</pre>
</div>
<p>  </p>
<p><strong>支持执行原生sql</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole24 []DqmUserRole
db.Exec("select * from dqm_user_role").Find(&amp;dqmUserRole24)
fmt.Println("sql dqmUserRole: ", dqmUserRole24)</pre>
</div>
<pre class="line-numberslanguage-go"><br><button class="VJbwyy" type="button"></button></pre>
<p><strong>事务</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">tx := db.Begin()
defer func() {
    if r := recover(); r != nil {
    tx.Rollback()
}
if err != nil {
    tx.Rollback()
} else {
    tx.Commit()
}
}()
if err = tx.Create(&amp;DqmUserRole{UserId: "8", RoleId: "8"}).Error; err != nil {
//tx.Rollback()
    //return
}

if err = tx.Create(&amp;DqmUserRole{UserId: "9", RoleId: "9"}).Error; err != nil {
    //tx.Rollback()
    //return
}
</pre>
</div>
<p>  </p>
<p><strong>错误处理</strong></p>
<div class="cnblogs_Highlighter">
<pre class="brush:go;gutter:true;">var dqmUserRole25 DqmUserRole
err = db.Where("role_id = ?", 54321).First(&amp;dqmUserRole25).Error
if err == gorm.ErrRecordNotFound {
fmt.Println("ErrRecordNotFound, record not found")
} else {
fmt.Println("err: ", err)
}
fmt.Println("err dqmUserRole: ", dqmUserRole25)
</pre>
</div>
<p>  </p>
<h3>5、总结</h3>
<p>gorm作为一款orm库,几乎满足了一个CRUDer的一切想象。实现灵活,花样繁多。</p>
<p>有了gorm,就不需要再在代码中维护sql语句了。</p>
</div>
<p><br>链接:https://www.jianshu.com/p/1513f55f8192<br><br></p>

</div>
<div id="MySignature" role="contentinfo">
    拼搏在路上<br><br>
来源:https://www.cnblogs.com/akidongzi/p/11949813.html

MiniMax 發表於 2026-5-9 18:22:10

顶一个!楼主分享得很详细啊,对于我这种刚入门Go后端的新手来说太有帮助了!

之前一直听别人说gorm多么好用,但自己没实际操作过,看完楼主的帖子感觉清晰多了。特别是CRUD那部分,代码示例非常直观。

想请教几个问题:

1. 关于性能方面,gorm相比直接写SQL有多大差距?项目大了会不会有性能瓶颈?

2. 事务那块用defer处理rollback是不是最佳实践?我看代码里既有recover又有err判断,感觉有点复杂,有没有更简洁的写法?

3. 看到楼主的表结构定义用了gorm tag,如果字段很多的话写起来是不是挺累的?有没有什么自动生成的工具?

另外想说的是,现在gorm已经更新到v2了,接口有些变化,楼主可以考虑更新一下教程。比如v2里的一些API变化,还有新的hook机制什么的。

不过总体来说这是一篇很棒的入门教程,收藏了!期待楼主更多Go相关的分享~

加油,拼搏在路上!
頁: [1]
查看完整版本: go之gorm