2011年5月12日 星期四

SCEA Ch07(4) - Entity Beans

ejb3 , 號稱提供了全新一組充滿能量與驚奇的psersistence的技術來處理entity beans,他的名字叫做 Java Persistence,讓我來看看到底好在哪了!!

  • 首先 persistence Entities 並不是早期的 entity beans的加強版,反而倒是一個全新的設計概念。

  • 提供了一組標準的ORM技術,用以整合至大眾流行的 persistence framework ,像是Hibernate or JDO

  • 這並不是被綁進Java EE container裡面才可運作的東西,事實上也可在J2SE裡面進行處理。

  • 定義一個service provider 介面讓所有實作者依循,藉此可在不同的persistence providers之間切換而不需要改到entity codes。


要做到ORM,讓我想到的是以前自己手工刻,不然就是使用FiresotrmDAO ,而到後來有更多的工具來支援了,像是Hibernate or Oracle TopLink,這部分的做法都大同小異,基本上就是透過設定檔來做對應DDL的資訊。對於在持久化層,Sun的JDO定義了一組protable API,可以支援到任何的RDBMS or OODBMS。

Entities的概念是符合以下所列,但必須再次強調,Entities不適EJB

  • Is loaded from storage and has its field populated with the stored data.

  • Can be modified in-memory to change the values of data

  • Can be saved back, tus updating the database data


底下是一個Entity class的範例


[caption id="attachment_340" align="alignnone" width="539" caption="entity class"]entity class[/caption]

在這範例中有幾個需要特別注意的事項

  • entity class is a plain java class ,甚至不需要實作 serializable

  • entity class 對應到資料欄位定義,可透過DD or annotations

  • entity 必須要有pkey,透過@Id來判定,若是有複合鍵的議題,則可指定為Object,該object 必須有default constructor and serizable.

  • 可透過getter / setter 來存取資料內容

  • 也可以提供biz methods作為呼叫執行使用



接下來看看如何在EJB裏頭去存取 entity


[caption id="attachment_341" align="alignnone" width="545" caption="BankBean.java"]BankBean.java[/caption]

[caption id="attachment_342" align="alignnone" width="463" caption="BankBean.java"]BankBean.java[/caption]

當要新增一筆資料時,必須呼叫EntityManager.persist(),這時候entity會被排入schedule準備與資料庫同步,直到真的交易確立(transaction commit) ,當entity載入實際資料庫資料後,會持有一個狀態(state),我們稱之為managed state,直到 persistence context ends 或者被移除。若已經交易完成,則state會改為 detached state,此時已經與資料庫不同步了。

Persistence Context Type

操作上要特別注意,若是使用 extended persistence context,也就是透過stateful session bean而來操作的entity,即使是stateful bean已經 destroyed,但entity bean 仍然會是保持managed state,還是可以持續做一些資料面的操作,即使不在交易控管內也能進行,這部份得特別留意。




[caption id="attachment_343" align="alignnone" width="600" caption="context type"]context type[/caption]

下面這例子是 Exntended Persistence Context

[caption id="attachment_344" align="alignnone" width="600" caption="extended persistence Context - in stateful session bean"]extended persistence Context - in stateful session bean[/caption]

[caption id="attachment_345" align="alignnone" width="506" caption="extended persistence context"]extended persistence context[/caption]

在這裡要特別注意到的是,宣告EXTENDED persistence context type,這個動作是必要的,否則就會讓整個entity在整個session bean 運作過程中是沒有意義的,整個entity state都會是detached。


使用 extended persistence context type時,entity會一直保持著managed state,因為 persistence context 跨了很多個transaction使用,而只有在bean被移除時才會變為detached state,也就是因為這樣,在上面範例中的每個資料操作,都不需要再去執行entityManager.find(),不過系統會自己檢查該entity是否存在,若不存在時,則會拋出IllegalStateException。


那麼總結來說,我們到底該如何選擇使用哪一種context type ?


大致上可以這樣分,如果每次client都會需要取得一堆資料,且會針對資料內容做更新處理的話,那就選用 stateful session bean 處理entity,若僅僅只會需要查詢一次,而不會有更新,那可以選用 stateless sssion 來處理。


Packaging and Deploying entity classes
Entity classes是以一個persistence unit 的概念包裝成一個package的,一個PU基本上就包含了有 具有邏輯群組功能的entity classes, mapping metadata , db confg data  等。PU是被定義在一個特殊的DD檔裡面名為 persistence.xml,且存放的路徑必須是EJBFolder\META-INF\persistence.xml。

最基本的至少需在persistence.xml中宣告一組PU --> <persistence-unit name="xxx">,但若整個persistence.xml當中都沒有宣告任何一個PU的話,那麼container會預設的將persistence.xml所處的jar file 裡面所包含的classes都視為 persistence unit。(my opinion : 可以不要有這麼多預設嗎...這樣很容易出事lol)

以下是幾個重要的element tag in persistence.xml

<description>

<provider>fully qualified class name of the persistence provider's implementation of the SPI class javax.persistence.spi.PersistenceProvider。

<transaction-type>可以是"JTA" or "RESOURCE_LOCAL" , default is "JTA"

<jta-data-source> , <non-jta-data-source> , 這些都是往JNDI要資源

<mapping-file>作為orm 設定的資料,可寫在orm.xml 或者是透過annotation宣告,但檔案必須與persistence.xml放在同一個目錄底下。

<jar-file>,<class> 列舉出entity classes 所在

<exclude-unlisted-classes>

<properties> vendor-specific configurations

The EntityManager API
Cleint端程式通常在下樹兩種情況下會用上EntityManager

  • With a container-managed EntityManager,contaner runtime對於提供給app一個entitymanager是有其責任與義務在的,container會透過注入的方式去注入EntityManager,就在我們宣告了@PersistenceContext的欄位上,當然也可以透過SessionContext.lookup()方式來取得。

  • With a application-managed EntityManager,這就由ap本身自己要提供一個EntityManager,通常是可以透過EntityManagerFactory interface來取得。



EntityManager主要的三大功能



  • Entity life-cycle management

  • Database synchronization operations

  • Entity lookup and queries



Entity instance有兩件重要的責任- 呈現出entity實體與persistence context的 關係作為管理使用,同時也要讓entity instance保持與database同步,為了能有效管理這之間的關係,針對entity instance區分出了四大狀態



  • new - entity class剛初始化完成,此時尚未載入任何的entity與db同步,同時也沒有任何的persistence context榜定,若此時變更entity instance state,將會完全與db無關。

  • managed - entity instance以載入一筆具有pkey的 raw data,同時也榜定了一個persistence context在身上,通常這是因為已經呼叫了persist() 之後才會有這樣的狀態,自此之後若針對此entity instance作任何資料變更只要最後有正確commit,都會與db 同步,或者是透過呼叫flush()。

  • detached - entity instance 雖然載入一筆具有pkey的 raw data,但卻沒有榜定任何一個persistence context。

  • removed - entity instance 榜定有一個 persistence context,但實際的bean 內含卻是等待移除的 (shceduled for removal from the database)



底下參考狀態機



[caption id="attachment_347" align="alignnone" width="502" caption="entitylifecycle"]entitylifecycle[/caption]

如果真的要去刪除db資料時,就必須呼叫EntityManager.remove(),但這裡並不是指把entity instance給砍掉,而是排程至交易器當中準備砍實體資料,假設一旦正確commit or flush()呼叫,就會完整執行。remove()本身不會去在乎entity instance是否是新的或者是已經被一除了,但確定的是他只會針對Managed state的entity發揮效用,若我們對於一個已經是detached state 的entity instance呼叫remove(),則會遇到IllegalArgumentException。

merge()的用意是為了把detached狀態的entity instance再度啦回到managed state,而這樣的操作通常是會在stateless session bean中來操作,當先前取回的entity做了改變以後,想再寫回至db,那麼就可以呼叫 entityManager.merge( obj),而當我們真的呼叫了merge以後,回傳回的entity將會是一個全新的entity instance。

Life-cycle callbacks
這裡的callback api 並不是ejb callbacks,而是指persistence provider所發出的,要再次聲明 entities不是EJB XD

以下這幾個是callback apis

prePersist , postPersist, preRemove , postRemove , preUpdate , postUpdate , postLoad

而要針對這些callback api使用時,很簡單只要在想要運作的事件的method中,宣告@callback apis,即可在相應的事件中達成所要得目的。當然如果不想要靠自己注入annotation,也可透過listener設定方式,像是以下這樣:

@Entity
@EntityListeners( AccountListener.class)
public class Account{}

而在listener class - AccountListner中則必須包含有注入事件的methods

public class AccountListener{

@PrePersist
void prePersist( Account a ){
// biz logics..
}


}

Database Synchronization
雖然在前述的說明中得知,只有在commit或flush之後才會真正完成與資料庫同步,但有時候會需要在一連串的biz method操作過程中,看到一部分已經commit的效果,要達成這樣特殊的需求就是得靠 flush mode 來設定了!!!

The flush mode can be set on specific methods or fields using metadata annotations , or globally on the persistence context using the setFlushMode(). The available options are COMMIT for sync at commit time only , or AUTO for sync of state at both commit time and before query execution.

那麼flush()又是作啥的? 基本上這是用來呼叫通知 persistence context 去更新資料庫使用,但不代表更新完以後原本的entity instance就會被更新了,若要整個entity instance都更新的話,得正確的去在一次呼叫 refresh().

Database manipulation and lock
對於資料庫的存取,在現今的系統中已經都不是單一一個存取源的封閉架構,常常會伴隨有多種不同的存取源對資料作處理,這時候就會跟交易隔離有關了,對於concurrent access to Database的處理議題我們都是以 Transaction Isolation Level來作保護,在Entity bean 的保護上,最大的層級是SERIZABLE,但這會導致整體系統效能降低,而最低限度層級則是READ UNCOMMITED,但這通常意味著無法正確與資料庫內容同步。

Java persistence 定義了兩種特性,用以來做為同時存取資料時的調整設定:

  • Optimistic locking(所謂的樂觀鎖定) using a version attribute

  • Explicit read and write locks



optimistic locking 算是一個用辭不當的使用法,所謂的樂觀鎖定,其實就是資料沒有被lock住 (@@" ....那是在鎖啥XD),那也就是說對於app而言是都可以同步去存取更新同一筆資料,而這潛在的衝突危機會在commit事件發生時,才被notify出來,進而將衝突的部分進行rollback(雖然說是同步,但事實上總是會有先後進行commit,這時先行者commit成功,後繼者fail)。這樣的好處是提高系統運作效能,不因lock導致系統需等待運作結果才進行其他的服務,但相對的在不利的一面( on the downside)來看,app必須自己處理commit 衝突的部分。

在Java persistence的預設交易隔離層級是READ COMMITED (基本作法是, 讀取的交易不會阻止其它的交易,一個未確認的更新交易會阻止其它所有的交易,但這影響效能較大,另一個基本作法是交易正在更新,尚未確定前都先操作暫存表格。)

既然在樂觀鎖定的模式中,我們的app必須自己能夠意識到commit conflict,那又是如何可以得知的? --> by @Version
這個attribute不會在任何的IDE中自己加入,必須是開發者自己自行針對需要被檢驗的欄位加入此項宣告設定,



[caption id="attachment_349" align="alignnone" width="639" caption="@Version"]@Version[/caption]

只有加入了這樣的設定,persistence provider才能在每次要更新時,透過檢查@Version去判斷衝突的發生,要標記具有@Version屬性的欄位資料型態必須是 int , Integer , short , Short, long , Long, TimeStamp

好吧,看到這裡雖然我們使用了樂觀鎖定來做事情,把任何可能的更新的衝突推遲到commit要確認時再來處理,但這樣卻是無法避免 nonrepeatable or phantom read,所以假設這樣的處理作法還不夠符合需求的話,可以再考慮以下的方式:



  • 設置更高層級的全域交易隔離層級,但相對的也造成系統效能低落,以及保持鎖定所需要的開銷。

  • 設置一個application-level的更高層級交易隔離層級,但這不容易找到平衡點,當然這會保留住更好的擴充性與系統效能。



在lock機制上,EntityManager API 提供兩種方式 : Read  / Write。操作上很簡單,像是 manager.lock(account , LockMode.WRITE);不管是lockread or lockwrite,都可以確保能避免 dirty and nonrepeatable read 。

Entity Lookup and Query API
public <T> T find ( class<T> entityClass , object primarykey);

build a query object to retrieve results

  • Obtain an instance of javax.persistence.Query from the EntityManager

  • Customize the query object , fullfill the query conditions , if necessary by setting query parameters or an upper limit for the result set size.

  • Execute the query.


[caption id="attachment_350" align="alignnone" width="700" caption="query"]query[/caption]

query還有其他幾種用法,像是nativeQuery or namedQuery, nativeQuery主要是用在有時候查詢結果是不只一個單一的entity使用,而namedQuery則像是事先宣告好一組查詢語法內容,後續只要直接叫用即可,以下參考圖片資訊。



[caption id="attachment_351" align="alignnone" width="700" caption="NativeQuery"]NativeQuery[/caption]

[caption id="attachment_352" align="alignnone" width="700" caption="NamedQuery"]NamedQuery[/caption]

補充:為了考量系統存取安全性,EntityManager並不是Thread-safe的,所以在設計上可參考Threadlocal方式來處理。

參考下列網址:EntityManager with threadlocal , Using ThreadLocal and Servlet Filters to cleanly access JPA an EntityManager

沒有留言: