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→>緩存維護中清理緩存