最近一直在看Apache OFbiz entity engine的源码。为了能够更透彻得理解,也因为之前没有看人别人写过分析它的文章,所以决定自己来写一篇。
首先,我提出一个问题,如果你有兴趣可以想一下它的答案:
JDBC真的给数据访问提供了足够的抽象,以至于你可以在多个支持jdbc访问的数据库之间任意切换而完全不需要担心你的数据访问代码吗?
我曾经在微博上有过关于该问题的思考:
其实这个感慨正是来自于我之前在看的一篇关于jdbc的文章,里面提到了jdbc中的一些设计模式(工厂方法),提供了与底层数据库交互的抽象(不可否认是非常好的做法),可以应对在不需要修改DAO的情况下,自由切换数据库。而我结合最近在看的OFbiz的entityengine发了上面的这些吐槽!
OFbiz对数据源的访问是否仍然借助于JDBC?当然,毫无疑问。但它解决了我在吐槽中所说的,它并不依赖程序员遵循SQL标准,而是通过entity engine根据它给出的语义,生成标准的sql。这些sql包含了DML、DDL、TCL(事务控制语言),从而提供一套完整的数据访问Engine。它带来的好处是什么?省去了大量机械而重复的DAO CRUD 的编写工作量,无缝支持多达13种数据库,抽象了常用的查询/筛选逻辑,提供了通过配置生成服务而无需编写代码的底层支持!(当然好处还不止这些)。
下面我们就一起来看看entity engine到底是如何做到这些的。
SQLProcessor 负责entityengine中所有SQL的最终执行,包括了事务的提交、回滚等。从该类的实现中你能看出,ofbiz最终跟数据库打交道的还是JDBC,而因为JDBC本身就是对数据库访问的抽象,所以使用JDBC操作数据库对于任何RDBMS都是适用的,你只需要提供最终使用的数据库的jdbc-driver。但数据库的某些特性,sql,数据类型,这些并不是完全一致的标准,因此entityengine提供的语义层有效得屏蔽了SQL与字段类型等在各个数据库上的差异,使得最终用户无需直接这些差异打交道,一切都有它来处理。
GenericDAO使用了多线程技术来执行某些操作,因此创建该对象的时候需要实例化一组线程以及创建一个线程池,这些操作相对来说有些“昂贵”,在其内部采用一个staticmap 来缓存已被成功创建的对象:
public static GenericDAO getGenericDAO(GenericHelperInfo helperInfo) {
GenericDAO newGenericDAO = genericDAOs.get(helperInfo.getHelperFullName());
if (newGenericDAO == null) {
genericDAOs.putIfAbsent(helperInfo.getHelperFullName(), new GenericDAO(helperInfo));
newGenericDAO = genericDAOs.get(helperInfo.getHelperFullName());
}
return newGenericDAO;
}
GenericHelper接口提供了更偏向业务的数据访问抽象,这使得你可以不用去关注数据源是什么,基于数据源的实现交给该接口的实现类即可。事实上,GenericHelper也确实有一个完全基于内存的实现(MemoryHelper),只是除了用于测试,好像没有正式在线上场景中使用,不过这是一个不错的想法——随着nosql的流行,一部分关系不是很强或者不是很重要的数据可以基于一些memorydb来实现。来看看该接口极其实现类的关系图:
它有两个实现者,其中一个实现便是GenericHelperDAO,毫无疑问它是基于RDBMS的实现,因为它内部都是通过GenericDAO的实例来完成数据访问的。另一个之前提到过了,是基于内存的实现。
梳理一下上面的几个部件:GenericDAO实现了基本的数据访问操作的(CRUD)。这其中,每个方法都需要具体去跟数据库通信,通过什么?通过SQLProcessor,SQLProcessor负责每次SQL的执行。通常一个系统里对于数据的访问/操作并不仅仅只是简单的CRUD,其中还包含有更复杂的操作(比如关系查询、条件删除等),因此OFbiz为这些相对复杂的操作抽象出了一个接口GenericHelper。GenericHelperDAO是对GenericHelper的实现,其内部借助于GenericDAO,来实现这些相对复杂的数据访问。到最后我们会看到entity engine内最关键的一个部件——Delegator,其内部就是基于GenericHelper来实现数据访问的。
GenericHelperInfo提供了对数据库建立连接所需信息的封装(包括用户名、密码等)
该类提供了对JDBC连接的集中创建,提供了很多静态方法,根据给定的不同参数,来创建JDBC连接:
同时也提供了加载/卸载jdbc-driver以及关闭连接的动作。
同上面的数据库连接工厂,此工厂类也对GenericHelper的实例对象进行类缓存。后面会看到entityengine最关键的接口Delegator就拥有一个getEntityHelper方法,该方法用于获取GenericHelper的实例,其实现就是通过GenericHelperFactory来获取的。
类关系图:
要提供一个通用的数据访问引擎,抽象出数据库相关的表示对象是必须的。entityengine对于所有数据库相关对象的定义都放在org.ofbiz.entity.model package下。
其中,ModelInfo提供了对model基本信息的定义(这些信息通常是一些描述信息,跟具体数据库相关的内容无关)。
ModelEntity继承自ModelInfo,它是真实的用于表述一条数据库表中记录的实体。上图中一些对象会作为依赖对象成为它的一部分。它本身也是Transfer Object模式的实现(更多关于ofbiz entity engine中使用过的j2ee pattern,请看我之前 一篇博文)。ModelViewEntity可以简单地理解为数据库的view,它是合成的产物。他相比ModelEntity更为复杂,并且定义了很多inner class。
ModelChild是很多数据库对象的抽象父类。它无法被实例化,抽象出它来的目的是,它含有一个parentModelEntity属性,该属性指向关联它的ModelEntity,而其他继承自它的数据库对象基本都需要该属性。
ModelRelation继承自ModelChild,它用于表示Model之间的关系(比如外键关系)。该实体映射到的entitymodel.xml中的如下配置节点:
ModelField给出了一个数据字段的抽象,它对应的配置形如:
We need an UOM for elevation (feet, meters, etc.)
To enter any related information
ModelReader:Model 定义读取器,从配置文件中读取model的定义
ModelFieldTypeReader: ModelFieldType 定义读取器,从配置文件中读取ModelFieldType的定义
ModelGroupReader: ModelGroup 定义读取器,从配置文件中读取ModelGroup的定义信息
DynamicViewEntity:定义动态视图实体
ModelEntityChecker: entity 定义检查器,它内部定义了很多预留字符串,在对entity 定义进行检查的时候,会对table name以及fieldname进行检查。
ModelUtil:它实现了从数据库命名到entity定义命名的相互转换规则上面介绍了对数据库相关对象的抽象,包括通用的数据库实体。但实际上在entityengine以及其他层对实体的访问并不是引用直接上面的ModelEntity对象。entityengine又对ModelEntity进行了包装,构建了GenericEntity对象。
类继承关系图:
GenericEntity是一个复杂对象,它包含了好多功能:
(1)将field类型从Object强转为其被定义好的特定的类型(通过一系列的get访问器)
(2)为了表明一个实体是包含了若干个field的集合,它实现了Map
GenericValue可以看做是对GenericEntity的扩展,用于持久化任何数据库entity。
它通过组合前面提到的Delegator,来“赋予”对象的CRUD功能(从这里也可以看出ofbiz的entity engine并不是传统意义上的ORM模型,这里的实体对象也不是贫血对象,而是被赋予了行为的充血对象),另外它还定义了很多方法用于获取跟当前实体相关的方法: