作者:Gavin King
翻譯:ChiKai
最近圍繞着簡單的JDBC框架,像iBATIS,總有一些喧鬧聲。就我個人觀點,在一個只有單一事務,實體間有着難懂的關聯圖,而且並不需要面向對象的域模式的應用,我自己是喜歡iBATIS的。假如你正在進行解決一些“極愚蠢的”遺留數據庫問題的工作,一個JDBC框架也是可以產生強的判斷力的。ORM的解決方案更趨向於假定那些同樣具有正確完整性約束參考的,比較清晰的外鍵聯合關係被體現出來的實體(Hiberante3這方面比Hibernate2.x少的多)。
有些人甚至建議JDBC框架是對ORM比較好的可供選擇的辦法,甚至對那些最適合用ORM的系統:具有一目瞭然的關係模型的面向對象的系統.他們說服自己說,你手寫的SQL代碼總是會比生成的SQL狀況好。我並不認爲這是正確的,並非僅僅是因爲大多數應用所需要的有壓倒性的顯得重要的SQL代碼是單調乏味,且不需要人們干涉的,而是因爲,JDBC框架是在ORM的不同語義級別起作用的。作爲一個解決方案,iBATIS關於SQL發佈的語義和作爲結果的資料這方面瞭解得非常少。這意味着非常少的時機爲了性能優化,例如,有效的緩存,(依據“有效”,我提及到的大體上地有效緩存無效策略,這對緩存的使用是至關緊要的)。而且,無論什麼時候我們手寫SQL,我們都知道N+1的Selects問題。那是非常單調乏味的爲了每個我可能需要連接在一起的關聯結合手寫一個新的SQL查詢。HQL在這方面有值得關注的幫助,因爲HQL是決不比SQL詳細的。如果說,一個JDBC框架能做ORM所能做的幾分性能優化,那將不得不成了一個類似詭辯的級別。本質上,它將需要成爲一個ORM,減少SQL的產生。事實上,我們已經開始注意到現有的JDBC框架發生的這種演變。這開始侵蝕任何一個規定的好處:需求的簡單。
這也會增加對下列問題有趣味的思考:如果,逐漸地增加原料,一個JDBC框架最後將會作爲ORM而結束,減少SQL的產生,爲什麼不接受一個現有的ORM解決方案呢?就像,呵呵,嗯,Hibernate,或許...和減去SQL的產生。
Hibernate團隊經過長期的驗證,需要混合和匹配產生SQL在那些特殊場合來手寫查詢。在Hibernate的老一版本,我們的解決方案就是簡單地暴露Hibernate正在使用的JDBC連接,因此,你能執行你自己的prepared statement。這將剛剛開始改變,馬克斯.安徒生最近在這方面已經做了很多工作了。現在,在Hibernate3,寫一個沒有產生SQL的應用實體將是可能的,這個時候仍然可以利用Hiberante的所有其它特性。
我們真的期望和打算人們以這種方式來用Hibernate?那好,並不是真正的-我懷疑有好多人會真正享受整天寫INSERT,UPDATE,DELETE 陳述語句的樂趣。另一方面,我也認爲相當少的人需要自定義特殊場合的查詢。除每一點的證明之外,我將給你展示怎麼用Hibernate來做,如果你真的想那麼做的話。
讓我們拿一個簡單的Person-Employment-Organization 域模式來做爲例子,(你可以在org.hibernate.test.sql 測試包裏面找到這些代碼,因此我並不打算在拷貝它們到這裏。)最簡單的類是Person;
這裏就是它的映射文件:
<class name="Person" lazy="true">
<id name="id" unsaved-value="0">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<loader query-ref="person"/>
<sql-insert>
INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )
</sql-insert>
<sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update>
<sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete>
</class>
首先我們要注意的是手寫INSERT, UPDATE and DELETE 陳述語句的時候,這個?參數的次序是匹配被列在上面的屬性的(我們最後將不得不支持指定的參數,我猜想)。我猜這沒什麼引起感興趣的地方。
更多的是對這<loader>標籤感興趣:它定義一個將會在當我們用get(),load(),或者延遲關聯來加載一個person實體的任何時候被用在指定的查詢的參考。特殊的,這指定的查詢可能是一個原生SQL查詢,既然這樣,下面的就是:
<sql-query name="person">
<return alias="p" class="Person" lock-mode="upgrade"/>
SELECT NAME AS {p.name}, ID AS {p.id} FROM PERSON WHERE ID=? FOR
UPDATE
</sql-query>
(原生SQL查詢可能會返回實體的多重的“列”;這就是最簡單的例子,在這裏僅僅只有一個實體被返回。)
Employment 是有些複雜,特殊的,並不是所有的屬性被包括在INSERT和UPDATE 陳述語句中:
<class name="Employment" lazy="true">
<id name="id" unsaved-value="0">
<generator class="increment"/>
</id>
<many-to-one name="employee" not-null="true" update="false"/>
<many-to-one name="employer" not-null="true" update="false"/>
<property name="startDate" not-null="true" update="false"
insert="false"/>
<property name="endDate" insert="false"/>
<property name="regionCode" update="false"/>
<loader query-ref="employment"/>
<sql-insert>
INSERT INTO EMPLOYMENT
(EMPLOYEE, EMPLOYER, STARTDATE, REGIONCODE, ID)
VALUES (?, ?, CURRENT_DATE, UPPER(?), ?)
</sql-insert>
<sql-update>UPDATE EMPLOYMENT SET ENDDATE=? WHERE ID=?</sql-update>
<sql-delete>DELETE FROM EMPLOYMENT WHERE ID=?</sql-delete>
</class>
<sql-query name="employment">
<return alias="emp" class="Employment"/>
SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer},
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
REGIONCODE as {emp.regionCode}, ID AS {emp.id}
FROM EMPLOYMENT
WHERE ID = ?
</sql-query>
在Organization實體的映射中有一個Employments的collection 集合:
<class name="Organization" lazy="true">
<id name="id" unsaved-value="0">
<generator class="increment"/>
</id>
<property name="name" not-null="true"/>
<set name="employments"
lazy="true"
inverse="true">
<key column="employer"/> <!-- only needed for DDL generation -->
<one-to-many class="Employment"/>
<loader query-ref="organizationEmployments"/>
</set>
<loader query-ref="organization"/>
<sql-insert>
INSERT INTO ORGANIZATION (NAME, ID) VALUES ( UPPER(?), ? )
</sql-insert>
<sql-update>
UPDATE ORGANIZATION SET NAME=UPPER(?) WHERE ID=?
</sql-update>
<sql-delete>DELETE FROM ORGANIZATION WHERE ID=?</sql-delete>
</class>
這裏的<loader>查詢並不僅僅是爲了Organization對象,也是爲了它的Employments集合:
<sql-query name="organization">
<return alias="org" class="Organization"/>
SELECT NAME AS {org.name}, ID AS {org.id} FROM ORGANIZATION
WHERE ID=?
</sql-query>
<sql-query name="organizationEmployments">
<return alias="empcol" collection="Organization.employments"/>
<return alias="emp" class="Employment"/>
SELECT {empcol.*},
EMPLOYER AS {emp.employer}, EMPLOYEE AS {emp.employee},
STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate},
REGIONCODE as {emp.regionCode}, ID AS {emp.id}
FROM EMPLOYMENT empcol
WHERE EMPLOYER = :id AND DELETED_DATETIME IS NULL
</sql-query>
當我寫這代碼時,我才真正開始感受到讓Hibernate幫我寫這些SQL的好處。
在這個簡單的例子中,我將已經排除至少35行以後不得不擁護的代碼。
最後,爲了一個特別的查詢,我們可以用一個原生SQL查詢(一個指定的查詢,或者一個嵌入到java中的代碼)。例子如下:
<sql-query name="allOrganizationsWithEmployees">
<return alias="org" class="Organization"/>
SELECT DISTINCT NAME AS {org.name}, ID AS {org.id}
FROM ORGANIZATION org
INNER JOIN EMPLOYMENT e ON e.EMPLOYER = org.ID
</sql-query>
我更寧願親自在Java中比在XML中寫代碼,爲了我的嗜好,因此所有的原料是那麼大量的臃腫的XML。我認爲我將會堅持從事SQL的生成,無論哪裏什麼地方。那並不是因爲我不喜歡SQL,事實上,我是一個SQL的狂熱迷,並僅僅喜歡當我打開Hibernate的日誌時候觀察過往查詢的翻動。那是因爲Hibernate比我會寫代碼多了。