📄 performance.html
字号:
查询抓取(默认的)在N+1查询的情况下是极其脆弱的,因此我们可能会要求在映射文档中定义使用连接抓取: </p><pre class="programlisting"><set name="permissions" fetch="join"> <key column="userId"/> <one-to-many class="Permission"/></set</pre><pre class="programlisting"><many-to-one name="mother" class="Cat" fetch="join"/></pre><p> 在映射文档中定义的<tt class="literal">抓取</tt>策略将会对以下列表条目产生影响: </p><div class="itemizedlist"><ul type="disc"><li><p> 通过<tt class="literal">get()</tt>或<tt class="literal">load()</tt>方法取得数据。 </p></li><li><p> 只有在关联之间进行导航时,才会隐式的取得数据。 </p></li><li><p> <tt class="literal">条件查询</tt> </p></li><li><p> 使用了<tt class="literal">subselect</tt>抓取的HQL查询 </p></li></ul></div><p> 不管你使用哪种抓取策略,定义为非延迟的类图会被保证一定装载入内存。注意这可能意味着在一条HQL查询后紧跟着一系列的查询。 </p><p> 通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中, 使用HQL的<tt class="literal">左连接抓取(left join fetch)</tt> 对其进行重载。这将通知 Hibernate在第一次查询中使用外部关联(outer join),直接得到其关联数据。 在<tt class="literal">条件查询</tt> API中,应该调用 <tt class="literal">setFetchMode(FetchMode.JOIN)</tt>语句。 </p><p> 也许你喜欢仅仅通过条件查询,就可以改变<tt class="literal">get()</tt> 或 <tt class="literal">load()</tt>语句中的数据抓取策略。例如: </p><pre class="programlisting">User user = (User) session.createCriteria(User.class) .setFetchMode("permissions", FetchMode.JOIN) .add( Restrictions.idEq(userId) ) .uniqueResult();</pre><p> (这就是其他ORM解决方案的“抓取计划(fetch plan)”在Hibernate中的等价物。) </p><p> 截然不同的一种避免N+1次查询的方法是,使用二级缓存。 </p></div><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="performance-fetching-proxies"></a>19.1.3. 单端关联代理(Single-ended association proxies) </h3></div></div><div></div></div><p> 在Hinerbate中,对集合的延迟抓取的采用了自己的实现方法。但是,对于单端关联的延迟抓取,则需要采用 其他不同的机制。单端关联的目标实体必须使用代理,Hihernate在运行期二进制级(通过优异的CGLIB库), 为持久对象实现了延迟载入代理。 </p><p> 默认的,Hibernate3将会为所有的持久对象产生代理(在启动阶段),然后使用他们实现 <tt class="literal">多对一(many-to-one)</tt>关联和<tt class="literal">一对一(one-to-one)</tt> 关联的延迟抓取。 </p><p> 在映射文件中,可以通过设置<tt class="literal">proxy</tt>属性为目标class声明一个接口供代理接口使用。 默认的,Hibernate将会使用该类的一个子类。 <span class="emphasis"><em>注意:被代理的类必须实现一个至少包可见的默认构造函数,我们建议所有的持久类都应拥有这样的构造函数</em></span> </p><p> 在如此方式定义一个多态类的时候,有许多值得注意的常见性的问题,例如: </p><pre class="programlisting"><class name="Cat" proxy="Cat"> ...... <subclass name="DomesticCat"> ..... </subclass></class></pre><p> 首先,<tt class="literal">Cat</tt>实例永远不可以被强制转换为<tt class="literal">DomesticCat</tt>, 即使它本身就是<tt class="literal">DomesticCat</tt>实例。 </p><pre class="programlisting">Cat cat = (Cat) session.load(Cat.class, id); // instantiate a proxy (does not hit the db)if ( cat.isDomesticCat() ) { // hit the db to initialize the proxy DomesticCat dc = (DomesticCat) cat; // Error! ....}</pre><p> 其次,代理的“<tt class="literal">==</tt>”可能不再成立。 </p><pre class="programlisting">Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxyDomesticCat dc = (DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!System.out.println(cat==dc); // false</pre><p> 虽然如此,但实际情况并没有看上去那么糟糕。虽然我们现在有两个不同的引用,分别指向这两个不同的代理对象, 但实际上,其底层应该是同一个实例对象: </p><pre class="programlisting">cat.setWeight(11.0); // hit the db to initialize the proxySystem.out.println( dc.getWeight() ); // 11.0</pre><p> 第三,你不能对“final类”或“具有final方法的类”使用CGLIB代理。 </p><p> 最后,如果你的持久化对象在实例化时需要某些资源(例如,在实例化方法、默认构造方法中), 那么代理对象也同样需要使用这些资源。实际上,代理类是持久化类的子类。 </p><p> 这些问题都源于Java的单根继承模型的天生限制。如果你希望避免这些问题,那么你的每个持久化类必须实现一个接口, 在此接口中已经声明了其业务方法。然后,你需要在映射文档中再指定这些接口。例如:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -