hibernate之优化抓取(优化指导方针---n+1查询问题)
默认情况下,hibernate从不加载你没有请求的数据,这样减少了持久化上下文的内存消耗。然而,它也会让你面临所谓n+1查询问题。如果每一个关联和集合都只按需初始化,并且没有配置其他的策略。特定的过程也可以很好地执行几十甚至几百个查询,以获得你需要的所有数据。你需要正确的策略来避免执行过多的SQL语句。
如果从默认的策略转换到通过联结即时抓取数据的查询,就可能遇到另一个问题:笛卡儿积问题。无须执行过多的SQL语句,相反现在可以(经常作为一种附带作用)创建获取过多数据的语句。
要找到这两个极端之间的中间地带:每个过程的正确抓取策略,以及你应用程序中的用例。你需要知道应该在映射元数据中设置哪种全局的抓取计划和策略,以及只对一个特定的查询应用哪种抓取策略(通过HQL还是Criteria)。
----------
1.n+1查询问题
n+1查询问题很容易通过一些示例代码理解。假设你没有在映射元数据中配置任何抓取计划或者抓取策略:所有的东西都是延迟的,且按需加载。下列示例代码试图给所有Item找到最高的Bid:
首先,获取所有Item实例;这在HQL和Criteria查询之间没有区别。这个查询触发1个SQL select,获取ITEM表的所有行,并返回n个持久化对象。接下来,你遍历这个结果,并访问每一个Item对象。
你访问的是每个Item的bids集合。这个集合目前为止还没有被初始化,每件货品的Bid对象都必须通过一个额外的查询来加载。这整个代码片段因此产生了n+1查询。
你永远都想要避免n+1查询。
第一种解决方案可能是给集合改变你的全局映射元数据,启用批量预抓取:
不是n+1查询,你现在看到n/10+1查询在把必要的集合获取到内存中。(环境:假设持久化上下文中的并非所有的Item对象都需要初始化它们的Bid集合。)
~~~~~
利用一个基于子查询的预抓取,可以把选择的数量减少到正好两个:
过程中的第一个查询现在执行单个SQL SELECT,获取所有Item实例。hibernate记住这个语句,并当你命中第一个未被初始化的集合时,再次应用它。所有的集合都通过第二个查询被初始化。(环境:假设持久化上下文中的所有其他Item对象都需要初始化它们的Bid集合。)
~~~~~
最后,可以有效地关闭bids集合的延迟加载,并转换到只导致单个SQL SELECT的一种即时抓取策略:
这似乎是一项你不应该进行的优化。映射元数据中的抓取策略在一个全局的级别中工作 。我们不应该把fetch="join"当作对集合映射的一种常用优化。你始终很少需要完全被初始化的集合。除了导致更高的内存消耗之外,每一个被外部联结的集合也都导致了更严重的笛卡尔积的问题。
在实践中,你最可能在映射元数据中给bids集合启用一个批量的或者子查询策略。如果一个特定的过程需要内存中每一个Item的所有bids,你修改初始的HQL或者Criteria查询,并应用一种动态的抓取策略:
这两个查询通过一个OUTER JOIN,为所有Item实例都生成了获取bids的单个SELECT(就像你已经用fetch="join"映射了这个集合时发生的那样)。
这可能是你第一次见到如何定义一个非全局的抓取策略。你放在映射元数据中的全局抓取计划和抓取策略设置只是始终应用的全局默认。任何优化过程也都需要更细粒度的规则,即只适用于特定的过程或者用例的抓取策略和抓取计划。