我們繼續介紹hibernate註解的相關內容
1. OneToOne懶載入問題
一對一註解時,若採用外鍵列進行實體的關聯的話,懶載入問題是需要注意下的。如下:
Student表:
id int not nullname varchar(50) not nullcard_id int not null
Card表:
id int not nullcard_no varchar() not null
Student類
public class Student {private int id;private String name;private Card card;....@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)@JoinColumn(name = "card_id")public Card getCard() {return card;}public void setCard(Card card) {this.card = card;}}
Card類
public class Card {private int id;private String cardNo;private Student student;....@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "card")public Student getStudent() {return student;}public void setStudent(Student student) {this.student = student;}}
按照上述關係,運行代碼後我們會發現,Student擷取Card時可以懶載入,但Card擷取Student時卻無法懶載入,那麼這是為什麼呢?
原因:在Card表裡由於沒有關係欄位,因此僅從Card表角度看無法知道擁有該卡的學生是誰(除非在Card表建立Student表的外鍵student_id,但由於冗餘了關係欄位,因此很少有人這麼幹吧【注意sql的join語句是考慮了兩張表的】),而從Student角度不一樣,由於含有card_id欄位可能清楚知道該學生擁有一張卡。
正是由於上面的原因,因此當從Card擷取Student時,hibernate為了確定Student表中到底有沒有該Card,因此發了一條sql:select * from Student where card_id = ?,參數既是該Card的id,以此來維護Card與Student的關係。
還有一種解釋,但需要理解hibernate的懶載入的機制:代理。
2. 懶載入原理
hibernate使用了代理(Proxy),對實體的調用會被代理接受和處理,hibernate可以設定這個代理被調用到的時候去載入資料,從而實現消極式載入。那麼對於一個映射對象,要麼它有值,要麼它是null,對於null值建立代理是沒多大作用的,而且也不能對null建立動態代理。那就是說hibernate在對消極式載入建立代理的時候要考慮這個映射的對象是否是null。如果是null不需要建立代理,直接把映射的值設定成null,如果映射的對象不為null,那麼hibernate就建立代理對象。
簡而言之,為null就不能懶載入,為代理對象才能懶載入。
那麼解決上述問題的辦法呢?
● Card類的getStudent改為manyToOne,並設定unique=true。缺點:需要將欄位改為Set<Student>。
● 手動為Student建立代理對象。(不建議這麼做,而且我也沒試過)
● 採用no-proxy懶載入機制,對類用instrument進行增強,即用類增強器對二進位Class檔案進行強化處理。(很少有人這麼做,不推薦)
以下是利用ant調用hibernate類增強器對class檔案進行強化處理,ant的build.xml指令碼如下:
<?xml version="1.0" encoding="UTF-8"?><project name="hibernatelazy" default="instrument" basedir="."> <property name="lib.dir" value="./lib"/> <property name="classes.dir" value="./classes"/> <path id="lib.class.path"> <fileset dir="${lib.dir}"> <include name="**/*.jar"/> </fileset> </path> <target name="instrument"> <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask"> <classpath path="${classes.dir}"/> <classpath refid="lib.class.path"/> </taskdef> <instrument verbose="true"> <fileset dir="${classes.dir}/com/derek/known/hbm"> <include name="Knownquestions.class"/> </fileset> </instrument> </target></project>
其中注意:
<property name="lib.dir" value="./lib"/>所需的JAR檔案路徑。
<property name="classes.dir" value="./classes"/>編譯輸出路徑。
我把build.xml放在了WEB-INF目錄下,輸出路徑就設定為該目錄下的classes目錄,待增強位元組碼檔案為classes目錄下的com/derek/known/hbm/Knownquestions.class; 在命令列下切換到此目錄,執行ant命令,即產生新的Knownquestions.class。
● 放棄懶載入,改為join的載入策略,或者使用hql:from Card t inner join fetch t.student r
3. 懶載入註解方式詳解
● FetchType(@OneToOne、@OneToMany等註解裡的fetch屬性對應的類,本身沒有註解)
JPA標準的通用載入策略註解屬性。FetchType可選值意義與區別如下:
FetchType.LAZY:懶載入,在第一次訪問關聯對象的時候載入
FetchType.EAGER:立刻載入,在查詢主對象的時候同時載入關聯對象。
● @Fetch
hibernate定義了載入關聯關係的擷取策略。Fetch可選值意義與區別如下:
FetchMode.JOIN:始終立刻載入,使用外連(outer join)查詢的同時載入關聯對象,忽略FetchType.LAZY設定。
FetchMode.SELECT:支援懶載入(除非設定關聯屬性lazy=false),當訪問每一個關聯對象時載入該對象,會累計產生N+1條sql語句
FetchMode.SUBSELECT:支援懶載入(除非設定關聯屬性lazy=false),在訪問第一個關聯對象時載入所有的關聯對象。會累計產生兩條sql語句。
● @LazyToOne
hibernate定義了@ManyToOne 和@OneToOne 關聯的延遲選項。LazyToOne可選值意義與區別如下:
LazyToOneOption.PROXY:基於代理的消極式載入。
LazyToOneOption.NO_PROXY:基於位元組碼增強消極式載入 - 注意需要在構建期處理位元組碼增強。
LazyToOneOption.FALSE:非消極式載入的關聯。
● @LazyCollection
hibernate定義了@ManyToMany和@OneToMany 關聯的延遲選項。LazyCollection可選值意義與區別如下:
LazyCollectionOption.TRUE:集合具有延遲性,只有在訪問的時候才載入。
LazyCollectionOption.EXTRA:集合具有延遲性,並且所有的操作都會盡量避免載入集合, 對於一個巨大的集合特別有用,因為這樣的集合中的元素沒有必要全部載入。
LazyCollectionOption.FALSE:非消極式載入的關聯。
延遲和擷取選項的等效註解
Annotations |
Lazy |
Fetch |
@[One|Many]ToOne](fetch=FetchType.LAZY) |
@LazyToOne(PROXY) |
@Fetch(SELECT) |
@[One|Many]ToOne](fetch=FetchType.EAGER) |
@LazyToOne(FALSE) |
@Fetch(JOIN) |
@ManyTo[One|Many](fetch=FetchType.LAZY) |
@LazyCollection(TRUE) |
@Fetch(SELECT) |
@ManyTo[One|Many](fetch=FetchType.EAGER) |
@LazyCollection(FALSE) |
@Fetch(JOIN) |