點擊上方"java全棧技術"關注,每天學習一個java知識點,喜歡的也可以關注微信公眾號"ITeye"
![「每日分享」Hibernate那些重要的知識點](http://p2.ttnews.xyz/loading.gif)
引題
對象關係映射(Object Relational Mapping,簡稱ORM)是一種為了解決面向對象與關係數據庫存在的互不匹配的現象的規範,簡單的說,ORM是通過使用描述對象和數據庫之間映射的元數據,將java程序中的對象自動持久化到關係數據庫中.
![「每日分享」Hibernate那些重要的知識點](http://p2.ttnews.xyz/loading.gif)
ORM框架在前後端領域都能看到它的影子,比如Android的greenDAO,比如iOS的coreData,比如Node.js的mongoose,這裡主要講解Java中Hibernate我們比較容易忽略和重要的點
save和get的執行流程
save:
- 根據對象找到user類(user.getClass)和對應的映射文件User.hbm.xml,並解析出表名t_user
- 使用內省機制操作user對象,獲取其中的屬性:id/name,
- 解析映射文件,找到屬性對應的列名
- 根據主鍵生成策略,如果是native,此時主鍵就不出現在SQL語句中,如當前的SQL語句為:insert into t_user(uname) values (?)其中?就對應user.getXxx()方法
get:
- 根據對象找到user類(user.getClass)和對應的映射文件User.hbm.xml,並解析出表名t_user
- 解析映射文件,找到
元素對應的列名uid,SQL就拼接成功了 - 處理結果集,把一條數據封裝成User對象
- 創建User對象
- 根據列名找到對應的屬性名,調用user.setXxx()後返回對象
get和load方法的區別
先看這段簡單的測試代碼
- get會立即發送select語句,load不會立刻發送,當使用到該對象的非OID屬性時才會發送,延遲加載
- load方法返回的對象永遠不為null,即使在數據庫中不存在,所以不能使用if-null的方式來判斷,而get可以為null,因為load執行的時候沒有發送select語句,所以他不知道數據庫中有沒有對應數據,所以索性返回一個不為null的對象,如果存在,則再把數據設置到對象中去,如果不存在,使用該對象時報錯
- load方法會創建出代理對象,但是代理對象必須在session關閉之前創建出來,否則會報hibernate中最常見的錯誤,no session,解決辦法為Hibernate.initialize(代理對象)
持久化對象的生命週期
為什麼需要關注持久化對象的生命週期?那我們來回憶使用Hibernate中是否遇到的三個問題
- 問題一: 主鍵生成策略不同,save操作時發生INSERT語句的時機不同?
- native: 在執行save方法的時候發送INSERT SQL,
- increment: 在提交事務的時候,才發送INSERT SQL
- 問題二: 刪除對象的時候,沒有立刻發生DELETE語句,而是在提交事務的時候發送的.
- 問題三: 為什麼在事務環境下,通過get方法得到的對象,只要修改了屬性值,會發生UPDATE語句.
那麼SQL的執行時機和什麼有關係呢?和對象的狀態有關係.那持久化對象的狀態有哪一些?怎麼劃分的?
劃分的規則:
- 當前對象是否有OID(該對象在表中對應有一個id值)
- 對象是否被session所管理(對象是否在一級緩存中)
對象狀態的總結
session中的方法僅僅只是改變對象的狀態,不負責發送SQL/默認情況下事務提交的時候發送SQL,那麼之前是三個問題就可以迎刃而解了.
- 問題一解答: :save方法僅僅是把臨時狀態的對象轉換為持久化狀態,本身不負責發送SQL.臨時狀態的對象沒有OID,調用save方法之後,變成持久化狀態,就必須有OID. * native: 表示數據庫主鍵的自增長,只有發送SQL,才能獲取主鍵,從而獲取OID。increment: 先發送SELECT語句查詢id(擁有了OID),不需要發送increment來獲取OID.
- 問題二解答: delete方法僅僅是改變對象的狀態,本身不負責發送SQL.因此按照默認的方式,提交事務的時候發送SQL
- 問題三解答: 通過get查詢操作得到的對象處於持久化狀態(有OID,存在於一級緩存中).此時,修改了非IOD的屬性值,發現一級緩存中的數據和快照區域的數據不同(髒數據),Hibernate就會做比較(一級緩存和快照區),發現不同,就發送UPDATE語句,做數據同步.session的flush方法,負責把一級緩存中的髒數據同步到數據庫中去.
二級緩存
要了解二級緩存,我們就必須知道一級緩存是什麼.介紹一級緩存之前,我們先回顧一下Session
session
- session對象,通過sessionFactory對象創建而來,包含了connection對象,封裝了很多操作方法.
- session不是線程安全的(使用局部變量),所以,session的最大生命週期:一個線程,在web應用當中,一個session的最大生命週期:request
- session中有一個緩存,稱為一級緩存。存放當前工作單元加載的對象.在一個session的生命週期之內,連續拿相同類型,相同ID的對象,只需要發送一次SQL
原理如圖:
雖然一級緩存可以提高性能,但是由於session的作用域有限,因此,提高的性能也是非常有限的.所以這就引出了二級緩存的概念
二級緩存
- 在整個應用中,有且只需要一個sessionFactory對象即可
- 生命週期為整個應用的緩存(二級緩存是sessionFactory上的緩存,能提供整個應用中所有的session使用)
- 所有的get,load方法,總是先查一級緩存,再查二級緩存,如果都沒有,在去數據庫裡面查詢
事務併發問題
事務併發時,會產生兩類丟失更新問題,如圖
- 第一類丟失更新: A事務撤銷時,把已經提交的B事務的更新數據覆蓋了.
- 第二類丟失更新: A事務覆蓋B事務已經提交的數據,造成B事務所做操作丟失.
然而解決的辦法有兩個,一個稱之為悲觀鎖,一個稱之為樂觀鎖
悲觀鎖(Pessimistic Lock): 悲觀地認為每次自己去拿數據的時候別人會修改數據,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。底層採用的就是SELECT ..... FOR UPDATE
樂觀鎖(Optimistic Lock): 樂觀地認為每次去拿數據的時候別人不會修改數據,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。
在hibernate中使用樂觀鎖,推薦使用version方式:
鏈接:https://www.jianshu.com/p/44f641fd8363
閱讀更多 java全棧技術 的文章