标签:des cWeb cPage style blog http color io os
近期一直在看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提供了对数据库建立连接所需信息的封装(包括username、password等)
该类提供了对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中的例如以下配置节点:
<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>
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>
<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对象。
类继承关系图:
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》)。
上图是跟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来缓存已创建过的对象。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) ) ); }
EntityWhereString:where语句的封装器
注该类已经明白声明了应该最小化使用该类,由于在entityengine中手工书写where条件并非非常安全的。
应该很多其它得以上面所述的三种condition来取代它。类关系图:
这里将运算符区分为两大类:
(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); }
从上面的类图能够看出,有三个类继承自EntityConditionValue:
(1)EntityFunction:封装了一些用于对字段进行处理的函数(length,trim,upper等)
(2)EntityConditionSubSelect:封装了子查询(子查询对外部查询而言就是一个值的概念)
(3)EntityFieldValue:封装了条件值表达式EntityConditionValue是抽象类,里面定义了一些供子类实现的抽象方法。当中几个关键的抽象方法为:
public abstract Object getValue(Delegator delegator, Map<String, ? extends Object> map);
public abstract void addSqlValue(StringBuilder sql, Map<String, String> tableAliases, ModelEntity modelEntity, List<EntityConditionParam> entityConditionParams, boolean includeTableNamePrefix, DatasourceInfo datasourceinfo);
我们首先来看一下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); } }
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);
第二个方法: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做拼接而已,没有特别的地方。entity engine除了对数据库对象、sql的抽象外,为了提升性能,也构建有缓存模块(这里缓存的存储介质是server内存,另外也比較简单并且并非非常成熟),下图为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类的实例。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>
主要支持两大类模式的查找:
(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节点,然后实现接口定义的契约方法。作为一个完整的entityengine,它也对游标进行了支持。这里跟游标相关的主要有三个数据库对象:连接、语句、结果集。看看他们的类图关系:
当中最顶层的父类为AbstractCursorHandler,它实现了InvocationHandler(并没有给出方法的直接实现,而是由子类进行实现),该接口是java反射相关的接口(通常配合Proxy来实现动态代理),而此处本意也是採用动态代理模式,但终于代理对象并没有真正用到。每一个继承自AbstractCursorHandler都各自实现了InvocationHandler接口定义的invoke方法,在内部作相应的处理,终于调用父类的invoke方法。
在之前我们有提到过,事实上所谓的SQL是一个标准与各自为政的混合体(这在web里非经常见比方css、浏览器等),并且jdbc类型(java这边)与数据库类型是全然不同的。所以这里有必要将他们直接建立映射关系,比方哪种jdbc类型,存储到数据库时须要相应到哪个类型。哪种数据库类型的数据在从数据库取出来之后须要被转换为什么java类型。这就是JdbcValueHandler的职责:它负责构建这些类型的映射关系,同一时候定义出他们之间双向的转换关系,这样对全部数据库都会兼容(当然这也要求这边的数据库类型是所支持的全部数据库的并集)。
那么它是怎样构建双向转换关系的呢?非常easy,每一个java类型都相应有一个该类型的handler比方:DoubleJdbcValueHandler、FloatJdbcValueHandler等。它们都支持双向操作方法(set到数据库,从数据库get出来):
之前介绍过GenericDAO用于运行对数据库的一些简单的CRUD工作,那些操作里对sql语句的合成就是依赖于该辅助类。它定义了一系列util方法来辅助实现sql子句的生成,类型的转换,sql字段的设置等。既然这里有子句的生成,那么这里也部分依赖于之前的EntityCondition产生的where子句。
该类提供了对数据库信息的维护,包括对数据库进行检查、修复、建表等等
当然,entity engine也提供了对事务的支持。TransactionFactoryInterface接口提供了对JTA管理的抽象方法。
该接口被两个类实现:
JNDIFactory:通过JNDI查找创建事务管理器
DumbFactory:并不真实工作的事务管理器
这两个工场会在TransactionFactory中依据配置创建。跟上面相似,这里也提供了一个帮助类来辅助管理事务。
由于各个数据库生成sequence的机制不同,因此entityengine并没有依赖不论什么一个数据库的实现,而是选择通过程序在内存中实现。它的实现算法并不复杂,通过在内存中维持SequenceBank来获取新的组件,SequenceBank包括了新值的生成、容器的扩充、冲突检測等,很多其它解析请看我之前的gist:
https://gist.github.com/yanghua/10346766
entity engine全部的配置信息都位于ofbiz数据库的名为SYSTEM_PROPERTY表中,该类用于辅助获取配置信息,并提供了一些获取特殊类型信息的帮助方法
org.ofbiz.entity.util package内还提供了一些其它的帮助类这里就不再过多提及了。
Apache OFbiz entity engine源代码解读
标签:des cWeb cPage style blog http color io os
原文地址:http://www.cnblogs.com/yxwkf/p/3979965.html