关于 golang 中 db 等事务处理时代码逻辑处理

本贴最后更新于 1413 天前,其中的信息可能已经事过境迁

概述

在编码过程中,特别是在写 dbo 层的数据库操作时,我们经常会遇到写事务的地方,在很长一段时间我都是使用的正常逻辑如下:

//在新建和更新的时候注意自动创建关联和更新 func (a *ArticleRepo) Create(article interface{}) (bool, error) { db := a.db.Begin() var tags []*models.Tag user := &models.User{} ac := article.(*models.Article) if err := db.Create(&ac).Error; err != nil { db.Rollback() return false, err } for _, t := range ac.Tags { tag := &models.Tag{} if err := db.Where("name = ? ", t.Name).First(&tag).Error; err != nil { tag.Name = t.Name tag.UserId = ac.AuthorID } tags = append(tags, tag) } if err := db.Where("id = ?", ac.AuthorID).First(&user).Error; err != nil { db.Rollback() return false, errors.New("请登陆") } if err := db.Model(&user).UpdateColumn("lottery_num", gorm.Expr("lottery_num + ?", 1)).Error; err != nil { db.Rollback() return false, err } if err := db.Model(&ac).Association("Tags"). Append(tags).Error; err != nil { db.Rollback() return false, err } if err := db.Commit().Error; err != nil { db.Rollback() return false, err } return true, nil }

我发现,在处理过程中会经常要写到 db.rollback 于是在见识过比较多的常用写法后写法以“匿名函数”的写法提取出主要流程完成主体业务省下大量无用代码,同时也避免忘记 rollback 和 commit,目前的写法以 transction_test.go 中为准:

首先创建事务接受上下文,db 源,fn 方法,然后将错误 recover,进行 wraper。

transction.go:

package dbx import ( "context" "fmt" "github.com/pkg/errors" ) func NewTransaction(ctx context.Context, db Database, fn func(ctx context.Context, tx Transaction) error) (err error) { tx, err := db.Begin() if err != nil { return errors.Wrap(err, "begin") } // recover defer func() { if r := recover(); r != nil { var ok bool err, ok = r.(error) if !ok { err = fmt.Errorf("%v", r) } err = errors.WithMessage(err, "recover") } if err != nil { if e := tx.Rollback(); e != nil { err = errors.WithMessagef(err, "rollback %v", e) } return } err = errors.Wrap(tx.Commit(), "tx commit") }() return fn(ctx, tx) }

sqlconn.go:

package dbx import ( "context" "database/sql" "github.com/jmoiron/sqlx" ) type ( Database interface { Begin() (Transaction, error) Ping() error } Transaction interface { Commit() error Rollback() error Get(dst interface{}, query string, args ...interface{}) error Select(dst interface{}, query string, args ...interface{}) error Exec(query string, args ...interface{}) (sql.Result, error) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) GetContext(ctx context.Context, dst interface{}, query string, args ...interface{}) error SelectContext(ctx context.Context, dst interface{}, query string, args ...interface{}) error } ) type DatabaseSQLX struct { *sqlx.DB } func (db *DatabaseSQLX) Begin() (Transaction, error) { tx, err := db.Beginx() return &Tx{Tx: tx}, err } type Tx struct { *sqlx.Tx } func (t *Tx) Commit() error { return t.Tx.Commit() } func (t *Tx) Rollback() error { return t.Tx.Rollback() }

mysql.go

package dbx import ( "fmt" "github.com/jmoiron/sqlx" "github.com/pkg/errors" ) const DefaultMySQLDSN = "%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=true" func ConnectMySQL(format string, data DSN) (err error) { dsn := fmt.Sprintf(format, data.User, data.Password, data.Host, data.Port, data.DBName) db, err := sqlx.Connect("mysql", dsn) if err != nil { return errors.WithStack(err) } db.SetMaxIdleConns(5) db.SetMaxOpenConns(30) DB = &DatabaseSQLX{DB: db} return errors.WithStack(DB.Ping()) }

db.go

type DSN struct { Host string Port int User string Password string DBName string }

trainsaction.go

func TestNewTransaction(t *testing.T) { ctx := context.Background() mockDB := &MockDB{} t.Run("success", func(t *testing.T) { defer resetTrans() assert.Equal(t, nil, NewTransaction(ctx, mockDB, func(ctx context.Context, tx Transaction) error { return nil })) assert.Equal(t, false, rollback) assert.Equal(t, true, commit) }) t.Run("return error", func(t *testing.T) { defer resetTrans() returnErr := errors.New("return error") err := NewTransaction(ctx, mockDB, func(ctx context.Context, tx Transaction) error { return errors.WithStack(returnErr) }) assert.Equal(t, true, rollback) assert.Equal(t, false, commit) assert.Equal(t, returnErr, errors.Cause(err)) }) t.Run("panic error", func(t *testing.T) { defer resetTrans() panicErr := errors.New("panic error") err := NewTransaction(ctx, mockDB, func(ctx context.Context, tx Transaction) error { panic(panicErr) }) assert.Equal(t, true, rollback) assert.Equal(t, false, commit) assert.Equal(t, panicErr, errors.Cause(err)) }) }
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    500 引用 • 1396 回帖 • 252 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
xhaoxiong
站在巨人的肩膀上学习与创新

推荐标签 标签

  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    53 引用 • 190 回帖 • 1 关注
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 7 关注
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 27 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 53 关注
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖 • 3 关注
  • SMTP

    SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。

    4 引用 • 18 回帖 • 639 关注
  • Thymeleaf

    Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。类似 Velocity、 FreeMarker 等,它也可以轻易的与 Spring 等 Web 框架进行集成作为 Web 应用的模板引擎。与其它模板引擎相比,Thymeleaf 最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个 Web 应用。

    11 引用 • 19 回帖 • 395 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 105 关注
  • 互联网

    互联网(Internet),又称网际网络,或音译因特网、英特网。互联网始于 1969 年美国的阿帕网,是网络与网络之间所串连成的庞大网络,这些网络以一组通用的协议相连,形成逻辑上的单一巨大国际网络。

    98 引用 • 367 回帖
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖 • 1 关注
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 441 关注
  • Ant-Design

    Ant Design 是服务于企业级产品的设计体系,基于确定和自然的设计价值观上的模块化解决方案,让设计者和开发者专注于更好的用户体验。

    17 引用 • 23 回帖 • 2 关注
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 81 关注
  • 996
    13 引用 • 200 回帖
  • 职场

    找到自己的位置,萌新烦恼少。

    127 引用 • 1708 回帖 • 1 关注
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    63 引用 • 289 回帖
  • TGIF

    Thank God It's Friday! 感谢老天,总算到星期五啦!

    291 引用 • 4495 回帖 • 664 关注
  • 链书

    链书(Chainbook)是 B3log 开源社区提供的区块链纸质书交易平台,通过 B3T 实现共享激励与价值链。可将你的闲置书籍上架到链书,我们共同构建这个全新的交易平台,让闲置书籍继续发挥它的价值。

    链书社

    链书目前已经下线,也许以后还有计划重制上线。

    14 引用 • 257 回帖 • 2 关注
  • Typecho

    Typecho 是一款博客程序,它在 GPLv2 许可证下发行,基于 PHP 构建,可以运行在各种平台上,支持多种数据库(MySQL、PostgreSQL、SQLite)。

    12 引用 • 67 回帖 • 445 关注
  • 以太坊

    以太坊(Ethereum)并不是一个机构,而是一款能够在区块链上实现智能合约、开源的底层系统。以太坊是一个平台和一种编程语言 Solidity,使开发人员能够建立和发布下一代去中心化应用。 以太坊可以用来编程、分散、担保和交易任何事物:投票、域名、金融交易所、众筹、公司管理、合同和知识产权等等。

    34 引用 • 367 回帖
  • Love2D

    Love2D 是一个开源的, 跨平台的 2D 游戏引擎。使用纯 Lua 脚本来进行游戏开发。目前支持的平台有 Windows, Mac OS X, Linux, Android 和 iOS。

    14 引用 • 53 回帖 • 563 关注
  • Openfire

    Openfire 是开源的、基于可拓展通讯和表示协议 (XMPP)、采用 Java 编程语言开发的实时协作服务器。Openfire 的效率很高,单台服务器可支持上万并发用户。

    6 引用 • 7 回帖 • 118 关注
  • OpenStack

    OpenStack 是一个云操作系统,通过数据中心可控制大型的计算、存储、网络等资源池。所有的管理通过前端界面管理员就可以完成,同样也可以通过 Web 接口让最终用户部署资源。

    10 引用 • 3 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    89 引用 • 1251 回帖 • 393 关注
  • 人工智能

    人工智能(Artificial Intelligence)是研究、开发用于模拟、延伸和扩展人的智能的理论、方法、技术及应用系统的一门技术科学。

    115 引用 • 319 回帖
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖 • 2 关注
  • 周末

    星期六到星期天晚,实行五天工作制后,指每周的最后两天。再过几年可能就是三天了。

    14 引用 • 297 回帖 • 1 关注