hibernate优化

来源:互联网 发布:webpack 压缩混淆js 编辑:程序博客网 时间:2024/04/24 18:52

抓取策略(Fetching strategies

当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候,Hibernate 使用抓取策略(fetching strategy) 获取关联对象。抓取策略可以在 O/R 映射的元数据中声明,也可以在特定的 HQL 或条件查询(Criteria Query)中重载声明。

Hibernate3 定义了如下几种抓取策略:

•连接抓取(Join fetching):Hibernate 通过在 SELECT 语句使用 OUTER JOIN(外连接)来获得对象的关联实例或者关联集合。

•查询抓取(Select fetching):另外发送一条 SELECT 语句抓取当前对象的关联实体或集合。除非你显式的指定 lazy="false" 禁止 延迟抓取(lazy fetching),否则只有当你真正访问关联关系的时候,才会执行第二条 select 语句。

•子查询抓取(Subselect fetching):另外发送一条 SELECT 语句抓取在前面查询到(或者抓取到)的所有实体对象的关联集合。除非你显式的指定 lazy="false" 禁止延迟抓取(lazy  fetching),否则只有当你真正访问关联关系的时候,才会执行第二条 select 语句。

•批量抓取(Batch fetching):对查询抓取的优化方案,通过指定一个主键或外键列表,Hibernate 使用单条 SELECT 语句获取一批对象实例或集合。

Hibernate 会区分下列各种情况:

•mmediate fetching,立即抓取:当宿主被加载时,关联、集合或属性被立即抓取。

•lazy collection fetching,延迟集合抓取:直到应用程序对集合进行了一次操作时,集合才被抓取(对集合而言这是默认行为)。

•Extra-lazy" collection fetching,"Extra-lazy" 集合抓取:对集合类中的每个元素而言,都是直到需要时才去访问数据库。除非绝对必要,Hibernate 不会试图去把整个集合都抓取到内存里来(适用于非常大的集合)。

•proxy fetching,代理抓取:对返回单值的关联而言,当其某个方法被调用,而非对其关键字进行 get 操作时才抓取。

•No-proxy" fetching,非代理抓取:对返回单值的关联而言,当实例变量被访问的时候进行抓取。与上面的代理抓取相比,这种方法没有那么“延迟”得厉害(就算只访问标识符,也会导致关联抓取)但是更加透明,因为对应用程序来说,不再看到 proxy。这种方法需要在编译期间进行字节码增强操作,因此很少需要用到。

•lazy attribute fetching,属性延迟加载:对属性或返回单值的关联而言,当其实例变量被访问的时候进行抓取。需要编译期字节码强化,因此这一方法很少是必要的。

   这里有两个正交的概念:关联何时被抓取,以及被如何抓取(会采用什么样的 SQL 语句)。注意不要混淆它们。我们使用抓取来改善性能。我们使用延迟来定义一些契约,对某特定类的某个脱管的实例,知道有哪些数据是可以使用的。

操作延迟加载的关联

默认情况下,Hibernate 3 对集合使用延迟 select 抓取,对返回单值的关联使用延迟代理抓取。对几乎是所有的应用而言,其绝大多数的关联,这种策略都是有效的。假若你设置了 hibernate.default_batch_fetch_size,Hibernate 会对延迟加载采取批量抓取优化措施(这种优化也可能会在更细化的级别打开)。

然而,你必须了解延迟抓取带来的一个问题。在一个打开的 Hibernate session 上下文之外调用延迟集合会导致一次意外。比如:

s = sessions.openSession();

Transaction tx = s.beginTransaction();

User u = (User) s.createQuery("from User u where u.name=:userName")

.setString("userName", userName).uniqueResult();

Map permissions = u.getPermissions();

tx.commit();

s.close();

Integer accessLevel = (Integer) permissions.get("accounts"); // Error!

在 Session 关闭后,permessions 集合将是未实例化的、不再可用,因此无法正常载入其状态。Hibernate 对脱管对象不支持延迟实例化。这里的修改方法是将 permissions 读取数据的代码移到事务提交之前。除此之外,通过对关联映射指定 lazy="false",我们也可以使用非延迟的集合或关联。但是,对绝大部分集合来说,更推荐使用延迟方式抓取数据。如果在你的对象模型中定义了太多的非延迟关联,Hibernate 最终几乎需要在每个事务中载入整个数据库到内存中。但是,另一方面,在一些特殊的事务中,我们也经常需要使用到连接抓取(它本身上就是非延迟的),以代替查询抓取。 下面我们将会很快明白如何具体的定制 Hibernate 中的抓取策略。在Hibernate3 中,具体选择哪种抓取策略的机制是和选择 单值关联或集合关联相一致的。

2.调整抓取策略(Tuning fetch strategies

查询抓取(默认的)在 N+1 查询的情况下是极其脆弱的,因此我们可能会要求在映射文档中定义

使用连接抓取:

<set name="permissions"

fetch="join">

<key column="userId"/>

<one-to-many class="Permission"/>

</set

<many-to-one name="mother" class="Cat" fetch="join"/>

在映射文档中定义的抓取策略将会对以下列表条目产生影响:

•通过 get() 或 load() 方法取得数据。

•只有在关联之间进行导航时,才会隐式的取得数据。

•条件查询

•使用了 subselect 抓取的 HQL 查询

不管你使用哪种抓取策略,定义为非延迟的类图会被保证一定装载入内存。注意这可能意味着在一条 HQL 查询后紧跟着一系列的查询。

通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中, 使用 HQL 的左连接抓取(left join fetch) 对其进行重载。这将通知 Hibernate在第一次查询中使用外部关联(outer join),直接得到其关联数据。在条件查询 API 中,应该调用 setFetchMode(FetchMode.JOIN)语句。

也许你喜欢仅仅通过条件查询,就可以改变 get() 或 load() 语句中的数据抓取策略。例如:

User user = (User) session.createCriteria(User.class)

.setFetchMode("permissions", FetchMode.JOIN)

.add( Restrictions.idEq(userId) )

.uniqueResult();

这就是其他 ORM 解决方案的“抓取计划(fetch plan)”在 Hibernate 中的等价物。截然不同的一种避免 N+1 次查询的方法是,使用二级缓存。

3.实例化集合和代理(Initializing collections and proxies

在 Session 范围之外访问未初始化的集合或代理,Hibernate 将会抛出LazyInitializationException 异常。也就是说,在分离状态下,访问一个实体所拥有的集合,或者访问其指向代理的属性时,会引发此异常。有时候我们需要保证某个代理或者集合在 Session 关闭前就已经被初始化了。当然,我们可以通过强行调用 cat.getSex() 或者 cat.getKittens().size() 之类的方法来确保这一点。 但是这样的程序会造成读者的疑惑,也不符合通常的代码规范。静态方法 Hibernate.initialized() 为你的应用程序提供了一个便捷的途径来延迟加载集合或代理。 只要它的 Session 处于 open 状态,Hibernate.initialize(cat) 将会为 cat 强制对代理实例化。同样,Hibernate.initialize(cat.getKittens()) 对 kittens 的集合具有同样的功能。

还有另外一种选择,就是保持 Session 一直处于 open 状态,直到所有需要的集合或代理都被载入。 在某些应用架构中,特别是对于那些使用 Hibernate 进行数据访问的代码,以及那些在不同应用层和不同物理进程中使用 Hibernate 的代码。 在集合实例化时,如何保证 Session 处于open 状态经常会是一个问题。有两种方法可以解决此问题:

在一个基于 Web 的应用中,可以利用 servlet 过滤器(filter),在用户请求(request)结束、页面生成 结束时关闭 Session(这里使用了在展示层保持打开 Session 模式(Open

Session in View),当然,这将依赖于应用框架中异常需要被正确的处理。在返回界面给用户之前,乃至在生成界面过程中发生异常的情况下,正确关闭 Session 和结束事务将是非常重要的.

在一个拥有单独业务层的应用中,业务层必须在返回之前,为 web 层“准备”好其所需的数据集合。这就意味着 业务层应该载入所有表现层/web 层所需的数据,并将这些已实例化完毕的数据返回。通常,应用程序应该为 web 层所需的每个集合调用 Hibernate.initialize()(这个调用必须发生咱 session 关闭之前);或者使用带有 FETCH 从句,或 FetchMode.JOIN的 Hibernate 查询,事先取得所有的数据集合。如果你在应用中使用了 Command 模式,代替Session Facade,那么这项任务将会变得简单的多。

你也可以通过 merge() 或 lock() 方法,在访问未实例化的集合(或代理)之前,为先前载入的对象绑定一个新的 Session。显然,Hibernate 将不会,也不应该自动完成这些任务,因为这将引入一个特殊的事务语义。

有时候,你并不需要完全实例化整个大的集合,仅需要了解它的部分信息(例如其大小)、或者集合的部分内容。你可以使用集合过滤器得到其集合的大小,而不必实例化整个集合:

( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue()

这里的 createFilter() 方法也可以被用来有效的抓取集合的部分内容,而无需实例化整个集合:

s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list();

4.使用批量抓取(Using batch fetching

Hibernate 可以充分有效的使用批量抓取,也就是说,如果仅一个访问代理(或集合),那么Hibernate 将不载入其他未实例化的代理。批量抓取是延迟查询抓取的优化方案,你可以在两种批量抓取方案之间进行选择:在类级别和集合级别。

类/实体级别的批量抓取很容易理解。假设你在运行时将需要面对下面的问题:你在一个 Session中载入了 25 个 Cat 实例,每个 Cat 实例都拥有一个引用成员 owner,其指向 Person,而 Person

类是代理,同时 lazy="true"。如果你必须遍历整个 cats 集合,对每个元素调用 getOwner() 方法,Hibernate 将会默认的执行 25 次 SELECT 查询, 得到其 owner 的代理对象。这时,你可以通过在映射文件的 Person 属性,显式声明 batch-size,改变其行为:

<class name="Person" batch-size="10">...</class>

随之,Hibernate 将只需要执行三次查询,分别为 10、10、 5。

你也可以在集合级别定义批量抓取。例如,如果每个 Person 都拥有一个延迟载入的 Cats 集合,现在,Sesssion 中载入了 10 个 person 对象,遍历 person 集合将会引起 10 次 SELECT 查询,

每次查询都会调用 getCats() 方法。如果你在 Person 的映射定义部分,允许对 cats 批量抓取,那么,Hibernate 将可以预先抓取整个集合。请看例子:

<class name="Person">

<set name="cats" batch-size="3">

...

</set>

</class>

使用延迟属性抓取(Using lazy property fetching

Hibernate3 对单独的属性支持延迟抓取,这项优化技术也被称为组抓取(fetch groups)。可以在映射文件中对特定的属性设置 lazy,定义该属性为延迟载入。

<class name="Document">

<id name="id">

<generator class="native"/>

</id>

<property name="name" not-null="true" length="50"/>

<property name="summary" not-null="true" length="200" lazy="true"/>

<property name="text" not-null="true" length="2000" lazy="true"/>

</class>

属性的延迟载入要求在其代码构建时加入二进制指示指令(bytecode instrumentation),如果你的持久类代码中未含有这些指令, Hibernate 将会忽略这些属性的延迟设置,仍然将其直接载入。

你可以在 Ant 的 Task 中,进行如下定义,对持久类代码加入“二进制指令。”

<target name="instrument" depends="compile">

<taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask">

<classpath path="${jar.path}"/>

<classpath path="${classes.dir}"/>

<classpath refid="lib.class.path"/>

</taskdef>

<instrument verbose="true">

<fileset dir="${testclasses.dir}/org/hibernate/auction/model">

<include name="*.class"/>

</fileset>

</instrument>

</target>

还有一种可以优化的方法,它使用 HQL 或条件查询的投影(projection)特性,可以避免读取非必要的列, 这一点至少对只读事务是非常有用的。它无需在代码构建时“二进制指令”处理,因

此是一个更加值得选择的解决方法。有时你需要在 HQL 中通过抓取所有属性,强行抓取所有内容。

5.二级缓存(The Second Level Cache

Hibernate 的 Session 在事务级别进行持久化数据的缓存操作。 当然,也有可能分别为每个类(或集合),配置集群、或 JVM 级别(SessionFactory 级别)的缓存。你甚至可以为之插入一个集群的缓存。注意,缓存永远不知道其他应用程序对持久化仓库(数据库)可能进行的修改 (即使可以将缓存数据设定为定期失效)。

使用 ehcache 实现 hibernate 二级缓存

导入jar包 

ehcache-1.5.0.jar

commons-logging.jar

backport-util-concurrent.jar

在src导入 ehcache.xml  配置对象,是否缓存到硬盘

hbm文件配置二级缓存并发策略 

<class name="cn.itcast.bos.domain.qp.WorkOrderManage" table="qp_workordermanage" catalog="bos">

<cache usage="read-write"/>

... 

applicationContext-common.xml 配置hibernate属性,开启二级缓存

<prop key="hibernate.cache.use_second_level_cache">true</prop>

<prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>


原创粉丝点击