由于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()..... 这样整个事务就会被传递。
经测试,事务嵌套已经实现。不过这里可能存在内存泄露,还需要进一步完善代码。
当然,具体的代码有很多细节需要处理。这里只是说了一个大概。只是引个思路。也希望作者能有更佳的解决方案。
16 Comments
郭强
朱华 Hunk 你把这篇文章移动到自己的个人空间下
朱华 Hunk
没个人空间-.-。不合适就删除了吧。
郭强
写得挺好,为什么要删除呢,你可以在这里开通自己的个人空间。
朱华 Hunk
折腾了半天。给移到这里了:关于ORM的事务嵌套的业务级调整(不会影响GF本身) - Hunk Zhu - Confluence (atlassian.net)
郭强
我去,确实够折腾的,在这里啊大佬
朱华 Hunk
我的没有。。。。是少了啥东东?
郭强
再试试呢?
朱华 Hunk
有了~ 移动完成
郭强
👍 不过你的代码高亮有点问题,wiki编写参考 文档编写帮助
郭强
其实我想了想,可以通过上下文变量
Context
传递下去,通过判断Context
中的TX对象来判断是否嵌套事务。何云辉
如果自己通过上下文解决的话 是不是 每个地方都要用类似的这个代码做判断 会不会有些冗余
if tx := this.TX(); tx == nil {
return Token.TokenDao
} else {
return Token.TX(tx)
}
朱华 Hunk
肯定会冗余,不过在写代码时会轻松很多。其实最好的解决方案是采用多实例,不然就只有依赖Context传递,只是我个人不太喜欢用Context
何云辉
封装的 事务管理能自动生成吗 我这边好多表全部都要建立 太麻烦了
朱华 Hunk
要封装得修改gf工具。我是不想去动gf本身。以后有时间再考虑怎么更好的处理这个吧。
郭强
新版本就是默认使用了
context
实现嵌套事务,工具那边这两天也会更新朱华 Hunk
最近比较忙,一直在折腾PHP项目,等缓过来再来研读下代码。^^ 其实我比较喜欢的是把ctx作为一个成员变量传递下去,不然每次显式声明,还是挺麻烦的。只是这样就要求service不能使用单例,得多实例了,这里就为了保障性能,就得加入对象池之类。会把框架复杂化。现在GF的用法,还是让我想起以前用C语言时的传参方式,后来用了C++,还是比较喜欢成员变量方式了。
Add Comment