事务

事务的封装和管理

Posted by Liangjf on August 24, 2020

事务的封装和管理

在平时开发中, 有些业务是需要保证ACID的, 因此事务就派上用场了.

大多是否在应用中是使用orm来操作数据库的, 下面的所有例子都是使用orm为例子.

用事务, 大多数人一般是这样用的:在需要用到事务的地方, 都来一堆以下的代码:

事务业务1:

var err error
//开始事务
if err = o.Begin(); err != nil {
    return nil, err
}

//操作1
if err = db.Exec(); err !=nil {
    _ = o.Rollback()
}

//操作2
if err = db.Exec(); err !=nil {
    _ = o.Rollback()
}

//操作3
if err = db.Exec(); err !=nil {
    _ = o.Rollback()
}

//提交事务
_ = o.Commit()

事务业务2:

var err error

//开始事务
if err = o.Begin(); err != nil {
    return nil, err
}

//操作1
if err = db.Exec(); err !=nil {
    _ = o.Rollback()
}

//操作2
if err = db.Exec(); err !=nil {
    _ = o.Rollback()
}

//操作3
if err = db.Exec(); err !=nil {
    _ = o.Rollback()
}

//提交事务
_ = o.Commit()

事务业务n…

这样使用, 正确性是没有问题的. 但是有一些缺点:

  • 代码臃肿
  • 不好管理事务
  • 事务没有统一入口, 记录各处都记录事务, 造成混乱
  • 不够傻瓜式

下面引出一种正确的, 优秀的处理方案

事务优化封装管理

  • 只提供一个事务接口, 封装好事务的处理, 业务开发者只需关心操作逻辑, 不需要关心事务, 事务的处理对业务开发者是透明的
  • 接口入参自定的业务逻辑处理函数, 业务开发只需要传入具体的操作就ok
  • 在事务接口里统一做统计,记录相关工作

源码如下:

//业务逻辑的函数, 开发者在使用事务时, 定义这样的函数传入Transaction就行了
type TxFunc func(db orm.Ormer) (interface{}, error)

//真正的事务处理函数
func Transaction(f TxFunc) (interface{}, error) {
	var(
		var err error
		o = orm.NewOrm()
	)

	//开启事务
	if err = o.Begin(); err != nil {
		return nil, err
	}
	
	defer func() {
		//捕捉panic
		if p := recover(); p != nil {
			_ = o.Rollback()
			panic(p) // re-throw panic after Rollback
		} else if err != nil {
			//回滚不覆盖err, 是因为回滚就是因为有业务的报错, 所以不应该被这条语句覆盖掉业务的err
			_ = o.Rollback() // err is non-nil; don't change it
		} else {
			//这里返回err, 是因为业务的err肯定是nil才走到这里, 如果在最后的commit的err非nil,是需要返回的
			err = o.Commit() // err is nil; if Commit returns error update err
		}
	}()

	//调用业务逻辑
	return f(o)
}

使用例子:

//业务逻辑处理函数
txF := func(db orm.Ormer) (interface{}, error) {
		var err error

		err = db.Exec()

		err = db.Exec()

		err = db.Exec()

		return nil, err
	}

//事务统一接口
res, err = models.Transaction(txF)
if err != nil {
	logs.Error("a Transaction err:%s", err.Error())
	return
}

也可以这样使用:

//事务统一接口
res, err = models.Transaction(func(db orm.Ormer) (interface{}, error) {
		var err error

		err = db.Exec()

		err = db.Exec()

		err = db.Exec()

		return nil, err
	})
if err != nil {
	logs.Error("a Transaction err:%s", err.Error())
	return
}

经过这样的优化, 事务的处理应该是简洁简单, 比较”傻瓜式”了.

总结

这样来封装事务接口, 可以做到事务的回滚和提交处理对开发者是透明的, 能够更好的统一管理事务, 不会造成事务sql随处可见的情况