懶加載LAZY和實時加載EAGER的概念,在各種開發語言中都有廣泛應用。其目的是實現關聯數據的選擇性加載,懶加載是在屬性被引用時,才生成查詢語句,抽取相關聯數據。而實時加載則是執行完主查詢后,不管是否被引用,立馬執行后續的關聯數據查詢。社區里有人認為懶加載這種功能比較雞肋,這種事仁者見仁,智者見智啦,個人覺得依自己業務場景而定。
順帶說一句,使用懶加載來調用關聯數據,必須要保證主查詢session(數據庫連接會話)的生命周期沒有結束,否則,你是無法抽取到數據的。在Spring Data JPA中如何自己控制session的生命周期,這個功能我還沒有研究,作為下一次的研究課題。
在今天解析懶加載與實時加載的實例時,需要借助實體關系映射功能,配置實體間的一對多(OneToMany)和多對一(ManyToOne)關系。當然,還有OneToOne、ManyToMany關系,這個如有興趣大家就自己研究下了。
一、為實體類ProcessBlock和Node添加一對多的關聯關系。在我這個示例中ProcessBlock是流程定義類,一個流程定義對應多個流程節點,即Node表。下面給出代碼。
1、ProcessBlock實體定義
@Entity(name = "nbpm_processblock") @NamedQuery(name = "ProcessBlock.findByName", query = "select p from nbpm_processblock p where p.name=?1") public class ProcessBlock implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "id") long id; @Column(name = "name") String name; @Column(name = "description") String description; @OneToMany(mappedBy="processblock",cascade=CascadeType.ALL,fetch = FetchType.EAGER) Set<Node> nodeSet = new HashSet<Node>(); public long getId() { return id; }
……
定義Set<Node>屬性 nodeSet,為其添加注解@OneToMany,意思就是一個ProcessBlock實體對應多個Node節點。注解屬性 mappedBy,是指關聯關系可以從Node類中的processblock屬性上獲取;cascade屬性設置成CascadeType.ALL,是說主從表全面建立級聯關系;fetch屬性設置成FetchType.EAGER,是指加載規則是即時加載。查看源碼會發現默認情況下,OneToMany注解的fetch屬性設置的是FetchType.LAZY。
2、Node實體定義
@Entity(name = "nbpm_node") public class Node implements Serializable { private static final long serialVersionUID = 1L; @Id @Column(name = "id") long id; @Column(name = "name") String name; @Column(name = "description") String description; @Column(name = "subclass") String subclass; /** * 關聯關系必須存在 */ @ManyToOne(optional = true) @JoinColumn(name="processblock") ProcessBlock processblock; public long getId() { return id; }
……
定義ProcessBlock屬性 processblock,為其添加注解@ManyToOne,意思就是一個Node實體對應一個ProcessBlock流程定義。添加注解@JoinColumn,屬性name設置成"processblock",意思是nbpm_node表中的processblock字段是外鍵,與ProcessBlock的主鍵建議關聯關系。需要說明一下 ManyToOne注解的fetch屬性 默認是FetchType.EAGER,查詢時會立馬抽取對應的主表數據。這塊的考慮還是很細致的,大致就是關聯數據是集合時就懶加載,是單條數據時就實時加載。
二、編寫查詢示例並展示結果。查詢使用自定義的JpaUtil工具,工具實現請參考《自定義JpaUtil,快速完成Hql執行邏輯(一)》
1、查詢ProcessBlock,主干流程數據,因為設置的nodeSet屬性是實時加載加載,查詢完成,斷點查看確實拿到了流程對應的所有節點信息。
Map<String, Object> params = new HashMap<>(); params.put("name", "主干流程"); List<ProcessBlock> list = ApplicationContextUtil.instance.getJpaUtil().list( "select u from simm.spring.entity.ProcessBlock u where u.name=:name", params, ProcessBlock.class); Set<Node> nodes = list.get(0).getNodeSet();
-- Hibernate框架生成的查詢語句 ----------------- Hibernate: select processblo0_.id as id1_2_, processblo0_.description as descript2_2_, processblo0_.name as name3_2_ from nbpm_processblock processblo0_ where processblo0_.name=? Hibernate: select nodeset0_.processblock as processb5_1_0_, nodeset0_.id as id1_1_0_, nodeset0_.id as id1_1_1_, nodeset0_.description as descript2_1_1_, nodeset0_.name as name3_1_1_, nodeset0_.processblock as processb5_1_1_, nodeset0_.subclass as subclass4_1_1_ from nbpm_node nodeset0_ where nodeset0_.processblock=? Hibernate: select nodeset0_.processblock as processb5_1_0_, nodeset0_.id as id1_1_0_, nodeset0_.id as id1_1_1_, nodeset0_.description as descript2_1_1_, nodeset0_.name as name3_1_1_, nodeset0_.processblock as processb5_1_1_, nodeset0_.subclass as subclass4_1_1_ from nbpm_node nodeset0_ where nodeset0_.processblock=? Hibernate: select nodeset0_.processblock as processb5_1_0_, nodeset0_.id as id1_1_0_, nodeset0_.id as id1_1_1_, nodeset0_.description as descript2_1_1_, nodeset0_.name as name3_1_1_, nodeset0_.processblock as processb5_1_1_, nodeset0_.subclass as subclass4_1_1_ from nbpm_node nodeset0_ where nodeset0_.processblock=? Hibernate: select nodeset0_.processblock as processb5_1_0_, nodeset0_.id as id1_1_0_, nodeset0_.id as id1_1_1_, nodeset0_.description as descript2_1_1_, nodeset0_.name as name3_1_1_, nodeset0_.processblock as processb5_1_1_, nodeset0_.subclass as subclass4_1_1_ from nbpm_node nodeset0_ where nodeset0_.processblock=?
2、將ProcessBlock類的屬性nodeSet設置成懶加載LAZY,查詢后,發現對應的節點列表信息獲取不到了。這里懶加載的數據讀取不到是因為主查詢的session已經結束。
@OneToMany(mappedBy="processblock",cascade=CascadeType.ALL,fetch = FetchType.LAZY) //@JoinColumn(name="processblock_id") Set<Node> nodeSet = new HashSet<Node>();
-- Hibernate框架生成的查詢語句 ----------------- Hibernate: select processblo0_.id as id1_2_, processblo0_.description as descript2_2_, processblo0_.name as name3_2_ from nbpm_processblock processblo0_ where processblo0_.name=?
3、Hql直接查詢關聯屬性
List<Node> nodeList = ApplicationContextUtil.instance.getJpaUtil().list( "select u.nodeSet from simm.spring.entity.ProcessBlock u where u.id=1", null, Node.class);
-- Hibernate框架生成的查詢語句 ----------------- Hibernate: select nodeset1_.id as id1_1_, nodeset1_.description as descript2_1_, nodeset1_.name as name3_1_, nodeset1_.processblock as processb5_1_, nodeset1_.subclass as subclass4_1_ nbpm_processblock processblo0_ inner join nbpm_node nodeset1_ on processblo0_.id=nodeset1_.processblock where processblo0_.id=1 Hibernate: select processblo0_.id as id1_2_0_, processblo0_.description as descript2_2_0_, processblo0_.name as name3_2_0_ from nbpm_processblock processblo0_ where processblo0_.id=?
至此,本次測試的主要內容就說完了,調試細節比較繁雜這里不再細表。在最后有個疑問與大家交流下,在ManyToOne這個注解中有一個optional的屬性,源碼里標注該屬性默認設置成true,即與主表間的關聯可以為null,設置成false時,則關聯關系不能為空。我猜想着設置此關系后,生成的sql語句可能會有所不同,或者會引起系統運行時報錯什么的。但是測試發現,並沒有影響,sql語句都是left outer join關聯,設置成false時,空關系也不會造成報錯,似乎沒有作用,調試源碼暫時還沒有發現其調用邏輯。有道友知道這個功能嗎?可以幫我解答一下,謝謝!