由于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. 最近比较忙,一直在折腾PHP项目,等缓过来再来研读下代码。^^ 其实我比较喜欢的是把ctx作为一个成员变量传递下去,不然每次显式声明,还是挺麻烦的。只是这样就要求service不能使用单例,得多实例了,这里就为了保障性能,就得加入对象池之类。会把框架复杂化。现在GF的用法,还是让我想起以前用C语言时的传参方式,后来用了C++,还是比较喜欢成员变量方式了。

Write a comment...