Apache OFbiz entity engine源码解读

简介

最近一直在看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到底是如何做到这些的。

源码解读

SQL的执行——SQLProcessor

SQLProcessor 负责entityengine中所有SQL的最终执行,包括了事务的提交、回滚等。从该类的实现中你能看出,ofbiz最终跟数据库打交道的还是JDBC,而因为JDBC本身就是对数据库访问的抽象,所以使用JDBC操作数据库对于任何RDBMS都是适用的,你只需要提供最终使用的数据库的jdbc-driver。但数据库的某些特性,sql,数据类型,这些并不是完全一致的标准,因此entityengine提供的语义层有效得屏蔽了SQL与字段类型等在各个数据库上的差异,使得最终用户无需直接这些差异打交道,一切都有它来处理。

提供了对实体最基本的CRUD功能——GenericDAO

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;
    }

如果你随意浏览一个方法的实现,在其内部都会实例化一个SQLProcessor来执行拼接而成的SQL(当然这里只有简单的SQL是直接拼接的,复杂一点的,带条件的SQL都是采用各种方式生成的,比如EntityCondition)。

提供更多面向业务层的数据访问方法的抽象——GenericHelper接口

GenericHelper接口提供了更偏向业务的数据访问抽象,这使得你可以不用去关注数据源是什么,基于数据源的实现交给该接口的实现类即可。事实上,GenericHelper也确实有一个完全基于内存的实现(MemoryHelper),只是除了用于测试,好像没有正式在线上场景中使用,不过这是一个不错的想法——随着nosql的流行,一部分关系不是很强或者不是很重要的数据可以基于一些memorydb来实现。来看看该接口极其实现类的关系图:


它有两个实现者,其中一个实现便是GenericHelperDAO,毫无疑问它是基于RDBMS的实现,因为它内部都是通过GenericDAO的实例来完成数据访问的。另一个之前提到过了,是基于内存的实现。

梳理一下上面的几个部件:GenericDAO实现了基本的数据访问操作的(CRUD)。这其中,每个方法都需要具体去跟数据库通信,通过什么?通过SQLProcessor,SQLProcessor负责每次SQL的执行。通常一个系统里对于数据的访问/操作并不仅仅只是简单的CRUD,其中还包含有更复杂的操作(比如关系查询、条件删除等),因此OFbiz为这些相对复杂的操作抽象出了一个接口GenericHelper。GenericHelperDAO是对GenericHelper的实现,其内部借助于GenericDAO,来实现这些相对复杂的数据访问。到最后我们会看到entity engine内最关键的一个部件——Delegator,其内部就是基于GenericHelper来实现数据访问的。

数据库连接信息实体抽象——GenericHelperInfo

GenericHelperInfo提供了对数据库建立连接所需信息的封装(包括用户名、密码等)

数据库连接的创建工厂——ConnectionFactory

该类提供了对JDBC连接的集中创建,提供了很多静态方法,根据给定的不同参数,来创建JDBC连接:


同时也提供了加载/卸载jdbc-driver以及关闭连接的动作。

GenericHelper的创建工厂——GenericHelperFactory

同上面的数据库连接工厂,此工厂类也对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中的如下配置节点:

<relation type="one" fk-name="BUDGET_BGTTYP" rel-entity-name="BudgetType">
        <key-map field-name="budgetTypeId"/>
      </relation>
      <relation type="one" fk-name="BUDGET_CTP" rel-entity-name="CustomTimePeriod">
        <key-map field-name="customTimePeriodId"/>
      </relation>
      <relation type="many" rel-entity-name="BudgetTypeAttr">
        <key-map field-name="budgetTypeId"/>
      </relation>

其中的<key-map />节点对应package中的ModelKeyMap实体。

ModelField给出了一个数据字段的抽象,它对应的配置形如:

<field name="geoPointId" type="id-ne"></field>
        <field name="dataSourceId" type="id"></field>
        <field name="latitude" type="floating-point" not-null="true"></field>
        <field name="longitude" type="floating-point" not-null="true"></field>
        <field name="elevation" type="floating-point"></field>
        <field name="elevationUomId" type="id"><description>We need an UOM for elevation (feet, meters, etc.)</description></field>
        <field name="information" type="comment"><description>To enter any related information</description></field>

ModelIndex 抽象出了数据库索引对象,它对象配置文件中的节点形如:

<index name="GLACCT_UNQCD" unique="true">
        <index-field name="accountCode"/>
      </index>

额外的一些辅助类:

ModelReader:Model 定义读取器,从配置文件中读取model的定义

ModelFieldTypeReader: ModelFieldType 定义读取器,从配置文件中读取ModelFieldType的定义

ModelGroupReader: ModelGroup 定义读取器,从配置文件中读取ModelGroup的定义信息

DynamicViewEntity:定义动态视图实体

ModelEntityChecker: entity 定义检查器,它内部定义了很多预留字符串,在对entity 定义进行检查的时候,会对table name以及fieldname进行检查。

ModelUtil:它实现了从数据库命名到entity定义命名的相互转换规则

上面介绍了对数据库相关对象的抽象,包括通用的数据库实体。但实际上在entityengine以及其他层对实体的访问并不是引用直接上面的ModelEntity对象。entityengine又对ModelEntity进行了包装,构建了GenericEntity对象。

作为TransferObject的数据实体模型——GenericEntity

类继承关系图:


GenericEntity是一个复杂对象,它包含了好多功能:

(1)将field类型从Object强转为其被定义好的特定的类型(通过一系列的get访问器)

(2)为了表明一个实体是包含了若干个field的集合,它实现了Map<String,Object>接口

(3)实现了Observable接口,这使得某个field被更新之后它能使得观察者得到通知以便数据可以被持久化到数据库

GenericValue可以看做是对GenericEntity的扩展,用于持久化任何数据库entity。

它通过组合前面提到的Delegator,来“赋予”对象的CRUD功能(从这里也可以看出ofbiz的entity engine并不是传统意义上的ORM模型,这里的实体对象也不是贫血对象,而是被赋予了行为的充血对象),另外它还定义了很多方法用于获取跟当前实体相关的方法:



GenericPK:继承自GenericEntity,表示一个主键对象。它没有太多override父类的行为,只是提供了几个静态方法,但需要提供跟构建GenericEntity类似的参数(因为它需要对GenericEntity进行初始化)。

备注:个人认为这里GenericPK继承GenericEntity有些不伦不类,因为它们从语义上应该理解为从属/包含关系。说白了,一个PK只是一个Entity的specialfield而已。(注意GenericEntity是实现了Map接口的,因为它包含所有的field集合),这里用GenericPK继承GenericEntity,也就间接实现了Map接口,而事实上它本身不能调用该接口的方法,因为它不具备一个field集合来装载所有的field,所以在它实例化的时候,它需要注入ModelEntity来实例化GenericEntity。

当然了,我这里只是从语义上来讲这种做法有些奇怪,而事实上从上下文可以看出这个继承链上的对象都是Transfer Object(原来的Value Object),这么实现应该是出于这个目的(目的是将对象的相关信息集中起来,避免在网络中频繁调用getter/setter方法,造成性能底下或者资源浪费。对于这一点可以参见《Core J2EE Patterns》)。

entity engine层的业务代表——Delegator

前面我们或多或少已经提及过Delegator了,现在是时候来更进一步得探讨它。

你可能感兴趣的:(apache,源码,ofbiz,entity-engine)