由于GF本身不支持处理ORM的事务嵌套,所以给业务微化封装带来不少麻烦。写代码也不再顺畅。

经过不断地尝试,我在自己使用的GF业务框架中解决了此问题。

首先,我将所有的DAO操作进行了一次封装。不再直接使用Dao单例实例。而是由一个方法来统一生成。这些方法会属于TransactionManage类

同时,让每一个Service继承自一个TransactionManage类,该类主要用于管理tx(这里为了保证tx统一性,用到了指针的指针)及提供事务方法Transaction。

这样,当我们需要开启事务时,只需要在service中调用父类的Transaction即可,在Transaction中会先判断是否存在tx对象,存在则直接调用回调函数(不再开启新事务),不存在则开启一个新的事务,并保存新事务的指针到txp(tx指针的指针)中。

而在api中,也有类似service的实现。只不过,会有一系列service类的封装函数。

这样,在整个调用过程中,就会如下:api → getXXXService() → getXXXDao() ,在每次调用时,会传递存在的tx对象指针。不管在哪一层开启了事务。均可判断是否存在上一级的TX,而通过此来判断是否需要开启新事务。

这里使用时,需要在api中,每次调用 GetServiceManage()来创建service管理对象。这个管理对象会在创建新的service时被传递下去。

这样调整后,Dao调用只是加了一层封装。本质上还是在封装中决策是否调用Dao.TX方法还是直接返回Dao.XXXDao。如下:


func (this BaseManage) TokenDao() *internal.TokenDao {
    if tx := this.TX(); tx == nil {
        return Token.TokenDao
    } else {
        return Token.TX(tx)
    }
}


而这个BaseMange是dao.BaseMange,是manage.TransactionManage的子类。故尔,其拥有 tx管理及开启事务的方法封装。

service由原来的单例改为了实时创建。创建后的service对象会放于serviceInstanceManage中管理起来,这样二次使用时,不会重复创建。




func NewServiceManage() *ServiceManage {
    o := new(ServiceManage)
    o.trans.TXP(new(*gdb.TX))
    o.ServiceInstancesManage.TransactionManage = &o.trans
    return o
}



//用于service
type BaseManage struct {
    dao.BaseManage
    service *ServiceInstancesManage
}

func (this *BaseManage) Init() { //构造函数,空方法,占位

}

func (this *BaseManage) Destroy() { //析构函数,空方法,占位

}

type ServiceInstancesManage struct {
    *manage.TransactionManage

    accessTokenService   *accessTokenService
    //其它service 


}

func (this *ServiceInstancesManage) AccessTokenService() *accessTokenService {
    sp := &this.accessTokenService
    if *sp == nil {
        *sp = new(accessTokenService)
        (*sp).service = this
        (*sp).Init()
    }
    (*sp).TXP(this.TXP())
    return *sp
}





同时,serviceInstanceManage也是manage.TransactionManage的子类。在使用时,应该在api中调用NewServiceManage()生成管理对象,之后就可以在管理对象中调用需要使用的service方法(这里为AccessTokenService() )。而该AccessTokenService继承自service.BaseMange从而继承自dao.BaseManage,当在service内需要调用其它service时,需要可以如此调用:this.service.AccessTokenService()..... 这样整个事务就会被传递。


经测试,事务嵌套已经实现。不过这里可能存在内存泄露,还需要进一步完善代码。


当然,具体的代码有很多细节需要处理。这里只是说了一个大概。只是引个思路。也希望作者能有更佳的解决方案。


  • No labels

16 Comments

  1. 朱华 Hunk 你把这篇文章移动到自己的个人空间下

    1. 没个人空间-.-。不合适就删除了吧。

      1. 写得挺好,为什么要删除呢,你可以在这里开通自己的个人空间。

          1. 我去,确实够折腾的,在这里啊大佬

            1. 我的没有。。。。是少了啥东东?

                1. 有了~ 移动完成

                  1. 👍 不过你的代码高亮有点问题,wiki编写参考 文档编写帮助

  2. 其实我想了想,可以通过上下文变量Context传递下去,通过判断Context中的TX对象来判断是否嵌套事务。

    1. 如果自己通过上下文解决的话 是不是 每个地方都要用类似的这个代码做判断 会不会有些冗余

       if tx := this.TX(); tx == nil {
              return Token.TokenDao
          } else {
              return Token.TX(tx)
          }


      1. 肯定会冗余,不过在写代码时会轻松很多。其实最好的解决方案是采用多实例,不然就只有依赖Context传递,只是我个人不太喜欢用Context

  3. 封装的 事务管理能自动生成吗 我这边好多表全部都要建立 太麻烦了

    1. 要封装得修改gf工具。我是不想去动gf本身。以后有时间再考虑怎么更好的处理这个吧。

      1. 新版本就是默认使用了context实现嵌套事务,工具那边这两天也会更新

        1. You are not logged in. Any changes you make will be marked as anonymous. You may want to Log In if you already have an account.
        1. 最近比较忙,一直在折腾PHP项目,等缓过来再来研读下代码。^^ 其实我比较喜欢的是把ctx作为一个成员变量传递下去,不然每次显式声明,还是挺麻烦的。只是这样就要求service不能使用单例,得多实例了,这里就为了保障性能,就得加入对象池之类。会把框架复杂化。现在GF的用法,还是让我想起以前用C语言时的传参方式,后来用了C++,还是比较喜欢成员变量方式了。