- 浏览: 51331 次
- 性别:
- 来自: 苏州
最新评论
-
237304457:
好东西,firefox debug 的学习
Firebug JavaScrupt JS 调试 -
lc87624:
能麻烦问一下这是转至哪里的文章吗?想看一下全文
JSP处理session与cookie关系(转载)
转载 主题:关于hibernate延迟加载的错误解决方案 收藏
在项目开发中,对于struts的数据读取,当遇到多个表关联的数据读取的时候,精彩会出现session close的错误,一般的解决方案是将hib的延迟加载错误取消掉,但是这样赶鸭子上架的解决机制无疑是对程序的极度不负责,在这里我们用到 hibernte自带的一种机制,session.load。
例如
notic----user
user----userInfo
三个表,notic a = (notic)session.load(notic,id);
a.getuser()
a.getuser.getuserinfo();
session.transaction.commit();
问题解决。
当然hibernate也有相关的迟缓策略,官方文档如下:
20.1. 抓取策略(Fetching strategies)
抓取策略(fetching strategy) 是指:当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候, Hibernate如何获取关联对象的策略。抓取策略可以在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会区分下列各种情况:
*
Immediate fetching,立即抓取 - 当宿主被加载时,关联、集合或属性被立即抓取。
*
Lazy collection fetching,延迟集合抓取 - 直到应用程序对集合进行了一次操作时,集合才被抓取。(对集合而言这是默认行为。)
*
Proxy fetching,代理抓取 - 对返回单值的关联而言,当其某个方法被调用,而非对其关键字进行get操作时才抓取。
*
Lazy attribute fetching,属性延迟加载 - 对属性或返回单值的关联而言,当其实例变量被访问的时候进行抓取(需要运行时字节码强化)。这一方法很少是必要的。
这里有两个正交的概念:关联何时 被抓取,以及被如何 抓取(会采用什么样的SQL语句)。不要混淆它们!我们使用抓取 来改善性能。我们使用延迟 来定义一些契约,对某特定类的某个脱管的实例,知道有哪些数据是可以使用的。
20.1.1. 操作延迟加载的关联
默认情况下,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读取数据的代码 移到tx.commit()之前。
除此之外,通过对关联映射指定lazy="false" ,我们也可以使用非延迟的集合或关联。但是, 对绝大部分集合来说,更推荐使用延迟方式抓取数据。如果在你的对象模型中定义了太多的非延迟关联,Hibernate最终几乎需要在每个事务中载入整个数据库到内存中!
但是,另一方面,在一些特殊的事务中,我们也经常需要使用到连接抓取(它本身上就是非延迟的),以代替查询抓取。下面我们将会很快明白如何具体的定制Hibernate中的抓取策略。在Hibernate3中,具体选择哪种抓取策略的机制是和选择单值关联或集合关联相一致的。
20.1.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() 方法取得数据。
*
只有在关联之间进行导航时,才会隐式的取得数据(延迟抓取)。
*
条件查询
通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中, 使用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次查询的方法是,使用二级缓存。
20.1.3. 单端关联代理(Single-ended association proxies)
在Hinerbate中,对集合的延迟抓取的采用了自己的实现方法。但是,对于单端关联的延迟抓取,则需要采用 其他不同的机制。单端关联的目标实体必须使用代理,Hihernate在运行期二进制级(通过优异的CGLIB库), 为持久对象实现了延迟载入代理。
默认的,Hibernate3将会为所有的持久对象产生代理(在启动阶段),然后使用他们实现 多对一(many-to-one) 关联和一对一(one-to-one) 关联的延迟抓取。
在映射文件中,可以通过设置proxy 属性为目标class声明一个接口供代理接口使用。 默认的,Hibernate将会使用该类的一个子类。 注意:被代理的类必须实现一个至少包可见的默认构造函数,我们建议所有的持久类都应拥有这样的构造函数
在如此方式定义一个多态类的时候,有许多值得注意的常见性的问题,例如:
<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat">
.....
</subclass>
</class>
首先,Cat 实例永远不可以被强制转换为DomesticCat , 即使它本身就是DomesticCat 实例。
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!
....
}
其次,代理的“== ”可能不再成立。
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
System.out.println(cat==dc); // false
虽然如此,但实际情况并没有看上去那么糟糕。虽然我们现在有两个不同的引用,分别指向这两个不同的代理对象, 但实际上,其底层应该是同一个实例对象:
cat.setWeight(11.0); // hit the db to initialize the proxy
System.out.println( dc.getWeight() ); // 11.0
第三,你不能对“final类”或“具有final方法的类”使用CGLIB代理。
最后,如果你的持久化对象在实例化时需要某些资源(例如,在实例化方法、默认构造方法中), 那么代理对象也同样需要使用这些资源。实际上,代理类是持久化类的子类。
这些问题都源于Java的单根继承模型的天生限制。如果你希望避免这些问题,那么你的每个持久化类必须实现一个接口, 在此接口中已经声明了其业务方法。然后,你需要在映射文档中再指定这些接口。例如:
<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>
这里CatImpl 实现了Cat 接口, DomesticCatImpl 实现DomesticCat 接口。 在load() 、iterate() 方法中就会返回 Cat 和DomesticCat 的代理对象。 (注意list() 并不会返回代理对象。)
Cat cat = (Cat) session.load(CatImpl.class, catid);
Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'");
Cat fritz = (Cat) iter.next();
这里,对象之间的关系也将被延迟载入。这就意味着,你应该将属性声明为Cat ,而不是CatImpl 。
但是,在有些方法中是不需要 使用代理的。例如:
*
equals() 方法,如果持久类没有重载equals() 方法。
*
hashCode() 方法,如果持久类没有重载hashCode() 方法。
*
标志符的getter方法。
Hibernate将会识别出那些重载了equals() 、或hashCode() 方法的持久化类。
20.1.4. 实例化集合和代理(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 和结束事务将是非常重要的, Servlet过滤器必须如此访问Session ,才能保证正确使用Session。 我们推荐使用ThreadLocal 变量保存当前的Session (可以参考第 1.4 节 “与Cat同乐” 的例子实现)。
*
在一个拥有单独业务层的应用中,业务层必须在返回之前,为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();
20.1.5. 使用批量抓取(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>
如果整个的batch-size 是3(笔误?),那么Hibernate将会分四次执行SELECT 查询, 按照3、3、3、1的大小分别载入数据。这里的每次载入的数据量还具体依赖于当前Session 中未实例化集合的个数。
如果你的模型中有嵌套的树状结构,例如典型的帐单-原料结构(bill-of-materials pattern),集合的批量抓取是非常有用的。 (尽管在更多情况下对树进行读取时,嵌套集合(nested set) 或原料路径(materialized path) (××) 是更好的解决方法。)
20.1.6. 使用子查询抓取(Using subselect fetching)
假若一个延迟集合或单值代理需要抓取,Hibernate会使用一个subselect重新运行原来的查询,一次性读入所有的实例。这和批量抓取的实现方法是一样的,不会有破碎的加载。
20.1.7. 使用延迟属性抓取(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中通过抓取所有属性 ,强行抓取所有内容。
20.2. 二级缓存(The Second Level Cache)
Hibernate的Session 在事务级别进行持久化数据的缓存操作。 当然,也有可能分别为每个类(或集合),配置集群、或JVM级别(SessionFactory级别 )的缓存。 你甚至可以为之插入一个集群的缓存。注意,缓存永远不知道其他应用程序对持久化仓库(数据库)可能进行的修改 (即使可以将缓存数据设定为定期失效)。
默认情况下,Hibernate使用EHCache进行JVM级别的缓存(目前,Hibernate已经废弃了对JCS的支持,未来版本中将会去掉它)。 你可以通过设置hibernate.cache.provider_class 属性,指定其他的缓存策略, 该缓存策略必须实现org.hibernate.cache.CacheProvider 接口。
表 20.1. 缓存策略提供商(Cache Providers)
Cache Provider class Type Cluster Safe Query Cache Supported
Hashtable (not intended for production use) org.hibernate.cache.HashtableCacheProvider memory yes
EHCache org.hibernate.cache.EhCacheProvider memory, disk yes
OSCache org.hibernate.cache.OSCacheProvider memory, disk yes
SwarmCache org.hibernate.cache.SwarmCacheProvider clustered (ip multicast) yes (clustered invalidation)
JBoss TreeCache org.hibernate.cache.TreeCacheProvider clustered (ip multicast), transactional yes (replication) yes (clock sync req.)
20.2.1. 缓存映射(Cache mappings)
类或者集合映射的“<cache> 元素”可以有下列形式:
<cache
usage="transactional|read-write|nonstrict-read-write|read-only" (1)
/>
(1)
usage 说明了缓存的策略: transactional 、 read-write 、 nonstrict-read-write 或 read-only 。
另外(首选?), 你可以在hibernate.cfg.xml中指定<class-cache> 和 <collection-cache> 元素。
这里的usage 属性指明了缓存并发策略(cache concurrency strategy) 。
20.2.2. 策略:只读缓存(Strategy: read only)
如果你的应用程序只需读取一个持久化类的实例,而无需对其修改, 那么就可以对其进行只读 缓存。这是最简单,也是实用性最好的方法。甚至在集群中,它也能完美地运作。
<class name="eg.Immutable" mutable="false">
<cache usage="read-only"/>
....
</class>
20.2.3. 策略:读/写缓存(Strategy: read/write)
如果应用程序需要更新数据,那么使用读/写缓存 比较合适。 如果应用程序要求“序列化事务”的隔离级别(serializable transaction isolation level),那么就决不能使用这种缓存策略。 如果在JTA环境中使用缓存,你必须指定hibernate.transaction.manager_lookup_class 属性的值, 通过它,Hibernate才能知道该应用程序中JTA的TransactionManager 的具体策略。 在其它环境中,你必须保证在Session.close() 、或Session.disconnect() 调用前, 整个事务已经结束。 如果你想在集群环境中使用此策略,你必须保证底层的缓存实现支持锁定(locking)。Hibernate内置的缓存策略并不支持锁定功能。
<class name="eg.Cat" .... >
<cache usage="read-write"/>
....
<set name="kittens" ... >
<cache usage="read-write"/>
....
</set>
</class>
在项目开发中,对于struts的数据读取,当遇到多个表关联的数据读取的时候,精彩会出现session close的错误,一般的解决方案是将hib的延迟加载错误取消掉,但是这样赶鸭子上架的解决机制无疑是对程序的极度不负责,在这里我们用到 hibernte自带的一种机制,session.load。
例如
notic----user
user----userInfo
三个表,notic a = (notic)session.load(notic,id);
a.getuser()
a.getuser.getuserinfo();
session.transaction.commit();
问题解决。
当然hibernate也有相关的迟缓策略,官方文档如下:
20.1. 抓取策略(Fetching strategies)
抓取策略(fetching strategy) 是指:当应用程序需要在(Hibernate实体对象图的)关联关系间进行导航的时候, Hibernate如何获取关联对象的策略。抓取策略可以在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会区分下列各种情况:
*
Immediate fetching,立即抓取 - 当宿主被加载时,关联、集合或属性被立即抓取。
*
Lazy collection fetching,延迟集合抓取 - 直到应用程序对集合进行了一次操作时,集合才被抓取。(对集合而言这是默认行为。)
*
Proxy fetching,代理抓取 - 对返回单值的关联而言,当其某个方法被调用,而非对其关键字进行get操作时才抓取。
*
Lazy attribute fetching,属性延迟加载 - 对属性或返回单值的关联而言,当其实例变量被访问的时候进行抓取(需要运行时字节码强化)。这一方法很少是必要的。
这里有两个正交的概念:关联何时 被抓取,以及被如何 抓取(会采用什么样的SQL语句)。不要混淆它们!我们使用抓取 来改善性能。我们使用延迟 来定义一些契约,对某特定类的某个脱管的实例,知道有哪些数据是可以使用的。
20.1.1. 操作延迟加载的关联
默认情况下,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读取数据的代码 移到tx.commit()之前。
除此之外,通过对关联映射指定lazy="false" ,我们也可以使用非延迟的集合或关联。但是, 对绝大部分集合来说,更推荐使用延迟方式抓取数据。如果在你的对象模型中定义了太多的非延迟关联,Hibernate最终几乎需要在每个事务中载入整个数据库到内存中!
但是,另一方面,在一些特殊的事务中,我们也经常需要使用到连接抓取(它本身上就是非延迟的),以代替查询抓取。下面我们将会很快明白如何具体的定制Hibernate中的抓取策略。在Hibernate3中,具体选择哪种抓取策略的机制是和选择单值关联或集合关联相一致的。
20.1.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() 方法取得数据。
*
只有在关联之间进行导航时,才会隐式的取得数据(延迟抓取)。
*
条件查询
通常情况下,我们并不使用映射文档进行抓取策略的定制。更多的是,保持其默认值,然后在特定的事务中, 使用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次查询的方法是,使用二级缓存。
20.1.3. 单端关联代理(Single-ended association proxies)
在Hinerbate中,对集合的延迟抓取的采用了自己的实现方法。但是,对于单端关联的延迟抓取,则需要采用 其他不同的机制。单端关联的目标实体必须使用代理,Hihernate在运行期二进制级(通过优异的CGLIB库), 为持久对象实现了延迟载入代理。
默认的,Hibernate3将会为所有的持久对象产生代理(在启动阶段),然后使用他们实现 多对一(many-to-one) 关联和一对一(one-to-one) 关联的延迟抓取。
在映射文件中,可以通过设置proxy 属性为目标class声明一个接口供代理接口使用。 默认的,Hibernate将会使用该类的一个子类。 注意:被代理的类必须实现一个至少包可见的默认构造函数,我们建议所有的持久类都应拥有这样的构造函数
在如此方式定义一个多态类的时候,有许多值得注意的常见性的问题,例如:
<class name="Cat" proxy="Cat">
......
<subclass name="DomesticCat">
.....
</subclass>
</class>
首先,Cat 实例永远不可以被强制转换为DomesticCat , 即使它本身就是DomesticCat 实例。
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!
....
}
其次,代理的“== ”可能不再成立。
Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy
DomesticCat dc =
(DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy!
System.out.println(cat==dc); // false
虽然如此,但实际情况并没有看上去那么糟糕。虽然我们现在有两个不同的引用,分别指向这两个不同的代理对象, 但实际上,其底层应该是同一个实例对象:
cat.setWeight(11.0); // hit the db to initialize the proxy
System.out.println( dc.getWeight() ); // 11.0
第三,你不能对“final类”或“具有final方法的类”使用CGLIB代理。
最后,如果你的持久化对象在实例化时需要某些资源(例如,在实例化方法、默认构造方法中), 那么代理对象也同样需要使用这些资源。实际上,代理类是持久化类的子类。
这些问题都源于Java的单根继承模型的天生限制。如果你希望避免这些问题,那么你的每个持久化类必须实现一个接口, 在此接口中已经声明了其业务方法。然后,你需要在映射文档中再指定这些接口。例如:
<class name="CatImpl" proxy="Cat">
......
<subclass name="DomesticCatImpl" proxy="DomesticCat">
.....
</subclass>
</class>
这里CatImpl 实现了Cat 接口, DomesticCatImpl 实现DomesticCat 接口。 在load() 、iterate() 方法中就会返回 Cat 和DomesticCat 的代理对象。 (注意list() 并不会返回代理对象。)
Cat cat = (Cat) session.load(CatImpl.class, catid);
Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'");
Cat fritz = (Cat) iter.next();
这里,对象之间的关系也将被延迟载入。这就意味着,你应该将属性声明为Cat ,而不是CatImpl 。
但是,在有些方法中是不需要 使用代理的。例如:
*
equals() 方法,如果持久类没有重载equals() 方法。
*
hashCode() 方法,如果持久类没有重载hashCode() 方法。
*
标志符的getter方法。
Hibernate将会识别出那些重载了equals() 、或hashCode() 方法的持久化类。
20.1.4. 实例化集合和代理(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 和结束事务将是非常重要的, Servlet过滤器必须如此访问Session ,才能保证正确使用Session。 我们推荐使用ThreadLocal 变量保存当前的Session (可以参考第 1.4 节 “与Cat同乐” 的例子实现)。
*
在一个拥有单独业务层的应用中,业务层必须在返回之前,为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();
20.1.5. 使用批量抓取(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>
如果整个的batch-size 是3(笔误?),那么Hibernate将会分四次执行SELECT 查询, 按照3、3、3、1的大小分别载入数据。这里的每次载入的数据量还具体依赖于当前Session 中未实例化集合的个数。
如果你的模型中有嵌套的树状结构,例如典型的帐单-原料结构(bill-of-materials pattern),集合的批量抓取是非常有用的。 (尽管在更多情况下对树进行读取时,嵌套集合(nested set) 或原料路径(materialized path) (××) 是更好的解决方法。)
20.1.6. 使用子查询抓取(Using subselect fetching)
假若一个延迟集合或单值代理需要抓取,Hibernate会使用一个subselect重新运行原来的查询,一次性读入所有的实例。这和批量抓取的实现方法是一样的,不会有破碎的加载。
20.1.7. 使用延迟属性抓取(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中通过抓取所有属性 ,强行抓取所有内容。
20.2. 二级缓存(The Second Level Cache)
Hibernate的Session 在事务级别进行持久化数据的缓存操作。 当然,也有可能分别为每个类(或集合),配置集群、或JVM级别(SessionFactory级别 )的缓存。 你甚至可以为之插入一个集群的缓存。注意,缓存永远不知道其他应用程序对持久化仓库(数据库)可能进行的修改 (即使可以将缓存数据设定为定期失效)。
默认情况下,Hibernate使用EHCache进行JVM级别的缓存(目前,Hibernate已经废弃了对JCS的支持,未来版本中将会去掉它)。 你可以通过设置hibernate.cache.provider_class 属性,指定其他的缓存策略, 该缓存策略必须实现org.hibernate.cache.CacheProvider 接口。
表 20.1. 缓存策略提供商(Cache Providers)
Cache Provider class Type Cluster Safe Query Cache Supported
Hashtable (not intended for production use) org.hibernate.cache.HashtableCacheProvider memory yes
EHCache org.hibernate.cache.EhCacheProvider memory, disk yes
OSCache org.hibernate.cache.OSCacheProvider memory, disk yes
SwarmCache org.hibernate.cache.SwarmCacheProvider clustered (ip multicast) yes (clustered invalidation)
JBoss TreeCache org.hibernate.cache.TreeCacheProvider clustered (ip multicast), transactional yes (replication) yes (clock sync req.)
20.2.1. 缓存映射(Cache mappings)
类或者集合映射的“<cache> 元素”可以有下列形式:
<cache
usage="transactional|read-write|nonstrict-read-write|read-only" (1)
/>
(1)
usage 说明了缓存的策略: transactional 、 read-write 、 nonstrict-read-write 或 read-only 。
另外(首选?), 你可以在hibernate.cfg.xml中指定<class-cache> 和 <collection-cache> 元素。
这里的usage 属性指明了缓存并发策略(cache concurrency strategy) 。
20.2.2. 策略:只读缓存(Strategy: read only)
如果你的应用程序只需读取一个持久化类的实例,而无需对其修改, 那么就可以对其进行只读 缓存。这是最简单,也是实用性最好的方法。甚至在集群中,它也能完美地运作。
<class name="eg.Immutable" mutable="false">
<cache usage="read-only"/>
....
</class>
20.2.3. 策略:读/写缓存(Strategy: read/write)
如果应用程序需要更新数据,那么使用读/写缓存 比较合适。 如果应用程序要求“序列化事务”的隔离级别(serializable transaction isolation level),那么就决不能使用这种缓存策略。 如果在JTA环境中使用缓存,你必须指定hibernate.transaction.manager_lookup_class 属性的值, 通过它,Hibernate才能知道该应用程序中JTA的TransactionManager 的具体策略。 在其它环境中,你必须保证在Session.close() 、或Session.disconnect() 调用前, 整个事务已经结束。 如果你想在集群环境中使用此策略,你必须保证底层的缓存实现支持锁定(locking)。Hibernate内置的缓存策略并不支持锁定功能。
<class name="eg.Cat" .... >
<cache usage="read-write"/>
....
<set name="kittens" ... >
<cache usage="read-write"/>
....
</set>
</class>
发表评论
-
JSP处理session与cookie关系(转载)
2011-08-29 16:12 1583为什么登陆后,只要不关 ... -
Hibernate的检索策略
2011-03-29 16:58 928Hibernate的检索策略包括类级别检索策略和关联级别检索策 ... -
转:JSON
2010-03-19 09:45 862转自:http://blog.csdn.net/lovehon ... -
Java Class Path
2010-03-12 14:48 3022我们在系统的环境变量里面添加一个新的变量叫 CLASSPATH ... -
Java reference 值传递还是应用传递的 争论
2010-02-26 17:34 1241转载自:http://dreamhead.blogbus.co ... -
JAVA JS JSP HTML
2010-02-26 13:49 1087应该注意前后台的分类: 什么时候把计算放在前台 用js或是其它 ... -
Firebug JavaScrupt JS 调试
2010-02-04 10:27 1301安装就不用说了,很简单,在FireFox上插件库里 ... -
Out of memory Error:JAVA ;Out of memory ;Tomcat; PermGen space
2010-01-25 18:17 2848PermGen space的全称是Permanent Ge ... -
IE 和 FIREFOX 的不同点
2009-12-23 12:50 0IE 和firefox再web前端的 css效果有很多显示的不 ... -
JAVA 内部类的使用
2009-11-26 11:09 1127JAVA 内部类的简单总结 定义在一个类内部的类叫内部类,包含 ... -
IE 和 firefox的区别
2009-11-19 14:53 10611.document.formName.item(itemNa ... -
FireFox 调试工具 firebug的使用
2009-09-22 15:40 8463如何使用firebug进行调试 2009-06-05 18:3 ... -
Forward 和SendRedirect
2009-09-10 08:50 9711.RequestDispatcher.forward() ... -
Servlet 学习笔记
2009-09-08 20:54 919Java Servlet 开发工具(JSDK)提供了多个软件包 ... -
JAVA Final 关键字的使用
2009-09-07 22:28 737一、final 根据程序上下文环境,Java关键字final ... -
Hibernate 1对多双向关联时候的 argument mismatch 问题
2009-08-17 16:26 971今天在hibernate 关联的时候 无意中把 set 写成H ... -
JNDI 原理 以及JBoss Demo
2009-08-13 11:24 3598摘要: 本文详细介绍了JNDI的架构与实现,JNDI ... -
Tomcat context initializeContext().lookup()参数的含义
2009-08-12 17:39 2380经常看到对于jndi的操作 server.xml < ... -
Hibernate JDBCTransaction JTATransaction
2009-08-12 15:02 1218Hibernate是对JDBC的轻量级对象封装,Hiberna ... -
DAO 泛型设计
2009-08-12 11:22 1102泛型是JDK1.5的一个新的特性,使用泛型机制 ...
相关推荐
3. **集合检索策略(Fetch Strategy)** `fetch`属性用于设置集合检索策略,主要有`LAZY`(默认,懒加载)和`EAGER`(即时加载)。例如,对于一对多或多对多关系,可以设置`fetch="join"`来执行外连接加载,一次性...
`@Id`注解标识主键字段,如`@GeneratedValue(strategy = GenerationType.IDENTITY)`表示自动增长的主键。 3. **属性映射** 使用`@Column`注解将类的属性映射到表的列。例如,`@Column(name = "username")`表示`...
在Java世界中,Hibernate是一个非常流行的ORM(对象关系映射)框架,它允许开发者使用面向对象的方式来操作数据库,极大地简化了数据库操作。本教程将深入探讨如何在Hibernate中使用注解来实现实体类的配置,特别是...
1. **延迟加载(Lazy Loading)**: 使用 `@OneToMany` 和 `@ManyToOne` 注解时,可以通过 `fetch = FetchType.LAZY` 实现关联对象的懒加载,降低初始化时的内存消耗。 2. **批处理(Batch Processing)**: 通过设置 ...
在Hibernate中,可以通过@OneToOne注解来定义这种关系,可以指定fetch属性来控制加载策略,并通过cascade属性设置级联操作。同时,可以使用@PrimaryKeyJoinColumn注解来指定主键的关联。 2. **一对多关联...
2. 利用懒加载(Lazy Loading)和集合的分页加载(Fetch Type)策略,降低内存消耗。 3. 使用二级缓存和查询缓存,减少数据库查询。 八、总结 Hibernate 3.6.7.Final作为一个稳定的版本,为开发者提供了强大且...
- 延迟加载选项:`@Fetch`。 - 获取模式:`@FetchMode`。 - **集合类型**: - 参数注解:`@ElementCollection`、`@MapKey`等。 - **缓存**:`@Cache`。 - **过滤器**:`@Filter`。 - **查询**:`@NamedQuery`、`@...
19.1.2. 调整抓取策略(Tuning fetch strategies) 19.1.3. 单端关联代理(Single-ended association proxies) 19.1.4. 实例化集合和代理(Initializing collections and proxies) 19.1.5. 使用批量抓取(Using...
Hibernate支持单表继承(@Inheritance(strategy=InheritanceType.SINGLE_TABLE))、联合继承(@Inheritance(strategy=InheritanceType.JOINED))和表-per-hierarchy继承(@Inheritance(strategy=InheritanceType....
@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY) private List<Child> children; } ``` 在这个例子中,`children`属性被...
若希望关联对象在加载主对象时一并加载,可以使用`fetch = FetchType.EAGER`。 3. **外键维护**:在双向关联中,通常只有一方负责维护外键,这里由UserProfile负责,因此在修改User对象的UserProfile时,应通过User...
若希望实现懒加载,可以在继承关系中使用`@OneToMany`或`@ManyToOne`注解并设置`fetch = FetchType.LAZY`。 7. **性能考虑** 不同的继承策略对数据库性能和设计有不同影响。STI可能导致表膨胀,而TPC可能导致更多...
- **2.2.5.5 关联关系获取**:可以通过 `@Fetch` 注解来优化关联关系的加载策略,提高查询性能。 - **2.2.6 映射复合主键与外键** - 使用 `@CompositeId` 和 `@JoinColumns` 来定义复杂的主键和外键映射。 - **...
在Java的持久化框架Hibernate中,数据访问优化是至关重要的,而抓取策略(Fetch Strategy)和懒加载(Lazy Loading)则是实现这一目标的关键技术。本文将深入探讨这两个概念,并通过具体的案例进行分析。 首先,让...
@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // ... } ``` 5. **属性映射注解(@Column)** `@Column`注解用于将类的属性映射到数据库表的列,可以指定列名、长度、是否允许为空等...
19.1.2. 调整抓取策略(Tuning fetch strategies) 19.1.3. 单端关联代理(Single-ended association proxies) 19.1.4. 实例化集合和代理(Initializing collections and proxies) 19.1.5. 使用批量抓取(Using...
19.1.2. 调整抓取策略(Tuning fetch strategies) 19.1.3. 单端关联代理(Single-ended association proxies) 19.1.4. 实例化集合和代理(Initializing collections and proxies) 19.1.5. 使用批量抓取(Using...
@GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; ``` 4. `@GeneratedValue`: 配合@Id使用,指定主键生成策略,如IDENTITY(自增)、SEQUENCE(序列)等。 5. `@Column`: 定义字段在数据库...
这可以通过调整fetch策略或者使用Hibernate的批处理功能来优化。 - 对于一对一关联,应确保两个实体间的关联是互斥的,避免出现循环引用导致的死锁问题。 - 在实际开发中,还需要考虑事务管理,确保数据的一致性。 ...