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