ORM問題域 - 切爾斯基 - 博客頻道 - CSDN.NET http://blog.csdn.net/chelsea/article/details/5094652
假設(shè)我們必須處理對象的存儲, 加載, 和查詢. 性能和引用完整性的約束, 給接口的實現(xiàn)帶來了以下問題:
加載根對象時如何避免加載大半個數(shù)據(jù)庫
存儲時如何更新整個對象圖
存儲時如何高效的更新整個對象圖
何時同步對象的內(nèi)存狀態(tài)和持久存儲狀態(tài)
如何確保在出錯時保持對象內(nèi)存狀態(tài)和持久存儲狀態(tài)之間的一致性
如何保證引用的唯一性以避免可能的更新沖突
對性能的精益求精, 又促使人們解決更多的細(xì)節(jié)問題:
N+1查詢問題
分離查詢模型和存儲模型
盡量減少查詢語句
這些問題的解決方案又會帶來新的問題.
- 加載根對象時如何避免加載大半個數(shù)據(jù)庫
更多的時候這是一個建模問題, 為什么我只需要顯示一點信息, 更新一點信息, 卻拉家?guī)Э诎寻藯U子打不著的親戚都帶上 : 細(xì)粒度對象設(shè)計, 直接訪問需要的信息, 減少所謂根對象的存在
一個workaround是延遲加載, 當(dāng)你無法修復(fù)你錯誤的建模時, 當(dāng)真正去訪問子對象的時候再發(fā)出查詢語句去加載. 這個方案會帶來如下問題:
查詢語句較多. 無解, 延遲意味著至少兩條SQL語句, 只能盡量減少
延遲加載的時機, 是自動透明的延遲加載, 還是用戶確定何時加載
Hibernate可通過配置文件指定是否lazy load, 一旦指定, 后面的load就是透明的在訪問子對象時發(fā)生. 也可在發(fā)出每次查詢時顯式指定
Entity Framework則要求用戶在每一次查詢時顯式指定包含哪個子對象, 對沒有指定包含的子對象, 只能在訪問前顯示使用load(). 理由是決定加載不加載,何時加載都是程序員的責(zé)任
然而更大的問題是如何管理數(shù)據(jù)庫連接, 要確保延遲加載的時候數(shù)據(jù)庫連接是開著的
可以使用Interceptor等技術(shù)維持 Session per request, Open Session in View pattern(處理好異常等, 確保session會關(guān)閉).
能在一個 Session 中使用兩個事務(wù)嗎?
是的冬耿,這事實上是這種模式(Open Session In View)的一個更好的實現(xiàn)个绍。在一個請求事件中船侧,一個數(shù)據(jù)庫事務(wù)用于數(shù)據(jù)的讀寫。第二個數(shù)據(jù)庫事務(wù)僅用于在渲染視圖期間讀數(shù)據(jù)。在這點上沒有對對象的修改。因此,數(shù)據(jù)庫鎖早在第一個事務(wù)時就被釋放了对竣,這使得應(yīng)用有更好的可伸縮性,第二個事務(wù)可以被優(yōu)化榜配。要使用兩階段的事務(wù)否纬,你需要比 Servlet Filter 更強大的攔截器 - AOP 是個很好的選擇。JBoss Seam 使用了這種模式蛋褥。
為什么 Hibernate 不在需要時就加載 Object临燃?
每個月很多人都會有這種想法,為什么 Hibernate 不能在有需要的就開啟一個新的數(shù)據(jù)庫連接(更有效率的是開啟一個 Session)壁拉,然后加載集合或是初始化代理谬俄,而是選擇拋出一個 LazyInitializationException。當(dāng)然弃理,這種想法溃论,第一眼看上去可能是明智之舉。但這種做法有很多的缺點痘昌,只有當(dāng)你考慮特別的事務(wù)訪問時才會發(fā)現(xiàn)钥勋。
如果 Hibernate 可以進(jìn)行任意的數(shù)據(jù)庫連接和事務(wù),這種操作是開發(fā)人員不可知辆苔,并且也是在任何事務(wù)邊界之外的算灸,那還要事務(wù)邊界做什么。當(dāng) Hibernate 開啟了新的數(shù)據(jù)庫連接去加載集合驻啤,但同時集合的擁有者卻被刪除了菲驴,這是將會發(fā)生什么?(注意骑冗,這種情況是不會發(fā)生在上面提到的兩階段的事務(wù)模式中的 - 單個 Session 可對實體可重復(fù)讀赊瞬。)當(dāng)所有的對象都可以通過關(guān)聯(lián)導(dǎo)航獲取時為什么還要有 Service 層?這種方式將消耗多少內(nèi)存贼涩?哪些對象要首先被清除掉巧涧?所有這些問題都是無解的,因為 Hibernate 是一個在線的事務(wù)處理服務(wù)(并包含一些批處理操作)遥倦,并不是一個“在未定義的工作單元中從數(shù)據(jù)持久倉庫取得對象”的服務(wù)谤绳。此外,對于 n+1 查詢問題,我們是否需要 n+1 的事務(wù)和連接的問題缩筛?
這個問題的解決方案當(dāng)然是正確的工作單元劃分和設(shè)計消略,支撐其的攔截技術(shù)就像這里所展現(xiàn)的一樣,并且/或者正確的抓取技術(shù)歪脏,使得特定工作單元所需的全部信息能夠以最小的影響疑俭、最好的性能和伸縮性被獲得。
- 存儲時如何更新整個對象圖
框架支持級聯(lián)更新. 是否應(yīng)該級聯(lián)更新, 哪些操作可以級聯(lián), 哪些不可以, 對象之間的哪些類型的關(guān)聯(lián)可以級聯(lián), 哪些不可以, 則是程序員的責(zé)任
通常被聚合的對象, 其生命周期應(yīng)由父對象負(fù)責(zé), 新增/更新/刪除都應(yīng)級聯(lián)
自身有存在意義的實體, 可以級聯(lián)更新, 但不應(yīng)刪除和新增
- 存儲時如何高效的更新整個對象圖
常用工作單元模式, Unit of Work.
- 何時同步對象的內(nèi)存狀態(tài)和持久存儲狀態(tài)
任何改動都立即提交到數(shù)據(jù)庫會帶來額外開銷. 一個時機是事務(wù)提交時.
Hibernate: 每間隔一段時間婿失,Session會執(zhí)行一些必需的SQL語句來把內(nèi)存中的對象的狀態(tài)同步到JDBC連接中。這個過程被稱為刷出(flush)啄寡,默認(rèn)會在下面的時間點執(zhí)行:
在某些查詢執(zhí)行之前
在調(diào)用org.hibernate.Transaction.commit()的時候
在調(diào)用Session.flush()的時候
- 如何確保在出錯時保持對象內(nèi)存狀態(tài)和持久存儲狀態(tài)之間的一致性
數(shù)據(jù)庫事務(wù)回滾, 清空內(nèi)存緩存, 重新加載
- 如何避免或處理可能的更新沖突
保證引用的唯一性: 使用單一的加載入口和緩存, Identity Map .
樂觀離線鎖會引入更新沖突問題, 一般使用Versioning來解決, 類似版本控制系統(tǒng)的更新問題; 但業(yè)務(wù)對象很少能自動Merge, Merge的語義也不好定義, 所以一般檢測到?jīng)_突之后只好重做了, 或者取決于業(yè)務(wù)邏輯, Last Win也是一種策略.
- N+1查詢問題
Eager Load + JOIN
截然不同的一種避免N+1次查詢的方法是豪硅,使用二級緩存。
N + 1 是關(guān)聯(lián)引入的問題, 網(wǎng)上的解釋和例子傾向于拿one-2-many說事, 但實際上one-2-one依然面臨使用多于一條SQL語句加載的問題
- 分離查詢模型和存儲模型
適合業(yè)務(wù)關(guān)系的對象模型未必對查詢是高效的. 需要單獨針對查詢建模, 可以用單獨的索引表來實現(xiàn). 在更新業(yè)務(wù)對象的存儲時同時更新索引表
- 盡量減少查詢語句
比如join over multiple select, 比如批量抓取
- 值類型
不需要有ID, 通常被聚合. 有對應(yīng)的Class, 但一般沒有對應(yīng)的Table, 僅是Table中的幾個字段
挑戰(zhàn)在于將對象語言類型系統(tǒng)(和開發(fā)者定義的實體和值類型)映射到 SQL/數(shù)據(jù)庫類型系統(tǒng)挺物。 Hibernate: 提供了連接兩個系統(tǒng)之間的橋梁:對于實體類型懒浮,我們使用class, subclass 等等。對于值類型识藤,我們使用 property, component 及其他砚著,通常跟隨著type屬性。這個屬性的值是Hibernate 的映射類型的名字