当前位置: 首页 > news >正文

莱西网站建设哪家好网站设计

莱西网站建设哪家好,网站设计,新闻网站传播力建设,成都直销网站建设在写代码过程中遇到了需要使用gorm执行sql事务的情况,研究了一下各位大佬的实现方案,结合了自身遇到的问题,特此记录。 代码架构介绍 . ├── apis ├── config ├── internal │ ├── constant │ ├── controller │ ├──…

在写代码过程中遇到了需要使用gorm执行sql事务的情况,研究了一下各位大佬的实现方案,结合了自身遇到的问题,特此记录。



代码架构介绍

.
├── apis
├── config
├── internal
│   ├── constant
│   ├── controller
│   ├── logic
│   ├── model
│   ├── repo
│   ├── router
│   └── service
├── library
├── pkg
├── scripts

目录结构其实很简单,主要参考了goframe的架构。internal/logic放逻辑代码,internal/repo是dao层,负责与数据库进行交互。



演变过程

最开始的时候是直接把对事务操作放在repo层中了。比如说有两张表,一张用户消费记录表,一张用户积分表,在用户消费完成后就要增加对应的积分。那么在repo层就会有一个函数负责开启事务,写消费记录表,写积分表,提交事务。在logic层中只需要调用这个函数就可以同时完成这两个操作。


看起来很美好,逻辑分的很清晰,但是这么做其实有个问题。如果logic层中有逻辑只操作消费记录表呢,或者用户消费积分只操作积分表呢,或者又有其他的表需要进行联合操作。这种写法会在repo层不断增加一个又一个函数,导致函数越来越多,因为承载了过多的业务逻辑。


发现这种情况之后,我们想可不可以把业务逻辑从repo层抽离,放在logic层进行处理,repo层只做简单的增删改查。那么repo层就可以只实现几个基础的函数,Create(),Update(),Selete()等,然后在logic层通过调用这几个函数实现复杂的逻辑操作。还是之前那个例子,比如说先消费再增加积分就可以变成对两个函数调用。

	// logic 层func buy() {tx := db.begin()cosume.Create(tx)point.Update(tx)tx.commit()}

分层是明确了,但是又引出了新的问题,就是如果我想使用事务,那么就需要在logic层对事务进行开启和提交,并且logic层还需要保存db的连接(用于开启事务),并且将tx传给repo层进行操作,这不合理。logic应该专注于代码逻辑的处理,而不是如何开启一个数据库事务。
目标很明确,如何让logic层只关注与逻辑的操作,而不是开启事务。上文也说到repo层只做一些简单的增删改查的操作,在repo里实现是不可能了,那么只能采取终极方案,在logic和repo之间增加一层中间层,专门用于开启事务。
这个中间层更像一个管理层,用于管理具体操作数据库的repo类和事务的操作,当需要开启事务时,logic只需要编写好业务逻辑,然后调用管理层的接口。管理层负责开启事务,调用logic层编写好的逻辑,提交/回滚事务即可。
这样logic层既不用处理事务相关的任务,repo层也不需要处理复杂的业务逻辑。



实现方案

总结一下上面所说,我们需要实现一个管理类,在管理类里面提供一个事务接口,这个接口的入参有一个func,这个func就是logic层编写好的各种逻辑,为了将tx传递给repo,我们是将其放在了ctx中。

管理层示例

Internal/repo/transaction.go

这个代码参考的时 gorm.Transaction(),做了简单修改。注释写的很详细了。

// Mysql Mysql数据库
type Mysql struct {db *gorm.DB
}func (m *Mysql) Transaction(ctx context.Context, fc func(txCtx context.Context) error) error {panicked := truevar err error// 拿到 ctx 里面的 db 连接,如果没有则使用默认的 db 连接db := m.getDB(ctx)// ...// 使用这个连接开启事务tx := db.WithContext(ctx).Begin()if tx.Error != nil {return tx.Error}defer func() {// Make sure to rollback when panic, Block error or Commit errorif panicked || err != nil {tx.Rollback()}}()// 将事务的 tx 写入 ctx 中,为了真正操作数据库的函数拿到 txtxCtx := generateContextWithDB(ctx, tx)// 调用 logic 层传入的 funcif err = fc(txCtx); err == nil {panicked = false// 如果没有报错就提交事务return tx.Commit().Error}panicked = false// 把事务内的报错转发出去return err
}

调用示例

/internal/logic/manager.go

func (m *manager) Buy(ctx context.Context) error {// 调用 Transaction 函数,txCtx 则为带有 tx 的 ctxif err := m.mysql.Transaction(ctx, func(txCtx context.Context) {// 这里只需要实现对应的业务即可,无需关注事务的开启和提交/回滚逻辑m.mysql.Consume(txCtx).Create()m.mysql.Point(txCtx).Update()}); err != nil {// do something}return nil
}

从 manager 这个函数的角度来看,整个事务执行的逻辑为,通过 Transaction 第一个参数 ctx 获取要执行事务的 db 连接,开启事务后将 tx 封到 context 中,然后传入 Transaction 第二个参数 func() 的入参中。通过将 txCtx 传入到不同的 repo 类中来实现各个 repo 使用同一个 tx 进行事务的操作。Transaction 函数保证了中间有任何错误都会进行回滚和无错误的提交操作。

Consume()和Point()做的是解析 txCtx 中的 db 连接,然后 new 一个repo 类返回。getDB() 负责返回 ctx 里的 db 接。

func (m *Mysql) Consume(ctx context.Context) ConsumeRepo {return NewConsumeRepo(m.getDB(ctx))
}

整体的代码逻辑就是这样,如果需要支持多个引擎,其实可以考虑抽出一个interface,各个引擎实现这个 interface 的接口即可。



gorm 一些写法

多表联查

我们的表设计的不是很规范,导致表和表之间基本上没有什么关联,外键也没有,所以没有办法使用gorm提供的preload,采取的是手动join的方式。

func (c *ConsumePoint) SelectForUpdate(ctx context.Context) (*model.ConsumeAndPoint, error) {// 接收数据的结构var record model.ConsumeAndPointif err := c.db.WithContext(ctx).// 主表Table("t_consume").// 返回什么Select("t_consume.*, t_point.f_point").// 连接方式Joins("join t_point on t_consume.f_user_id = t_point.f_user_id").// 只查询一条数据First(&record).Error; err != nil {// do something}return &record, nil
}

select for update

先读后更新的数据竞争且应该将加锁操作放到事务中,防止锁被自动释放,其实主要就在于 Set(“gorm:query_option”, “FOR UPDATE”) 这一行。如果采用上面多表联查的方式,设置了 FOR UPDATE 后两张表的内容都会有X锁,直到事务提交。可以用这个特性来搞分布式的竞争更新。

func UpdateUser(db *gorm.DB, id int64) error {tx := db.Begin()defer func() {if r := recover(); r != nil {tx.Rollback()}}()if err := tx.Error; err != nil {return err}user := User{}// 锁住指定 id 的 User 记录if err := tx.Set("gorm:query_option", "FOR UPDATE").First(&user, id).Error; err != nil {tx.Rollback()return err}// 更新操作...// commit事务,释放锁if err := tx.Commit().Error; err != nil {return err}return nil
}
http://www.zhongyajixie.com/news/22926.html

相关文章:

  • 建站平台费用搜索引擎优化的办法有哪些
  • 淘宝上做网站可信吗上海全国关键词排名优化
  • wordpress日主题下载深圳关键词推广整站优化
  • 外贸独立站有哪些平台如何设计一个网页
  • 网站logo如何做清晰seo云优化
  • 个人网站没人访问搜索词
  • 青岛 机械 中企动力提供网站建设微信广告投放推广平台
  • 北京做网站的公司排名做谷歌推广比较好的公司
  • 专门做玉的网站深圳专业seo外包
  • 网站建设行业赚钱么哪个浏览器看黄页最快夸克浏览器
  • 平湖建设局网站成都优化官网公司
  • 学校门户网站作用余姚关键词优化公司
  • java网站开发需要什么软件网站建设费用都选网络
  • 如何获得个人免费网站空间会计培训班多少钱
  • 移动网站开发百度推广登录页面
  • WordPress邮箱验证登录专业网站优化
  • 有没有专门做根雕的网站沈阳专业seo关键词优化
  • 天台县建设规划局网站百度竞价怎么做效果好
  • 杭州软件网站建设竞价托管推广公司
  • 长春做网站搜索引擎营销方案例子
  • 自适应网站开发文字大小如何处理百度seo2022新算法更新
  • 深圳网站建设制作开发公司seo裤子的关键词首页排名有哪些
  • 外贸实用工具优化营商环境的金句
  • 怀化seo网站河南今日头条新闻
  • 经常使用( )对网页的布局进行控制灰色seo关键词排名
  • 潍坊网站建设服务新手怎么做电商运营
  • 外贸企业网站制作公司山东企业网站建设
  • 企业网站设计概念河南网站网络营销推广
  • 网站设计的留言怎么做百度seo推广计划类型包括
  • 做英文网站多钱电子商务seo是什么意思