OFBiz之Entity简介:
一、Entity Engine屏蔽了数据库访问的很多细节,通过XML配置文件定义描述实体,实体引擎自动维护实体至数据库的所有细节
二、支持主流数据库,包括Oracle、MySql、MS SQL、PostgreSql等
三、OFBiz实体引擎与其他的ORM框架最大的不同,是OFBiz只有一个GenericValue对象,不像其它ORM框架,要定义各种不同类型的,防止产生过多的实体对象,避免产生类爆炸。
四、OFBiz的GenericValue对象不同于其它ORM框架定义的实体对象,它没有getter和setter方法,全部通过put(key,value)的形式来设置属性的值,保持最大的灵活性。
1.什么是Entities(实体)?
Entities(实体)是MVC框架中一个模型的组基本单元,简单来说,一个entity是一个单独的数据库表,这个表包含了一个现实世界中的实体信息,例如一个人。一个Person表包含描述一个人的相关字段。
2.实体引擎概念(Entity Engine Concepts)
2.1数据源(datasource)
一个数据源通过作为默认的RDBMS(关系型数据库管理系统),在一个数据库实例中可以有多种数据库模式,每一种数据库模式具有不同的实体
数据源通常被定义和配置在(projectName\framework\config\entityengine.xml),OFBiz默认的数据源为localderby,我们在这里使用PostgreSql数据库来说明,代码如下:
在<inline-jdbc>元素中,jdbc-uri去创建另一个数据库实例,简单来说,就是在PostgreSql数据库中创建一个名为ofbiz的数据库文件夹
<datasource name="localpostnew" helper-class="org.ofbiz.entity.datasource.GenericHelperDAO" schema-name="public" field-type-name="postnew" check-on-start="true" add-missing-on-start="true" use-fk-initially-deferred="false" alias-view-columns="false" join-style="ansi" use-binary-type-for-blob="true" use-order-by-nulls="true"> <read-data reader-name="tenant"/> <read-data reader-name="seed"/> <read-data reader-name="seed-initial"/> <read-data reader-name="demo"/> <read-data reader-name="ext"/> <inline-jdbc jdbc-driver="org.postgresql.Driver" jdbc-uri="jdbc:postgresql://127.0.0.1:5432/ofbiz" jdbc-username="ofbiz" jdbc-password="ofbiz" isolation-level="ReadCommitted" pool-minsize="2" pool-maxsize="250" time-between-eviction-runs-millis="600000"/> </datasource>
2.2 实体代理(Entity Delegators)
OFBiz通过实体代理来连接资料库,一个实体代理主要目的是提供CRUD方法去操作资料库。实体代理不能执行这些CRUD任务,因此实体代理指定哪一个数据源(datasource)去访问实体组的哪一个实体,然后执行任务。实体代理被定义在(projectName\framework\config\entityengine.xml)文件中,一个代理定义在<delegator>元素中,OFBiz默认localderby。
2.3 定义一个实体Entity
实体定义在(projectName\applications\%module%\entitydef\entitymodel.xml)中,例如:
<?xml version="1.0" encoding="UTF-8"?> <entitymodel xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ofbiz.apache.org/dtds/entitymodel.xsd"> <title>Entity of an Open For Business Project Component</title> <description>None</description> <version>1.0</version> <entity entity-name="Budget" package-name="org.ofbiz.accounting.budget" title="Budget Entity"> <field name="budgetId" type="id-ne"></field> <field name="budgetTypeId" type="id"></field> <field name="customTimePeriodId" type="id"></field> <field name="comments" type="comment"></field> <prim-key field="budgetId"/> <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> </entity> </entitymodel>
<entity>元素中的属性名entity-name必须是唯一的,因为实体引擎通过entity元素的属性名来鉴别entity;package-name属性充当分类工具,允许OFBiz在不同的包中处理相应的实体。
一个<entity>元素可以包含<field>、<prim-key>、<relation>、<index>等4个元素,一个实体至少包含一个<field>元素,一个<field>元素必须有 name和type属性,通过<prim-key>来引用实体,一个实体可以有多个<prim-key>,当有多个主键时,要引用实体,就需要更详细的信息,如果不定义主键的话,数据库不会创建表。
<relation>元素,定义当前实体与其他实体之间的关系,一般用做创建外键和根据关系查询使用
rel-entity-name:被关联实体名称
fk-name:如果创建外键,那么定义外键的名称
title:给当前关系起个别名
field-name:当前实体的字段,指明当前实体的哪个字段与被关系实体有关系
rel-field-name:被关系的实体的字段名称,指明field-name和被关系实体的哪个字段有关系,如果rel-field-name与field-name相同,那么rel-field-name可以不定义。
type=”one-nofk“:关联类型,主要有三类"one"、"one-nofk"、"many",one、one-nofk的使用条件是倍关系实体的rel-field-name为主键,而many的使用条件是被关系实体的rel-field-name为非主键,one与one-nofk的区别在于one会在数据库表结构中创建外键约束,而one-nofk不会Relation除了用来创建外键约束之外还被用来做关系查询,当访问关系的时候可以用.getRelated("")和.getRelatedOne(""),用title+entityName作为参数,当实体一个"many"关系的时候使用getRelated返回一个列表,当实体一个"one"关系的时候使用getRelatedOne返回一个实体对象
<index>元素,除了作为提升数据检索速度的性能工具,indexes也允许我们执行特定的约束,indexes可以被定义在一个entity中的任意字段
2.4 实体组
实体组是一组实体被组织在一个组名下,组名作为一个把手为实体代理决定指定数据源查询指定的实体。实体组定义在(projectName\applications\%module%\entitydef\entitygroup.xml)
3.实体定义文件
实体定义文件一般存放在对应模块entitydef文件夹下面,以party为例,路径(%ofbiz-home%\applications\party\entitydef\entitymodel.xml),通过对应模块的ofbiz-component.xml进行加载.
<entity-resource type="model" reader-name="main" loader="main" location="entitydef/entitymodel.xml"/> <entity-resource type="model" reader-name="main" loader="main" location="entitydef/entitymodel_old.xml"/>
4.实体类型
普通实体(Entity):实体引擎中的模型基本单元,一个实体唯一对应数据库中的一张表
视图实体(View-Entity):简单的说就是由不同普通实体通过关联得到虚拟的实体。和数据库视图概念类似
拓展实体(Extend-Entity):可以继承和拓展已存在的普通实体
动态实体(DynamicViewEntity):在程序中手动创建一个实体,一般用于查询
4.1 普通实体
<entity entity-name="TenantDataSource" package-name="org.ofbiz.entity.tenant"> <description> There should be one record for each tenant and each group-map for the active delegator. The jdbc fields will override the datasource -> inline-jdbc values for the per-tenant delegator. </description> <field name="tenantId" type="id-ne"/> <field name="entityGroupName" type="name"/> <field name="jdbcUri" type="long-varchar"/> <field name="jdbcUsername" type="long-varchar"/> <field name="jdbcPassword" type="long-varchar"></field> <prim-key field="tenantId"/> <prim-key field="entityGroupName"/> <relation type="one" fk-name="TNTDTSRC_TNT" rel-entity-name="Tenant"> <key-map field-name="tenantId"/> </relation> </entity>
普通实体和数据库中的表是一一对应,程序会根据实体定义的数据库中创建表、索引,外键约束等。
4.2 视图实体
一个视图实体由四部分组成:成员实体、字段、视图衔接(将成员实体连接在一起的关系)、关系(实体与实体之间的关系);
视图实体一般用于多表连接复杂查询,视图实体不会在数据库中反应出来。
<view-entity entity-name="WorkEffortAssocView" package-name="org.ofbiz.workeffort.workeffort" title="Work Effort Association Entity with Name"> <member-entity entity-alias="WA" entity-name="WorkEffortAssoc"/> <member-entity entity-alias="WETO" entity-name="WorkEffort"/> <alias-all entity-alias="WA"/> <alias entity-alias="WETO" name="workEffortToName" field="workEffortName"/> <alias entity-alias="WETO" name="workEffortToSetup" field="estimatedSetupMillis"/> <alias entity-alias="WETO" name="workEffortToRun" field="estimatedMilliSeconds"/> <alias entity-alias="WETO" name="workEffortToParentId" field="workEffortParentId"/> <alias entity-alias="WETO" name="workEffortToCurrentStatusId" field="currentStatusId"/> <alias entity-alias="WETO" name="workEffortToWorkEffortPurposeTypeId" field="workEffortPurposeTypeId"/> <alias entity-alias="WETO" name="workEffortToEstimatedStartDate" field="estimatedStartDate"/> <alias entity-alias="WETO" name="workEffortToEstimatedCompletionDate" field="estimatedCompletionDate"/> <alias entity-alias="WETO" name="workEffortToActualStartDate" field="actualStartDate"/> <alias entity-alias="WETO" name="workEffortToActualCompletionDate" field="actualCompletionDate"/> <view-link entity-alias="WA" rel-entity-alias="WETO"> <key-map field-name="workEffortIdTo" rel-field-name="workEffortId"/> </view-link> <relation type="one-nofk" fk-name="WK_EFFRTASSV_FWE" title="From" rel-entity-name="WorkEffort"> <key-map field-name="workEffortIdFrom" rel-field-name="workEffortId"/> </relation> </view-entity>
<member-entity>元素,通常一个视图实体有两个以上成员实体成员,每一个实体在视图实体被定义在<member-entity>元素中,如
<member-entity entity-alias="HotelGuest" entity-name="PersonL"/>
每一个实体成员可以被视图实体多次引入,每次引入通过entity-alias属性鉴别,如
<member-entity entity-alias="HotelGuest" entity-name="PersonL"/> <member-entity entity-alias="HotelOwner" entity-name="PersonL"/>
视图是描述实体成员的层级,例如,一位客人住在一个宾馆,这个宾馆拥有者掌管,如下
<member-entity entity-alias="HotelGuest" entity-name="PersonL"/> <member-entity entity-alias="Hotel" entity-name="HotelL"/> <member-entity entity-alias="HotelOwner" entity-name="PersonL"/>
定义一个视图的关键字用<alias>元素,通过entity-alias属性处理实体别名,在entitydef文件夹下,编辑entitymodel.xml,添加PersonL实体和Hotel实体:
<entity entity-name="PersonL" package-name="org.ofbiz.learning"> <field name="personId" type="id-ne"></field> <field name="firstName" type="name"></field> <field name="lastName" type="name"></field> <field name="housedAt" type="id"></field> <field name="someExtraDetails" type="description"></field> <prim-key field="personId"/> <relation type="one-nofk" fk-name="PERSON_HOTEL" rel-entity-name="HotelL"> <key-map field-name="housedAt" rel-field-name="hotelId"/> </relation> </entity> <entity entity-name="HotelL" package-name="org.ofbiz.learning"> <field name="hotelId" type="id-ne"></field> <field name="hotelName" type="name"></field> <field name="ownedBy" type="id"></field> <field name="postalAddressId" type="id"></field> <prim-key field="hotelId"/> <relation type="one-nofk" fk-name="HOTEL_OWNER" rel-entity-name="PersonL"> <key-map field-name="ownedBy" rel-field-name="personId"/> </relation> </entity>
假如我们想要用一个视图实体包关键字:guest name,hotel name,hotel owner,在entitymodel.xml中添加一个视图实体GuestHotelOwnerViewL:
<view-entity entity-name="GuestHotelOwnerViewL" package-name="org.ofbiz.learning"> <member-entity entity-alias="HotelGuest" entity-name="PersonL"/> <member-entity entity-alias="Hotel" entity-name="HotelL"/> <member-entity entity-alias="HotelOwner" entity-name="PersonL"/> <alias entity-alias="HotelGuest" name="guestFirstName" field="firstName"/> <alias entity-alias="HotelGuest" name="guestLastName" field="lastName"/> <alias entity-alias="Hotel" name="hotelName" field="hotelName"/> <alias entity-alias="HotelOwner" name="ownerFirstName" field="firstName"/> <alias entity-alias="HotelOwner" name="ownerLastName" field="lastName"/> <view-link entity-alias="HotelGuest" rel-entity-alias="Hotel"> <key-map field-name="housedAt" rel-field-name="hotelId"/> </view-link> <view-link entity-alias="Hotel" rel-entity-alias="HotelOwner"> <key-map field-name="ownedBy" rel-field-name="personId"/> </view-link> </view-entity>
<alias-all>元素包括所有自定实体成员中的元素,它可以剔除一些元素,也可以给字段名增添前缀,生成有意义的alias别名,我们用<alias-all>元素构建上面例子的视图实体。
<view-entity entity-name="GuestHotelOwnerViewAllL" package-name="org.ofbiz.learning"> <member-entity entity-alias="HotelGuest" entity-name="PersonL"/> <member-entity entity-alias="Hotel" entity-name="HotelL"/> <member-entity entity-alias="HotelOwner" entity-name="PersonL"/> <alias-all entity-alias="HotelGuest" prefix="guest"> <exclude field="id"/> <exclude field="housedAt"/> <exclude field="someExtraDetails"/> </alias-all> <alias-all entity-alias="Hotel" prefix="hotel"> <exclude field="hotelId"/> <exclude field="ownedBy"/> </alias-all> <alias-all entity-alias="HotelOwner" prefix="owner"> <exclude field="personId"/> <exclude field="housedAt"/> <exclude field="someExtraDetails"/> </alias-all> <view-link entity-alias="HotelGuest" rel-entity-alias="Hotel"> <key-map field-name="housedAt" rel-field-name="hotelId"/> </view-link> <view-link entity-alias="Hotel" rel-entity-alias="HotelOwner"> <key-map field-name="ownedBy" rel-field-name="personId"/> </view-link> </view-entity>
内部链接和外部链接,<view-link>有rel-optional属性去指定被加入这些实体之间的关系是否可选择,如果是可选的(内连接),那么entity-alias指定的成员实体的所有记录将被查询返回到视图实体,如果不是可选的(外连接),将返回被rel-entity-alias指定的成员实体的相关记录。
视图实体中relation只能用来做关系查询
而view-link用来做join关联查询。在entityengine.xml中<datasource..>元素当中的join-style属性当中设置你的数据库join方法
4.2.1 在字段上应用函数
视图实体字段可以包含函数结果,不仅仅是成员实体字段值,函数:count、count-distinct、min、max、sum、avg、upper、lower
计算记录的数量:
<alias entity-alias="PA" name="contactMechId" function="count"/>
计算不同记录的数量:
<alias entity-alias="PA" name="city" function="count-distinct"/>
大写和小写的功能:
<alias entity-alias="PA" name="upperToName" field="toName" function="upper"/> <alias entity-alias="PA" name="lowerAddress1" field="address1" function="lower"/>
4.2.2 Grouping for Summary Views
当我们想通过一个关键字把一组数据分组的时候,我们在一个视图实体alias字段上添加group-by属性,我们通过city数量来计算postal address的数量:
<view-entity entity-name="TestGrouping" package-name="org.ofbiz.learning"> <member-entity entity-alias="PA" entity-name="PostalAddress"/> <alias entity-alias="PA" name="count" field="contactMechId" function="count"/> <alias entity-alias="PA" name="city" group-by="true"/> </view-entity>
嵌入<complex-alias>元素,一个<complex-alias>元素可以包含另一个<complex-alias>元素
<member-entity entity-alias="EA" entity-name="SomeEntityAlias"/> <alias name="complexComputedField"> <complex-alias operator="*"> <complex-alias operator="+"> <complex-alias-field entity-alias="EA" field="a"/> <complex-alias-field entity-alias="EA" field="b"/> </complex-alias> <complex-alias operator="/"> <complex-alias operator="-"> <complex-alias-field entity-alias="EA" field="c"/> <complex-alias-field entity-alias="EA" field="d"/> </complex-alias> <complex-alias-field entity-alias="EA" field="e"/> </complex-alias> </complex-alias> </alias>
4.3 扩展实体
集成已存在的实体并对其进行扩展
<extend-entity entity-name="UserLogin"> <field name="partyId" type="id"></field> <relation type="one" fk-name="USER_PARTY" rel-entity-name="Party"> <key-map field-name="partyId"/> </relation> <relation type="one-nofk" rel-entity-name="Person"> <key-map field-name="partyId"/> </relation> <relation type="one-nofk" rel-entity-name="PartyGroup"> <key-map field-name="partyId"/> </relation> </extend-entity>
4.4 动态实体
在程序中手动创建实体,对其进行查询
DynamicViewEntity salesUsageViewEntity = new DynamicViewEntity(); salesUsageViewEntity.addMemberEntity("OI", "OrderItem"); salesUsageViewEntity.addMemberEntity("OH", "OrderHeader"); salesUsageViewEntity.addMemberEntity("ItIss", "ItemIssuance"); salesUsageViewEntity.addMemberEntity("InvIt", "InventoryItem"); salesUsageViewEntity.addViewLink("OI", "OH", Boolean.valueOf(false), ModelKeyMap.makeKeyMapList("orderId")); salesUsageViewEntity.addViewLink("OI", "ItIss", Boolean.valueOf(false), ModelKeyMap.makeKeyMapList("orderId", "orderId", "orderItemSeqId", "orderItemSeqId")); salesUsageViewEntity.addViewLink("ItIss", "InvIt", Boolean.valueOf(false), ModelKeyMap.makeKeyMapList("inventoryItemId")); salesUsageViewEntity.addAlias("OI", "productId"); salesUsageViewEntity.addAlias("OH", "statusId"); salesUsageViewEntity.addAlias("OH", "orderTypeId"); salesUsageViewEntity.addAlias("OH", "orderDate"); salesUsageViewEntity.addAlias("ItIss", "inventoryItemId"); salesUsageViewEntity.addAlias("ItIss", "quantity"); salesUsageViewEntity.addAlias("InvIt", "facilityId"); EntityListIterator salesUsageIt = delegator.findListIteratorByCondition(salesUsageViewEntity, EntityCondition.makeCondition( UtilMisc.toList( EntityCondition.makeCondition("facilityId", EntityOperator.EQUALS, facilityId), EntityCondition.makeCondition("productId", EntityOperator.EQUALS, productId), EntityCondition.makeCondition("statusId", EntityOperator.IN, UtilMisc.toList("ORDER_COMPLETED", "ORDER_APPROVED", "ORDER_HELD")), EntityCondition.makeCondition("orderTypeId", EntityOperator.EQUALS, "SALES_ORDER"), EntityCondition.makeCondition("orderDate", EntityOperator.GREATER_THAN_EQUAL_TO, checkTime) ), EntityOperator.AND),null, null, null, null );
5 实体定义
5.1 命名规则
实体名称(entity-name)首字母大写,如果实体名称由多个关键字组成,那么关键字首字母大写,如entity-name="TenanDataSource",ofbiz会在创建数据库表的时候根据entity-name实体名称首字母之外的大写字母前面加"_",所以entity-name="TenanDataSource"生成的数据库表名为"Tenant_Data_source",要控制entity-name实体名称不要超过25个字母。
Field表字段,命名规则与实体名称一样,唯一不同的是首字母小写
5.2 实体与数据库的关联
<entity-group group="org.ofbiz.olap" entity="SalesInvoiceItemFact"/> <entity-group group="org.ofbiz.olap" entity="SalesInvoiceItemStarSchema"/>
Entity-group一般被定义在模块\entitydef\entitygroupXXX.xml中,对实体进行分组,使不同的实体分属不同的entity-group。不是所有的entity都进行了entity-group分组,如果没有被分组,系统启动的时候会将实体默认归类到"org.ofbiz"中。
查看数据库定义文件%ofbiz_home%/framework/entity/config/entityengine.xml
<delegator name="default" entity-model-reader="main" entity-group-reader="main" entity-eca-reader="main" distributed-cache-clear-enabled="false"> <group-map group-name="org.ofbiz" datasource-name="localpostnew"/> <group-map group-name="org.ofbiz.olap" datasource-name="localpostolap"/> <group-map group-name="org.ofbiz.tenant" datasource-name="localposttenant"/> </delegator>
可以发现delegator将多个group-name组织到一起并将group-name与datasource对应起来,datasource-name是什么?
<datasource name="localdernew" helper-class="org.ofbiz.entity.datasource.GenericHelperDAO" schema-name="OFBIZ" field-type-name="postgres" check-on-start="true" add-missing-on-start="true" use-pk-constraint-names="false" use-indices-unique="false" alias-view-columns="false" use-order-by-nulls="true"> <read-data reader-name="seed"/> <read-data reader-name="seed-initial"/> <read-data reader-name="demo"/> <read-data reader-name="ext"/> <inline-jdbc jdbc-driver="org.apache.derby.jdbc.EmbeddedDriver" jdbc-uri="jdbc:derby:ofbiz;create=true" jdbc-username="ofbiz" jdbc-password="ofbiz" isolation-level="ReadCommitted" pool-minsize="2" pool-maxsize="250" time-between-eviction-runs-millis="600000"/> </datasource>
Datasource定义了数据库驱动,数据库用户名、密码等,所以datasource就是我们说的数据库。
PS:我们通过entity-group将各个实体和数据库之间关联起来,然后将一个或多个数据库归属到一个delegator中,我们又是怎么使用数据库进行数据库操作的呢?我们可以发现
<context-param> <param-name>entityDelegatorName</param-name> <param-value>default</param-value> <description>The Name of the Entity Delegator to use, defined in entityengine.xml</description> </context-param>
6.常用实体引擎API:GenericPK、GenericValue、GenericDelegator、EntityCondition
6.1 GenericPK
GenericPK一般用于生产实体的主键
6.2 GenericValue
GenericValue通用实体值对象,处理任何被定义实体的持久化
6.3 GenericDelegator
GeneticDelegator通用的数据库访问代理类,对数据库CRUD的实现都通过该类实现。
6.3.1 创建实体 以非持久化的GenericValue值对象的形式创建Entity
GenericValue party = delegator.makeValue("party");
//创建非主键
party.setNonPKFields(UtilMisc.toMap("partyTypeId","PERSON","description","测试Party","statusId","PARTY_ENABLED"));
//创建主键
String partyId = delegator.getNextSeqId("party");
party.setPKFields(UtilMisc.toMap("partyId",partyId)); //getNextSeqId("String entityName")生成主键序列号
//以GenericValue值对象的形式创建Entity实体,并持久化至数据库
GenericValue createParty = delegetor.create(party);
Assert.assertEquals(partyId,createParty.getPkShortValueString());
然后再启动测试用例
更简便的方式:
Map<String, String> partyMap = UtilMisc.toMap("partyTypeId","PERSON","description","测试Party","statusId","PARTY_ENABLED");
//makeValidValue(entityName, Map<String, ?extends Object>fields);验证每个字段的有效性,无效不设置
delegator.makeValidValue("Party", partyMap);
6.3.2 更新实体
createParty.put("partyId", "123");
int updated = delegator.store(createParty);//int updated 为更新的字段数量,store()方法只更新数据库已有的字段,storeAll是一个更新字段集合,如果数据库中没有更新的字段,会添加进去。
Assert.assertEquals(1,updated);
6.3.2 删除实体
int deleted = delegator.removeByAnd("Party", UtilMisc.toMap("partyId",partyId));
Assert.assertEquals(1,deleted);
6.4 EntityCondition
EntityCondition封装查询实体的条件,makeCondition()方法设置查询约束
6.5 实体缓存(cache)
实体缓存设置,在entity配置文件中添加属性never-cache="false/true"来设置是否添加实体缓存,ofbiz为了性能优化,系统默认开启实体缓存,never-cahce="true"我们才可能调用实体缓存相关API,相关API:
findByPrimaryKeyCache,
findByAndCache,
findByConditonCache,
findByAllCache,
OFBiz会自动检测缓存实体是否有更新,如果有更新会自己更新。在OFBiz的开发中,我们优先使用带有cache缓存的API
UtilCache API下是相关的缓存API,UtilCache.clearCache("")清理缓存,同样我们也在WEBTOOLS→>缓存维护中清理缓存