简介
最近一直在看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了,现在是时候来更进一步得探讨它。上图是跟Delegator相关的类图。
Delegator是entity层的业务代表,因此为了满足繁多的业务需求,它必然包含了非常多的数据访问接口,而事实上情况也正是如此。上面我们提到GenericValue自身已经包含了CRUD的行为,其实这只能算是一直“伪实现”。GenericValue中的create方法:
public GenericValue create() throws GenericEntityException {
return this.getDelegator().create(this);
}
GenericDelegator中的create方法的核心部分:
GenericHelper helper = getEntityHelper(value.getEntityName());
ecaRunner.evalRules(EntityEcaHandler.EV_RUN, EntityEcaHandler.OP_CREATE, value, false);
value.setDelegator(this);
this.encryptFields(value);
// if audit log on for any fields, save new value with no old value because it's a create
if (value != null && value.getModelEntity().getHasFieldWithAuditLog()) {
createEntityAuditLogAll(value, false, false);
}
value = helper.create(value);
从上面这段核心代码可以看出:
(1)GenericHelper是GenericDelegator真实的“数据操纵者”
(2)将GenericDelegator的当前实例注入到GenericValue中(因为这里该方法的调用并不只是由GenericValue触发,有可能是第三方调用该方法,所以待创建的GenericValue并不一定拥有对GenericValue的引用)
(3)GenericDelegator与GenericValue是相互依赖的
DelegatorFactory:创建Delegator的抽象工厂,在其内部维护了一个静态的ConcurrentHashMap来缓存已创建过的对象。SQL语句的条件——EntityCondition
我们之前讨论的都还只是一些简单的sql,而在业务系统里的sql并没有这么简单,他们的复杂有很大一部分都是因为sql拥有复杂的条件,而作为一个抽象层,entity engine也提供了对condition的抽象(主要的实现位于org.ofbiz.entity.condition package中)。EntityCondition是一个抽象类,定义了很多构建不同condition的静态方法:
在entityengine中将condition抽象为这么几种不同类型的condition:
(1)EntityConditionList:包含一组EntityCondition,以特殊的运算操作符来组合
(2)EntityExpr:用简单的表达式来组合形成EntityCondition
(3)EntityFieldMap:一个包含键值对的map(键等于值),以特殊的运算符组合
以上这些不同形式的condition又可以相互组合来形成更为复杂的conditionEntityConditionFunction:对sql语句条件中的函数进行抽象(注意只是条件中的函数)
EntityConditionFunction是一个抽象类,继承自EntityCondition,它只抽象了sql where 子句中的NOT函数EntityDateFilterCondition对时间段的筛选条件抽象,在之前我介绍ofbiz权限模块设计的时候曾经说过,在ofbiz的数据库设计的时候经常采用通过个记录标记时效性(起始生效时间、结束生效时间)来代替数据删除动作。其中有个关键的makeCondition方法:
public static EntityExpr makeCondition(Timestamp moment, String fromDateName, String thruDateName) {
return EntityCondition.makeCondition(
EntityCondition.makeCondition(
EntityCondition.makeCondition(thruDateName, EntityOperator.EQUALS, null),
EntityOperator.OR,
EntityCondition.makeCondition(thruDateName, EntityOperator.GREATER_THAN, moment)
),
EntityOperator.AND,
EntityCondition.makeCondition(
EntityCondition.makeCondition(fromDateName, EntityOperator.EQUALS, null),
EntityOperator.OR,
EntityCondition.makeCondition(fromDateName, EntityOperator.LESS_THAN_EQUAL_TO, moment)
)
);
}
翻译为伪代码条件就是:((thruDateName== null or thruDateName > moment)and (fromDateName== null or fromDateName <= moment))
EntityWhereString:where语句的封装器
注该类已经明确声明了应该最小化使用该类,因为在entityengine中手工书写where条件并不是非常安全的。
应该更多得以上面所述的三种condition来代替它。有了条件自然离不开运算/操作符——EntityOperator
类关系图:
这里将运算符区分为两大类:
(1)比较运算符:主要由EntityComparisonOperator,比较运算符有:=、>=、<= 等
(2)逻辑运算符:主要指and/ or,由EntityJoinOperator实现,注意该类名中的“join”其实不是指的是数据库表之间的join,这里join取的是连接的意思,用于说明and/or主要是连接不同条件的运算符。在EntityOperator内部有一个static的hashmap用于对所有operator进行注册,这里你就可以看到,它给出了对于同一个操作符的很多不同的表述方式,比如就拿notequal这个operator的语义来说,它在ofbiz中就可以以下面这些字符串来表示(其实还不止这么多,在register的方法内部还会对中间的短连接线作处理,替换为下划线再注册一次):
static { register("not-equal", NOT_EQUAL); }
static { register("not-equals", NOT_EQUAL); }
static { register("notEqual", NOT_EQUAL); }
static { register("!=", NOT_EQUAL); }
static { register("<>", NOT_EQUAL); }
上面这些描述同一个操作符的字符串,不仅仅在entity层起作用,在其他层同样有效!比如在service层就可以在simpleservive中通过xml的方式配置service,里面还是会牵扯到一些比较操作的描述,你也一样可以用上面这些注册过的表述方式(因为最终所有的比较几乎还是要转化为sql语义)。
封装了条件的值以及对值相关的操作——EntityConditionValue
类关系图:从上面的类图可以看出,有三个类继承自EntityConditionValue:
(1)EntityFunction:封装了一些用于对字段进行处理的函数(length,trim,upper等)
(2)EntityConditionSubSelect:封装了子查询(子查询对外部查询而言就是一个值的概念)
(3)EntityFieldValue:封装了条件值表达式EntityConditionValue是抽象类,里面定义了一些供子类实现的抽象方法。其中几个关键的抽象方法为:
public abstract Object getValue(Delegator delegator, Map<String, ? extends Object> map);
该方法主要被EntityFunction实现,用于处理value的值。
public abstract void addSqlValue(StringBuilder sql, Map<String, String> tableAliases, ModelEntity modelEntity, List<EntityConditionParam> entityConditionParams,
boolean includeTableNamePrefix, DatasourceInfo datasourceinfo);
该方法主要通过传入的参数sql,构建出部分sql语句的值
我们首先来看一下EntityFunction。它自身是一个抽象类,无法被实例化。但内部定义了一些静态类继承自EntityFunction,同时定义了一个内部接口用于获取value值。该接口被各个内部类按照自己的语义实现(比如upper,lower等)
public static class LOWER extends EntityFunction<String> {
public static Fetcher<String> FETCHER = new Fetcher<String>() {
public String getValue(Object value) { return value.toString().toLowerCase(); }
};
protected static final SQLFunctionFactory<String, LOWER> lowerFactory = new SQLFunctionFactory<String, LOWER>() {
@Override
protected LOWER create() {
return new LOWER();
}
@Override
protected void init(LOWER function, Object value) {
function.init(value);
}
};
protected LOWER() {}
public void init(Object value) {
super.init(FETCHER, SQLFunction.LOWER, value);
}
}
请注意最后部分的init方法,在这些内部类在被实例化的同时,他们会调用父类(EntityFunction)来将一些初始化变量传递给父类以完成反向注入。
排序——OrderByItem
entity engine中的排序主要牵扯到两个类(位于org.ofbiz.entity.conditionpackage中):
(1)OrderByItem:这是对单一排序条件进行的封装
(2)OrderByList:这是对一组排序条件进行的封装
我们首先来看一下OrderByItem的实现,它是表示排序条件的主要对象,而OrderByList更像是承载OrderByItem的一个容器。该类中有几个重要的方法:public static final OrderByItem parse(String text);
public void makeOrderByString(StringBuilder sb, ModelEntity modelEntity, boolean includeTablenamePrefix, DatasourceInfo datasourceInfo);
先来看第一个方法,parse用于将orderby的字符串描述(按照一定的规则产生),解析为一个OrderByItem对象。从中解析出这样几个属性:descending、nullsFirst、value(EntityConditionValue的实例)然后利用它们实例化OrderByItem。
第二个方法:makeOrderByString,用于产生最终的orderby子句:
public void makeOrderByString(StringBuilder sb, ModelEntity modelEntity, boolean includeTablenamePrefix, DatasourceInfo datasourceInfo) {
if ((nullsFirst != null) && (!datasourceInfo.useOrderByNulls)) {
sb.append("CASE WHEN ");
getValue().addSqlValue(sb, modelEntity, null, includeTablenamePrefix, datasourceInfo);
sb.append(" IS NULL THEN ");
sb.append(nullsFirst ? "0" : "1");
sb.append(" ELSE ");
sb.append(nullsFirst ? "1" : "0");
sb.append(" END, ");
}
getValue().addSqlValue(sb, modelEntity, null, includeTablenamePrefix, datasourceInfo);
sb.append(descending ? " DESC" : " ASC");
if ((nullsFirst != null) && (datasourceInfo.useOrderByNulls)) {
sb.append(nullsFirst ? " NULLS FIRST" : " NULLS LAST");
}
}
这里涉及到datasourceInfo中的一个配置项:useOrderByNulls(是否基于null值排序)如果没有,则必须先构建case语句对null值做相应的转换。
至于OrderByList这里我们就不多关注了,只是对每个OrderByItem做拼接而已,没有特别的地方。实体缓存——Cache
entity engine除了对数据库对象、sql的抽象外,为了提升性能,也构建有缓存模块(这里缓存的存储介质是服务器内存,另外也比较简单而且并不是非常成熟),下图为entityengine缓存类关系图:
从图中可以看出entityengine主要对两大类对象进行了缓存:
(1)Entity:由EntityCache负责实现,主要是基于GenericPK-GenericValue对
(2)EntityCondition:由AbstractEntityConditionCache实现,但它自身是个抽象类。AbstractEntityConditionCache又被两个类实现:
(1)EntityObjectCache:它基于String-Object的模式来缓存跟EntityConditon相关的数据
(2)EntityListCache:它基于Object-List<GenericValue>的模式来缓存
以上这两个类,主要的使用场景位于org.ofbiz.entity.cache下的一个Cache类中。它类似于该package对外的业务代理。对于上面三种缓存结构的操作都由它来代理。而Cache类在entityengine中唯一的使用是在Delegator中。它定义了一个接口,用于获取该Cache类的实例。对于所有配置信息结构的抽象——NameInfo
entity engine工作本身是依赖于配置描述的(配置文件位于/projectdir/framework/entity/config/entityengine.xml),以下是对该配置文件中配置项信息的实体定义:
(1)数据库相关信息的配置(DatasourceInfo)
(2)业务代理信息的配置描述(DelegatorInfo)
(3)Entity数据读取器信息的描述(EntityDataReaderInfo)
(4)EntityEca读取器信息的配置(EntityEcaReaderInfo)
(5)EntityGroup读取器信息的配置(EntityGroupReaderInfo)
(6)EntityModel读取器信息的配置(EntityModelReaderInfo)
(7)字段类型信息描述(FieldTypeInfo)
(8)资源以及资源加载器信息的描述(ResourceInfo/ResourceLoaderInfo)类继承关系如下图:
同一package下有一个EntityConfigUtil作为该package的帮助类(或者也算是业务代表)来代理这些基础数据对象的获取操作。
配置化&自定义查找——Finder
entity engine作为ofbiz基础框架的一部分,给其他层提供所有的数据查找,这些查找可能是通过编写代码调用,也可能是通过配置调用,这里就不得不提一下ofbiz的一种类xml语言(称之为mini-lang)构建的service(称之为simple-service)。它通过一系列的配置来完成对一个service的定义。这种方式优缺点都有,但ofbiz提供了这种方式,也提供了在配置中指定对数据的查找条件,它就有必要提供对这些查找的底层支持。所以,Finder就是用来支持形如下面这些语义的:
<if-compare field="parameters.useCache" operator="equals" value="true" type="Boolean">
<!-- if caching was requested, don't use an iterator -->
<find-by-and entity-name="InventoryItem" map="lookupFieldMap" list="inventoryItems" use-cache="true"/>
<else>
<find-by-and entity-name="InventoryItem" map="lookupFieldMap" list="inventoryItems" use-iterator="true" use-cache="false"/>
</else>
</if-compare>
Finder的继承关系结构如下:
主要支持两大类模式的查找:
(1)基于主键的查找:由PrimaryKeyFinder实现
(2)集合查找,这里又细分为两种模式一种是直接基于某个条件,另一种是基于and条件。
最顶层的是Finder抽象类,它提供了一个关键的抽象方法:
public abstract void runFind(Map<String, Object> context, Delegator delegator) throws GeneralException;
所有继承它的非抽象类都需要实现该方法。
在finderpackage中还有一个值得关注的类:EntityFinderUtil,它内部定义了很多对于simple-service中的查询要求的处理对象。上个关系图:
其中的两个主要接口:
(1)OutputHandler:主要用于定义处理如何输出数据/输出多少数据
(2)Condition:主要用于定义怎样创建一个EntityCondition
从广义的角度来看,一个用于指定输出数据的形式,另一个指定如何筛选数据。
而这些数量繁多的类,大都可以归类为以上两个功能,它们每个类内部负责解析跟自己职责相关的xml节点,然后实现接口定义的契约方法。entity engine对于游标的支持——AbstractCursorHandler
作为一个完整的entityengine,它也对游标进行了支持。这里跟游标相关的主要有三个数据库对象:连接、语句、结果集。看看他们的类图关系:
其中最顶层的父类为AbstractCursorHandler,它实现了InvocationHandler(并没有给出方法的直接实现,而是由子类进行实现),该接口是java反射相关的接口(通常配合Proxy来实现动态代理),而此处本意也是采用动态代理模式,但最终代理对象并没有真正用到。每个继承自AbstractCursorHandler都各自实现了InvocationHandler接口定义的invoke方法,在内部作相应的处理,最终调用父类的invoke方法。
JDBC数据类型处理器——JdbcValuehandler
在之前我们有提到过,其实所谓的SQL是一个标准与各自为政的混合体(这在web里非常常见比如css、浏览器等),而且jdbc类型(java这边)与数据库类型是完全不同的。所以这里有必要将他们直接建立映射关系,比如哪种jdbc类型,存储到数据库时需要对应到哪个类型。哪种数据库类型的数据在从数据库取出来之后需要被转换为什么java类型。这就是JdbcValueHandler的职责:它负责构建这些类型的映射关系,同时定义出他们之间双向的转换关系,这样对所有数据库都会兼容(当然这也要求这边的数据库类型是所支持的所有数据库的并集)。
那么它是如何构建双向转换关系的呢?很简单,每一个java类型都对应有一个该类型的handler比如:DoubleJdbcValueHandler、FloatJdbcValueHandler等。它们都支持双向操作方法(set到数据库,从数据库get出来):
GenericDAO的辅助工具类——SqlJdbcUtil
之前介绍过GenericDAO用于执行对数据库的一些简单的CRUD工作,那些操作里对sql语句的合成就是依赖于该辅助类。它定义了一系列util方法来辅助实现sql子句的生成,类型的转换,sql字段的设置等。既然这里有子句的生成,那么这里也部分依赖于之前的EntityCondition产生的where子句。
数据库维护工具类——DatabaseUtil
该类提供了对数据库信息的维护,包括对数据库进行检查、修复、建表等等
事务的创建工厂——TransactionFactory
当然,entity engine也提供了对事务的支持。TransactionFactoryInterface接口提供了对JTA管理的抽象方法。
该接口被两个类实现:
JNDIFactory:通过JNDI查找创建事务管理器
DumbFactory:并不真实工作的事务管理器
这两个工场会在TransactionFactory中根据配置创建。事务管理的帮助类——TransactionUtil
跟上面类似,这里也提供了一个帮助类来辅助管理事务。
自增序列生成器——SequenceUtil
由于各个数据库生成sequence的机制不同,因此entityengine并没有依赖任何一个数据库的实现,而是选择通过程序在内存中实现。它的实现算法并不复杂,通过在内存中维持SequenceBank来获取新的组件,SequenceBank包含了新值的生成、容器的扩充、冲突检测等,更多解析请看我之前的gist:
https://gist.github.com/yanghua/10346766
获取系统配置帮助类——EntityUtilProperties
entity engine所有的配置信息都位于ofbiz数据库的名为SYSTEM_PROPERTY表中,该类用于辅助获取配置信息,并提供了一些获取特殊类型信息的帮助方法
org.ofbiz.entity.util package内还提供了一些其他的帮助类这里就不再过多提及了。