??xml version="1.0" encoding="utf-8" standalone="yes"?>
一?/span>hibernate抓取{略Q单端代理的扚w抓取fetch=select(默认Q?/span>/joinQ?/span>
试用例Q?/span>
Student student = (Student)session.get(Student.class, 1);
System.out.println(student.getName());
System.out.println(student.getClasses().getName());
1Q保?/span>默认Q同fetch="select",如:
<many-to-one name="classes" column="classesid" fetch="select"/>
fetch="select",另外发送一?/span>select语句抓取当前对象兌实体或集?/span>
执行l果Q?/span>2条语?/span>
Hibernate: select student0_.id as id1_0_, student0_.name as name1_0_, student0_.class_id as class3_1_0_ from student_join student0_ where student0_.id=?
学生1
Hibernate: select classes0_.id as id0_0_, classes0_.name as name0_0_ from classes_join classes0_ where classes0_.id=?
高一(1)?/span>
======================================
2Q设|?/span>fetch="join",如:
<many-to-one name="classes" column="classesid" fetch="join"/>
fetch="join",hibernate会通过select语句使用外连接来加蝲其关联实体或集合
此时lazy会失?/span>
执行l果Q一?/span>join语句
Hibernate: select student0_.id as id1_1_, student0_.name as name1_1_, student0_.class_id as class3_1_1_, classes1_.id as id0_0_, classes1_.name as name0_0_ from student_join student0_ left outer join classes_join classes1_ on student0_.class_id=classes1_.id where student0_.id=?
学生1
高一(1)?/span>
======================================================
二?/span>hibernate抓取{略Q集合代理的扚w抓取Q?/span>fetch=selectQ默认)/join/subselectQ?/span>
试用例Q?/span>
Classes c = (Classes) session.load(Classes.class, new Integer(1));
System.out.println("Class.name=" + c.getName());
Set stuSet = c.getStudents();
System.out.println(stuSet.size());
if(stuSet != null && !stuSet.isEmpty()){
for(Iterator it = stuSet.iterator(); it.hasNext();){
Student s = (Student) it.next();
System.out.println("student.name=" + s.getName());
}
}
1Q保持默认,?/span>fetch="select",如:
<set name="students" inverse="true" fetch="select">
fetch="select",另外发送一?/span>select语句抓取当前对象兌实体或集?/span>
试l果Q?/span>2条独立的查询语句
Hibernate: select classes0_.id as id0_0_, classes0_.name as name0_0_ from classes_join classes0_ where classes0_.id=?
Class.name=高一(1)?/span>
Hibernate: select students0_.class_id as class3_1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.class_id as class3_1_0_ from student_join students0_ where students0_.class_id=?
9
student.name=学生7
student.name=学生3
student.name=学生1
student.name=学生8
student.name=学生2
student.name=学生4
student.name=学生5
student.name=学生9
student.name=学生6
(2)讄fetch="join",如:
<set name="students" inverse="true" fetch="join">
fetch="join",hibernate会通过select语句使用外连接来加蝲其关联实体或集合
此时lazy会失?/span>
试l果Q?/span>1条独立的join查询语句
Hibernate: select classes0_.id as id0_1_, classes0_.name as name0_1_, students1_.class_id as class3_3_, students1_.id as id3_, students1_.id as id1_0_, students1_.name as name1_0_, students1_.class_id as class3_1_0_ from classes_join classes0_ left outer join student_join students1_ on classes0_.id=students1_.class_id where classes0_.id=?
Class.name=高一(1)?/span>
9
student.name=学生6
student.name=学生4
student.name=学生9
student.name=学生7
student.name=学生2
student.name=学生3
student.name=学生8
student.name=学生1
student.name=学生5
(3)讄fetch="subselect",如:用在查询语句?/span>
<set name="students" inverse="true" fetch="subselect">
fetch="subselect",另外发送一?/span>select语句抓取在前面查询到的所有实体对象的兌集合
试用例Q?/span>
List classList = session.createQuery("from Classes where id in (1,2,3)").list();
for(Iterator iter = classList.iterator(); iter.hasNext();){
Classes c = (Classes)iter.next();
System.out.println("Class.name=" + c.getName());
Set stuSet = c.getStudents();
System.out.println(stuSet.size());
if(stuSet != null && !stuSet.isEmpty()){
for(Iterator it = stuSet.iterator(); it.hasNext();){
Student s = (Student) it.next();
System.out.println("student.name=" + s.getName());
}
}
}
当不?/span>fetch="subselect" ,卻I<set name="students" inverse="true">,l果如下Q?/span>
执行?/span>3条查询语?/span>
Hibernate: select classes0_.id as id0_, classes0_.name as name0_ from classes_join classes0_ where classes0_.id in (1 , 2 , 3)
Class.name=高一(1)?/span>
Hibernate: select students0_.class_id as class3_1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.class_id as class3_1_0_ from student_join students0_ where students0_.class_id=?
9
student.name=学生8
student.name=学生5
student.name=学生3
student.name=学生9
student.name=学生7
student.name=学生1
student.name=学生4
student.name=学生6
student.name=学生2
Class.name=高一(2)?/span>
Hibernate: select students0_.class_id as class3_1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.class_id as class3_1_0_ from student_join students0_ where students0_.class_id=?
4
student.name=学生3
student.name=学生4
student.name=学生1
student.name=学生2
Class.name=高一(3)?/span>
Hibernate: select students0_.class_id as class3_1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.class_id as class3_1_0_ from student_join students0_ where students0_.class_id=?
0
当不?/span>fetch="subselect" ,卻I<set name="students" inverse="true" fetch="subselect">,l果如下Q?/span>
执行?/span>1条查询语句(嵌套子查询)
Hibernate: select classes0_.id as id0_, classes0_.name as name0_ from classes_join classes0_ where classes0_.id in (1 , 2 , 3)
Class.name=高一(1)?/span>
Hibernate: select students0_.class_id as class3_1_, students0_.id as id1_, students0_.id as id1_0_, students0_.name as name1_0_, students0_.class_id as class3_1_0_ from student_join students0_ where students0_.class_id in (select classes0_.id from classes_join classes0_ where classes0_.id in (1 , 2 , 3))
9
student.name=学生8
student.name=学生4
student.name=学生5
student.name=学生9
student.name=学生6
student.name=学生2
student.name=学生3
student.name=学生1
student.name=学生7
Class.name=高一(2)?/span>
4
student.name=学生3
student.name=学生4
student.name=学生2
student.name=学生1
Class.name=高一(3)?/span>
0
1.量使用many-to-oneQ避免用单one-to-many
2.灉|使用单向one-to-many
3.不用一对一Q用多对一代替一对一
4.配置对象~存Q不使用集合~存
5.一对多使用Bag 多对一使用Set
6.l承使用昄多?nbsp; HQL:from object polymorphism="exlicit" 避免查处所有对?br />7.消除大表Q用二U缓?br /> 对于上面q些QRobbinq行了详l的讲解?br />one-to-manyQ?/strong>
使用inverse=false(default)Q对象的兌关系是由parent对象来维护的
而inverse=true的情况下Q一般用户双向多对多兌Q由子对象维护关联关p,增加子对象的时候需要显C:child.setParent(child)
Z提高性能Q应该尽量用双向one-to-many inverse=trueQ在MVCl构中的DAO接口中应该直接用Session持久化对象,避免通过兌关系Q这句话有点不理解)Q而在单项关系中正用二U缓存,则可以大q提高以查询Z的应用?br /> 多对一性能问题比较,但是要避免经典N+1问题?br /> 通过主键q行兌Q相当于大表拆分表。(q个是区分面向对象设计和面向q程设计的一个关键点Q?br />list、bag、set的正运?/strong>
one-to-manyQ?br /> A、用list 需要维护Index Column字段Q不能被用于双向兌Q而且必须使用inverse=falseQ需要}慎用在某些E有场合(基本上是不予考虑使用Q?br /> B、bag/set在one-to-many中语义基本相同,推荐使用bag
many-to-oneQ?br /> A、bag和set不同Qbag允许重复插入Q徏议用set
在庞大的集合分页中应该用session.createFilter
session.createFilter(parent.getChildren(),""),setFirstResult(0),setMaxResult(10))
避免N+1 参考(http://www.iteye.com/post/266972Q?/strong>
在多对一的情况下Q查询child对象Q当在页面上昄每个子类的父cd象的时候会DN+1ơ查询,需要采用下面的Ҏ避免Qmany-to-one fetch="join|select"Q该Ҏ可能有问题)
inverse=true 无法l护集合~存Q还不是很理解集合缓存和对象~存Q?/strong>
OLTPcd的web应用Q可以群集水qx展,不可避免的出现数据库瓉
框架能降低访问数据库的压力,采用~存是衡量一个框架是否优U的重要标准,从缓存方面看Hibernate
A、对象缓存,l颗_度Q是针对表的U别Q透明化访问,因ؓ有不改变代码的好处,所以是ORM提高性能的法?br /> B、Hibernate是目前ORM框架中缓存性能最好的框架
C、查询缓?br />最后Robbinq针对大家经常出现的Hibernate vs iBatis的讨行了一个ȝQ?br /> 对于OLTP应用Q用ORM框架 而OLEB应用Q不定是什么应用)最好采用JDBC或者其他方法处?br /> Hibernate們于细颗粒度设计,面向对象Q将大表拆分为多个小表,消除冗余字段Q通过二~存提升性能?br /> iBatis們于粗颗粒度设计,面向关系Q尽量把表合qӞ通过Column冗余Q消除关联关p,但是iBatis没有有效的缓存手Dc?/span>
可以说Robbin的性能ȝ对于使用Hibernate的开发h员有着很重要的Ҏ作用。非常感谢他无私奉献自己的经验?/span>
HQL看v来和SQL很相伹{从HQL的WHERE子句中通常可以猜到相应的SQL WHERE子句。WHERE子句中的字段军_了数据库选择的烦引?/p>
大多数Hibernate开发者所常犯的一个错误是无论何时Q当需要新WHERE子句的时候都会创Z个新的烦引。因为烦引会带来额外的数据更新开销Q所以应该争取创建少量烦引来覆盖可能多的查询?br />4.1?/strong>让你使用一个集合来处理所有可能的数据搜烦条g。如果这不太实际Q那么你可以使用后端剖析工具来创Z个针对应用程序涉及的所有SQL的集合。基于那些搜索条件的分类Q你最l会得到一个小的烦引集。与此同Ӟq可以尝试向WHERE子句中添加额外的谓语来匹配其他WHERE子句?/p>
范例7 有两个UI搜烦器和一个后端守护进E搜索器来搜索名为iso_deals的表。第一个UI搜烦器在unexpectedFlag、dealStatus、tradeDate和isold属性上有谓语?/p>
W二个UI搜烦器基于用户键入的qo器,其中包括的内定wtradeDate和isold以外q有其他属性。开始时所有这些过滤器属性都是可选的?br />后端搜烦器基于isold、participantCode和transactionType属性?br />l过q一步业务分析,发现W二个UI搜烦器实际是Z一些隐式的unexpectedFlag和dealStatus值来选择数据的。我们还让tradeDate成ؓqo器的必要属性(Z使用数据库烦引,每个搜烦qo器都应该有必要属性)?/p>
鉴于q一点,我们依次使用unexpectedFlag、dealStatus、tradeDate和isold构造了一个复合烦引。两个UI搜烦器都能共用它。(序很重要,如果你的谓语以不同的序指定q些属性或在它们前|列了其他属性,数据库就不会选择该复合烦引。) 后端搜烦器和UI搜烦器区别太大,因此我们不得不ؓ它构造另一个复合烦引,依次使用isold、participantCode和transactionType?/p> 既可以用绑定参数构造HQL的WHERE子句Q也可以使用字符串拼接的ҎQ该军_Ҏ能会有一定媄响。用绑定参数的原因是让数据库一ơ解析SQLQ对后箋的重复请求复用生成好的执行计划,q样做节省了CPU旉和内存。然而,到最优的数据讉K效率Q不同的l定值可能需要不同的SQL执行计划?/p>
例如Q一段数据范围可能只返回数据总量?%Q而一大段数据范围可能q回数据总量?0%。前者用烦引更好,而后者则最好用全表扫描?/p>
OLTP使用l定参数Q数据仓库用字W串拼接Q因为OLTP通常在一个事务中重复插入和更新数据,只取量数据Q数据仓库通常只有量SQL查询Q有一个确定的执行计划比节省CPU旉和内存更为重要?/p>
要是你知道你的OLTP搜烦对不同绑定值应该用相同执行计划又该怎么办呢Q?/p>
Oracle 9i及以后版本在W一ơ调用绑定参数ƈ生成执行计划时能探出参数倹{后l调用不会再探测Q而是重用之前的执行计划?/p>
你可以在数据库中q行聚合?#8220;order by”Q也可以在应用程序的服务层中事先加蝲所有数据然后做聚合?#8220;order by”操作。推荐用前者,因ؓ数据库在q方面通常会比你的应用E序做得好。此外,q样做还能节省网l带宽,q也是一U拥有跨数据库移植性的做法?/p>
当你的应用程序对数据聚合和排序有HQL不支持的特定业务规则旉外?/p>
详见4.7.1?/strong>?/p>
本地查询调优其实q不直接与HQL有关。但HQL的确可以让你直接向底层数据库传递本地查询。我们ƈ不徏议这么做Q因为本地查询在数据库间不可UL?/p>
抓取{略军_了在应用E序需要访问关联对象时QHibernate以何U方式以及何时获取关联对象。HRD中的W?0?#8220;改善性能”对该主题作了很好的阐qͼ我们在此关注它的用方法?/p>
不同的用户可能会有不同的数据抓取要求。Hibernate允许在两个地方定义数据抓取策略,一处是在映元数据中,另一处是在HQL或Criteria中覆盖它?/p>
常见的做法是Z主要的抓取用例在映射元数据中定义默认抓取{略Q针对少数用例在HQL和Criteria中覆盖抓取策略?/p>
假设pojoA和pojoB是父子关pd例。如果根据业务规则,只是偶尔需要从实体两端加蝲数据Q那你可以声明一个gq加载集合或代理抓取Qproxy fetchingQ。当你需要从实体两端获取数据Ӟ可以用立x取(eager fetchingQ覆盖默认策略,例如使用HQL或Criteria配置q接抓取Qjoin fetchingQ?/p>
另一斚wQ如果业务规则在大多数时候需要从实体两端加蝲数据Q那么你可以声明立即抓取q在Criteria中设|gq加载集合或代理抓取来覆盖它QHQL目前q不支持q样的覆盖)?/p>
select抓取会导致N+1问题。如果你知道自己L需要从兌中加载数据,那么p始终使用q接抓取。在下面两个场景中,你可能会把N+1视ؓ一U模式而非反模式?/p>
W一U场景,你不知道用户是否会访问关联对象。如果他/Ҏ有访问,那么你赢了;否则你仍焉要额外的Nơselect SQL语句。这是一Uo人左右ؓ隄局面?/p>
W二U场景,pojoA和很多其他POJO有one-to-many兌Q例如pojoB和pojoC。用立即的内连接或外连接抓取会在结果集中将pojoA重复很多ơ。当pojoA中有很多非空属性时Q你不得不将大量数据加蝲到持久层中。这U加载需要很多时_既有|络带宽的原因,如果Hibernate的会话是有状态的Q其中也会有会话~存的原因(内存消耗和GC暂停Q?/p>
如果你有一个很长的one-to-many兌链,例如从pojoA到pojoB到pojoC以此cLQ情况也是类似的?/p>
你也怼M用HQL中的DISTINCT关键字或Cirteria中的distinct功能或是Java的Set接口来消除重复数据。但所有这些都是在HibernateQ在持久层)中实现的Q而非数据库中?/p>
如果Z你的|络和内存配|的试表明N+1性能更好Q那么你可以使用扚w抓取、subselect抓取或二U缓存来做进一步调优?/p>
范例8 以下是一个用批量抓取的HBM文g片段Q?/p> 以下是多端pojoB生成的SQLQ?/p> 问号数量与batch-size值相{。因此Nơ额外的关于pojoB的select SQL语句被减到了N/10ơ?/p>
如果?small>fetch="select"替换?small>fetch="subselect"QpojoB生成的SQL语句是q样的: 管Nơ额外的select减少?ơ,但这只在重复q行pojoA的查询开销很低时才有好处?/p>
如果pojoA中的pojoB集合很稳定,或pojoB有pojoA的many-to-one兌Q而且pojoA是只d用数据,那么你可以用二U缓存来~存pojoA以消除N+1问题Q?strong>4.8.1?/strong>中有一个例子)?/p> 除非有一张拥有很多你不需要的字段的遗留表Q否则不应该使用q种抓取{略Q因为它的gq属性分l会带来额外的SQL?/p>
在业务分析和设计q程中,你应该将不同数据获取或修改分l放C同的领域对象实体中,而不是用这U抓取策略?/p>
如果不能重新设计遗留表,可以使用HQL或Criteria提供的投影功能来获取数据?/p>
HRDW?0.2?“二~存”中的描述对大多数开发者来说过于简单,无法做出选择?.3版及以后版本不再推荐使用Z“CacheProvider”的缓存,而用Z“RegionFactory”的缓存,q也让h更糊涂了。但是就是最新的3.5参考文档也没有提及如何使用新缓存方法?/p>
Z下述考虑Q我们将l箋x于老方法: 理解该机制是做出合理选择的关键。关键的c?接口是CacheConcurrencyStrategy和它针对4中不同缓存用的实现c,q有EntityUpdate/Delete/InsertAction?/p>
针对q发~存讉KQ有三种实现模式Q?/p>
无论是锁q是事务都没影响Q因为缓存自数据从数据库加蝲后就不会改变?/p> 对缓存的更新发生在数据库事务完成后。缓存需要支持锁?/p> 对缓存和数据库的更新被包装在同一个JTA事务中,q样~存与数据库L保持同步的。数据库和缓存都必须支持JTA。尽缓存事务内部依赖于~存锁,但Hibernate不会昑ּ调用M的缓存锁函数?/p> 以数据库更新Z。EntityUpdateAction对于事务感知d?#8220;read-write”的非事务感知dQ还?#8220;nonstrict-read-write”的非事务感知d相应有如下调用序列: 软锁只是一U特定的~存值失效表q方式,在它获得新数据库值前L其他事务d~存。那些事务会转而直接读取数据库?/p>
~存必须支持锁;事务支持则不是必ȝ。如果缓存是一个集,“更新~存”的调用会新值推送给所有副本,q通常被称?#8220;推(pushQ?#8221;更新{略?/p> 既不需要支持缓存锁Q也不需要支持事务。如果是~存集群Q?#8220;清除~存”调用会让所有副本都失效Q这通常被称?#8220;拉(pullQ?#8221;更新{略?/p> 对于实体的删除或插入动作Q或者集合变_调用序列都是怼的?/p>
实际上,最后两个异步调用序列仍能保证数据库和缓存的一致性(基本是“read committed”的隔MU别Q,q要归功于第二个序列中的软锁?#8220;更新数据?#8221;后的“更新~存”Q还有最后一个调用序列中的悲?#8220;清除~存”?/p>
Z上述分析Q我们的是: 依笔者看来,二~存q一U数据源Q因此用JTA也未必合理。实际上最后两个调用序列在大多数场景下是个不错的替代方案,q要归功于它们的数据一致性保障?/p> 范例9 以下是一个ISO收费cd的HBM文g片段Q?/p> 一些用户只需要ISO收费cd本nQ一些用h需要ISO收费cdQ还需要它的三个关联对象。简单v见,开发者会立即加蝲所有三个关联对象。如果项目中没h负责Hibernate调优Q这是很常见的?/p>
4.7.1?/strong>中讲q了最好的Ҏ。因为所有的兌对象都是只读引用数据Q另一U方法是使用延迟抓取Q打开q些对象的二U缓存以避免N+1问题。实际上前一U方法也能从引用数据~存中获益?/p>
因ؓ大多数项目都有很多被其他数据引用的只d用数据,上述两种Ҏ都能改善全局pȝ性能?/p> 下表是新老两U方法中对应的主要类/接口Q?
新方?/strong> 老方?/strong> RegionFactory CacheProvider Region Cache EntityRegionAccessStrategy CacheConcurrencyStrategy CollectionRegionAccessStrategy CacheConcurrencyStrategy W一个改q是RegionFactory构徏了特定的RegionQ例如EntityRegion和TransactionRegionQ而不是用一个通用的访问Region。第二个改进是对于特定缓存的“usage”属性|Region要求构徏自己的访问策略,而不是所有Region都一直用CacheConcurrencyStrategy?U实现?/p>
要用新ҎQ应该设|factory_class而非provider_class配置属性。以Ehcache 2.0ZQ?/p> 其他相关的Hibernate~存配置都和老方法一栗?/p>
新方法也能向后兼定w留方法。如果还是只配了CacheProviderQ新Ҏ中将使用下列自说明(self-explanatoryQ适配器和桥隐式地调用老的接口/c: RegionFactoryCacheProviderBridge、EntityRegionAdapter、CollectionRegionAdapter、QueryResultsRegionAdapter、EntityAccessStrategyAdapter和CollectionAccessStrategyAdapter 二~存也能~存查询l果。如果查询开销很大而且要重复运行,q也会很有帮助?/p>
大多数Hibernate的功能都很适合那些每个事务都通常只处理少量数据的OLTPpȝ。但是,如果你有一个数据仓库或者事务需要处理大量数据,那么另当别Z?/p>
如果你已l在使用常规会话了,那这是最自然的方法。你需要做三g事: batch_size讄为正g开启JDBC2的批量更斎ͼHibernate的徏议值是5?0。基于我们的试Q极低值和极高值性能都很差。只要取值在合理范围内,区别只有几U而已。如果网l够快,q个l果是一定的?/p>
W二个配|设为trueQ这要求JDBC驱动在executeBatch()Ҏ中返回正的行数。对于Oracle用户而言Q批量更新时不能其设ؓtrue。请阅读Oracle的《JDBC Developer’s Guide and Reference》中?#8220;标准批处理的Oracle实现中的更新计数”Q?a >Update Counts in the Oracle Implementation of Standard BatchingQ以获得更多详细信息。因为它Ҏ量插入来说还是安全的Q所以你可以为批量插入创建单独的专用数据源。最后一个配|项是可选的Q因Z可以在会话中昑ּ关闭二~存?/p> for ( int i=0; i<100000; i++ ) { 批处理通常不需要数据缓存,否则你会内存耗尽q大量增加GC开销。如果内存有限,那这U情况会很明显?/p> 每次事务修改的对象数量越就意味着会有更多数据库提交,正如4.5?/strong>所q每ơ提交都会带来磁盘相关的开销?/p>
另一斚wQ每ơ事务修改的对象数量多意味着锁定变更旉长Q同时数据库需要更大的redo log?/p>
无状态会话执行v来比上一U方法更好,因ؓ它只是JDBC的简单包装,而且可以l开很多常规会话要求的操作。例如,它不需要会话缓存,也不和Q何二U缓存或查询~存有交互?br />然而它的用法ƈ不简单。尤其是它的操作q不会联到所兌的实例上Q你必须自己来处理它们?/p>
使用DML风格的插入、更新或删除Q你直接在数据库中操作数据,q和前两U方法在Hibernate中操作数据的情况有所不同?/p>
因ؓ一个DML风格的更新或删除相当于前两种Ҏ中的多个单独的更新或删除Q所以如果更新或删除中的WHERE子句暗示了恰当的数据库烦引,那么使用DML风格的操作能节省|络开销Q执行得更好?/p>
强烈l合使用DML风格操作和无状态会话。如果用有状态会话,不要忘记在执行DML前清除缓存,否则Hibernate会更新或清除相关缓存(见下面的范例10Q?/p>
如果你的HQL或Criteria会返回很多数据,那么要注意两件事Q?/p>
fetch_size讄为正值将开启JDBC扚w抓取Ҏ。相对快速网l,在慢速网l中q一Ҏ为重要。Oracle的经验值是10。你应该Z自己的环境进行测试?/p> 范例10 我们有一个后CQ务,分段加蝲大量的IsoDeal数据用于后箋处理。我们还会在分段数据交给下游pȝ处理前将其更Cؓ处理中状态。最大的一D|50万行数据。以下是原始代码中截取出来的一D: 包含上述代码的方法加上了Spring 2.5声明式事务的注解。加载ƈ更新50万行数据大约׃10分钟。我们识别出了以下这些问题: 不幸的是Spring 2.5不支持Hibernate无状态会话,所以我们只能关闭二U缓存;讄fetch_sizeQ用DML风格的更新来代替for循环Q以此改善性能?/p>
但是Q执行时间还是要6分钟。将Hibernate的日志别调成trace后,我们发现是更C话缓存造成了g时。通过在DML更新前清除会话缓存,我们时间羃短到?分钟Q全部都是将数据加蝲C话缓存中p的时间?/p> 本节向你展C如何减SQL生成的数量?/p>
“select抓取”{略会导致N+1问题。如?#8220;q接抓取”{略适合你的话,你应该始l用该{略避免N+1问题?/p>
但是Q如?#8220;q接抓取”{略执行效果不理惻I像4.7.2?/strong>中那P你可以?#8220;subselect抓取”?#8220;扚w抓取”?#8220;延迟集合抓取”来减所需的额外SQL语句数?/p>
范例11 我们的ElectricityDeal与DealCharge有单向one-to-many兌Q如下列HBM文g片段所C: ?#8220;key”元素中,“not-null”?#8220;update”对应的默认值是false和trueQ上qC码ؓ了明这些取|它们写了出来?/p>
如果你想创徏一个ElectricityDeal和十个DealChargeQ会生成如下SQL语句Q?/p>
Z消除那额外的10句更新语句,可以在那10句DealCharge插入语句中包?#8220;DEAL_KEY”Q你需要将“not-null”?#8220;update”分别修改为true和false?/p>
另一U做法是使用双向或many-to-one兌Q让DealCharge来管理关联?/p>
在范?1中,我们为ElectricityDeal加上了select-before-updateQ这会对瞬时QtransientQ对象或分离QdetachedQ对象生额外的select语句Q但却能避免不必要的数据库更新?/p>
你应该做Z些权衡,如果对象没多属性,不需要防止不必要的数据库更新Q那么就不要使用该特性,因ؓ你那些有限的数据既没有太多网l传输开销Q也不会带来太多数据库更新开销?/p>
如果对象的属性较多,例如是一张大的遗留表Q那你应该开启该Ҏ,?#8220;dynamic-update”l合使用以避免太多数据库更新开销?/p>
在范?1中,如果你想删除1个ElectricityDeal和它?00个DealChargeQHibernate会对DealCharge?00ơ删除?/p>
如果?#8220;on-delete”修改?#8220;cascade”QHibernate不会执行DealCharge的删除动作;而是让数据库ҎON CASCADE DELETEU束自动删除?00个DealCharge。不q,需要让DBA开启ON CASCADE DELETEU束Q大多数DBA不愿意这么做Q因Z们想避免父对象的意外删除U联到它的依赖对象上。此外,q要注意Q该Ҏ会l过Hibernate对版本数据(versioned dataQ的常用乐观锁策略?/p>
范例11中用Oracle的序列作为标识符生成器。假设我们保?00个ElectricityDealQHibernate会将下面的SQL语句执行100ơ来获取下一个可用的标识W: 如果|络不是很快Q那q无疑会降低效率?.2.3及后l版本中增加了一个增强的生成?#8220;SequenceStyleGenerator”Q它带了两个优化器:hilo和pooled。尽HRD?a >W??#8220;基础O/R映射” 讲到了这两个优化器,不过内容有限。两个优化器都用了HiLo法Q该法生成的标识符{于Hi值加上Lo|其中Hig表组PLo值顺序且重复C1q代到最大组大小Q组号在Lo?#8220;转回?#8221;1时加1?/p>
假设l大是5Q可以用max_lo或increment_size参数来表C)Q下面是个例子: l号取自数据库序列的下一个可用|Hi值由Hibernate定义Q是l号乘以increment_size参数倹{?/p> Hi值直接取自数据库序列的下一个可用倹{数据库序列的增量应该设|ؓincrement_size参数倹{?/p> 直到内存l中的D尽后,两个优化器才会去讉K数据库,上面的例子每5个标识值符讉K一ơ数据库。用hilo优化器时Q你的序列不能再被其他应用程序用,除非它们使用与Hibernate相同的逻辑。用pooled优化器,在其他应用程序用同一序列时则相当安全?/p>
两个优化器都有一个问题,如果Hibernate崩溃Q当前组内的一些标识符值就会丢失,然而大多数应用E序都不要求拥有q箋的标识符|如果你的数据库,比方说OracleQ缓存了序列|当它崩溃时你也会丢失标识W|?/p>
如果在范?1中用pooled优化器,新的id配置如下Q?/p> 本文늛了大多数你在Hibernate应用E序调优时会觉得很有用的调优技巧,其中的大多数旉都在讨论那些行之有效却缺乏文的调优主题Q例如承映、二U缓存和增强的序列标识符生成器?/p>
它还提到了一些Hibernate调优所必需的数据库知识。一些范例中包含了你可能遇到的问题的实际解决Ҏ?/p>
除此之外Q值得一提的是Hibernate也可以和In-Memory Data GridQIMDGQ一起用,例如Oracle的Coherance或GigaSpaces IMDGQ这能让你的应用E序辑ֈ毫秒U别?/p>
[1] Latest Hibernate Reference Documentation on jboss.com [2] Oracle 9i Performance Tuning Guide and Reference [3] Performance Engineering on Wikipedia [4] Program Optimization on Wikipedia [5] Pareto Principle (the 80/20 rule) on Wikipedia [6] Premature Optimization on acm.org [7] Java Performance Tuning by Jack Shirazi [8] The Law of Leaky Abstractions by Joel Spolsky [9] Hibernate’s StatisticsService Mbean configuration with Spring [11] Java VisualVM [12] Column-oriented DBMS on Wikipedia [13] Apache DBCP BasicDataSource [14] JDBC Connection Pool by Oracle [15] Connection Failover by Oracle [16] Last Resource Commit Optimization (LRCO) [17] GigaSpaces for Hibernate ORM Users
4.6.2l定参数 vs.字符串拼?/h4>
4.6.3聚合及排?/h4>
4.6.4覆盖抓取{略
4.6.5本地查询
4.7抓取{略调优
4.7.1覆盖抓取{略
4.7.2 N+1模式或是反模式?
<class name="pojoA" table="pojoA">
…
<set name="pojoBs" fetch="select" batch-size="10">
<key column="pojoa_id"/>
…
</set>
</class>
select … from pojoB where pojoa_id in(?,?,?,?,?, ?,?,?,?,?);
select … from pojoB where pojoa_id in(select id from pojoA where …);
4.7.3延迟属性抓?/h4>
4.8 二~存调优
4.8.1 ZCacheProvider的缓存机?/h4>
<class name="IsoChargeType">
<property name="isoId" column="ISO_ID" not-null="true"/>
<many-to-one name="estimateMethod" fetch="join" lazy="false"/>
<many-to-one name="allocationMethod" fetch="join" lazy="false"/>
<many-to-one name="chargeTypeCategory" fetch="join" lazy="false"/>
</class>
4.8.2 RegionFactory
<property name="hibernate.cache.region.factory_class">
net.sf.ehcache.hibernate.EhCacheRegionFactory
</property>
4.8.3 查询~存
4.9扚w处理调优
4.9.1使用有状态会话的非DML风格批处?/h5>
hibernate.jdbc.batch_size 30
hibernate.jdbc.batch_versioned_data true
hibernate.cache.use_second_level_cache false
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Customer customer = new Customer(.....);
//if your hibernate.cache.use_second_level_cache is true, call the following:
session.setCacheMode(CacheMode.IGNORE);
session.save(customer);
if (i % 50 == 0) { //50, same as the JDBC batch size
//flush a batch of inserts and release memory:
session.flush();
session.clear();
}
}
tx.commit();
session.close();4.9.2使用无状态会话的非DML风格批处?/h4>
4.9.3 DML风格
4.9.4扚w加蝲
hibernate.jdbc.fetch_size 10
Query query = session.createQuery("FROM IsoDeal d WHERE chunk-clause");
query.setLockMode("d", LockMode.UPGRADE); //for Inprocess status update
List<IsoDeal> isoDeals = query.list();
for (IsoDeal isoDeal : isoDeals) { //update status to Inprocess
isoDeal.setStatus("Inprocess");
}
return isoDeals;
4.10 SQL生成调优
4.10.1 N+1抓取问题
4.10.2 Insert+Update问题
<class name="ElectricityDeal"
select-before-update="true" dynamic-update="true"
dynamic-insert="true">
<id name="key" column="ID">
<generator class="sequence">
<param name="sequence">SEQ_ELECTRICITY_DEALS</param>
</generator>
</id>
…
<set name="dealCharges" cascade="all-delete-orphan">
<key column="DEAL_KEY" not-null="false" update="true"
on-delete="noaction"/>
<one-to-many class="DealCharge"/>
</set> </class> 4.10.3 更新前执行select
4.10.4 U联删除
4.10.5 增强的序列标识符生成?/h4>
select SEQ_ELECTRICITY_DEALS.NEXTVAL from dual;
<id name="key" column="ID">
<generator class="org.hibernate.id.enhance.SequenceStyleGenerator">
<param name="sequence_name">SEQ_ELECTRICITY_DEALS</param>
<param name="initial_value">0</param>
<param name="increment_size">100</param>
<param name="optimizer ">pooled</param>
</generator>
</id> 5 ȝ
6 资源
]]>
Hibernate是最行的对象关pL(ORMQ引擎之一Q它提供了数据持久化和查询服务?/p>
调优是一个P代的、持l进行的q程Q涉及Y件开发生命周期(SDLCQ的所有阶Dc在一个典型的使用Hibernateq行持久化的Java EE应用E序中,调优会涉及以下几个方面:
没有一套精心设计的Ҏ去q行以上调优是非常耗时的,而且很可能收效甚微。好的调优方法的重要部分是ؓ调优内容划分优先U。可以用Pareto定律Q又U?#8220;80/20法则”Q来解释q一点,即通常80%的应用程序性能改善源自?0%的性能问题[5]?/p>
相比Z盘和网l的讉KQ基于内存和CPU的访问能提供更低的gq和更高的吞吐量。这U基于IO的Hibernate调优与底层系lIO部分的调优应该优先于ZCPU和内存的底层pȝGC、CPU和内存部分的调优?/p>
范例1
我们调优了一个选择甉|的HQL查询Q把它从30U降C1U以内。如果我们在垃圾回收斚w下功夫,可能收效甚微——也许只有几毫U或者最多几U,相比HQL的改q,GC斚w的改善可以忽略不计?/p>
好的调优Ҏ的另一个重要部分是军_何时优化[4]?/p>
U极优化的提倡者主张开始时p行调优,例如在业务规则和设计阶段Q在整个SDLC都持l进行优化,因ؓ他们认ؓ后期改变业务规则和重新设计代价太大?/p>
另一zh提倡在SDLC末期q行调优Q因Z们抱怨前期调优经怼让设计和~码变得复杂。他们经常引用Donald Knuth的名a“q早优化是万恶之?/em>” [6]?/p>
Zq调优和编码需要一些权衡。根据笔者的l验Q适当的前期调优能带来更明智的设计和细致的~码。很多项目就p|在应用程序调优上Q因Z面提到的“q早优化”阶段在被引用时脱M上下文,而且相应的调优不是被推迟得太晚就是投入资源过?/p>
但是Q要做很多前期调优也不太可能Q因为没有经q剖析,你ƈ不能定应用E序的瓶颈究竟在何处Q应用程序一般都是这h化的?/p>
Ҏ们的多线E企业应用E序的剖析也表现出大多数应用E序q_只有20-50%的CPU使用率。剩余的CPU开销只是在等待数据库和网l相关的IO?/p>
Z上述分析Q我们得样一个结论,l合业务规则和设计的Hibernate调优在Pareto定律?0%的那个部分,相应的它们的优先U更高?/p>
一U比较实际的做法是: 你能在Jack Shirazi的《Java Performance Tuning?[7]一书中扑ֈ更多关于性能调优阶段的常见徏议?/p>
下面的章节中Q我们会按照调优的大致顺序(列在前面的通常影响最大)去解释一些特定的调优技术?/p>
没有对Hibernate应用E序的有效监控和剖析Q你无法得知性能瓉以及何处需要调优?/p>
管使用Hibernate的主要目的是你从直接用SQL的痛苦中解救出来Qؓ了对应用E序q行调优Q你必须知道Hibernate生成了哪些SQL。JoeSplosky在他的《The Law of Leaky Abstractions》一文中详细描述了这个问题?/p>
你可以在log4j中将org.hibernate.SQL包的日志U别设ؓDEBUGQ这样便能看到生成的所有SQL。你q可以将其他包的日志U别设ؓDEBUGQ甚至TRACE来定位一些性能问题?/p>
如果开?strong>hibernate.generate.statisticsQHibernate会导出实体、集合、会话、二U缓存、查询和会话工厂的统计信息,q对通过SessionFactory.getStatistics()q行的调优很有帮助。ؓ了简单v见,Hibernateq可以用MBean“org.hibernate.jmx.StatisticsService”通过JMX来导出统计信息。你可以在这个网站找到配|范?/u> ?/u> 一个好的剖析工具不仅有利于Hibernate调优Q还能ؓ应用E序的其他部分带来好处。然而,大多数商业工P例如JProbe [10]Q都很昂c幸q的是Sun/Oracle的JDK1.6自带了一个名?#8220;Java VisualVM” [11]的调试接口。虽然比起那些商业竞争对手,它还相当基础Q但它提供了很多调试和调优信息?/p>
管业务规则和设计调优ƈ不属于Hibernate调优的范_但此处的军_对后面Hibernate的调优有很大影响。因此我们特意指Z些与Hibernate调优有关的点?/p>
在业务需求收集与调优q程中,你需要知道: Z业务需求,你会得到一个最优设计,其中军_了应用程序类型(是OLTPq是数据仓库Q亦或者与其中某一U比较接q)和分层结构(持久层和服务层分离q是合ƈQ,创徏领域对象Q通常是POJOQ,军_数据聚合的地方(在数据库中进行聚合能利用强大的数据库功能Q节省网l带宽;但是除了像COUNT、SUM、AVG、MIN和MAXq样的标准聚合,其他的聚合通常不具有移植性。在应用服务器上q行聚合允许你应用更复杂的业务逻辑Q但你需要先在应用程序中载入详细的数据)?/p>
范例2 分析员需要查看一个取自大数据表的甉|ISOQIndependent System OperatorQ聚合列表。最开始他们想要显C大多数字段Q尽数据库能在1分钟内做出响应,应用E序也要?0分钟?百万行数据加载到前端UI。经q重新分析,分析员保留了14个字Dc因为去掉了很多可选的高聚合度字段Q从剩下的字D中q行聚合分组q回的数据要很多,而且大多数情况下的数据加载时间也~小C可接受的范围内?/p>
范例3 q?4?#8220;非标?#8221;QshapedQ表C每时都可以有自己的电量和hQ如果所?4时的电量和h相同Q我们称之ؓ“标准”Q小时会修改时甉|交易Q其中包?个属性:每小时电量和h。v初我们用Hibernate?em>select-before-update3. 监控和剖?/h2>
3.1.1 监控SQL生成
3.1.2 查看Hibernatel计
3.1.3 剖析
4. 调优技?/h2>
4.1 业务规则与设计调?/h3>
Ҏ,是更新24行数据需?4ơ选择。因为我们只需?个属性,而且如果不修改电量或h的话也没有业务规则禁止无效修改,我们关闭了select-before-updateҎ,避免?4ơ选择?/p>
管l承映射是领域对象的一部分Q出于它的重要性我们将它单独出来。HRD [1]中的W??#8220;l承映射”已经说得很清楚了Q所以我们将xSQL生成和针Ҏ个策略的调优?/p>
以下是HRD中范例的cdQ?/p>
只需要一张表Q一条多态查询生成的SQL大概是这LQ?/p>
select id, payment_type, amount, currency, rtn, credit_card_type from payment
针对具体子类Q例如CashPaymentQ的查询生成的SQL是这LQ?/p>
select id, amount, currency from payment where payment_type=’CASH’
q样做的优点包括只有一张表、查询简单以及容易与其他表进行关联。第二个查询中不需要包含其他子cM的属性。所有这些特性让该策略的性能调优要比其他{略Ҏ得多。这U方法通常比较适合数据仓库pȝQ因为所有数据都在一张表里,不需要做表连接?/p>
主要的缺Ҏ个类层次中的所有属性都挤在一张大表里Q如果有很多子类Ҏ的属性,数据库中׃有太多字D늚取gؓnullQ这为当前基于行的数据库Q用基于列的DBMS的数据仓库处理这个会更好些)的SQL调优增加了难度。除非进行分区,否则唯一的数据表会成为热点,OLTPpȝ通常在这斚w都不太好?/p>
需?张表Q多态查询生成的SQL如下Q?/p>
select id, payment_type, amount, currency, rtn, credit_card type, case when c.payment_id is not null then 1 when ck.payment_id is not null then 2 when cc.payment_id is not null then 3 when p.id is not null then 0 end as clazz from payment p left join cash_payment c on p.id=c.payment_id left join cheque_payment ck on p.id=ck.payment_id left join credit_payment cc on p.id=cc.payment_id;
针对具体子类Q例如CashPaymentQ的查询生成的SQL是这LQ?/p>
select id, payment_type, amount, currency from payment p left join cash_payment c on p.id=c.payment_id;
优点包括数据表比较紧凑(没有不需要的可空字段Q,数据跨三个子cȝ表进行分区,Ҏ使用类的表与其他表q行兌。紧凑的数据表可以针对基于行的数据库做存储块优化Q让SQL执行得更好。数据分区增加了数据修改的ƈ发性(除了类Q没有热点)QOLTPpȝ通常会更好些?/p>
同样的,W二个查询不需要包含其他子cȝ属性?/p>
~点是在所有策略中它用的表和表连接最多,SQL语句E显复杂Q看看Hibernate动态鉴别器的长CASE子句Q。相比单张表Q数据库要花更多旉调优数据表连接,数据仓库在用该{略旉常不太理想?/p>
因ؓ不能跨超cd子类的字D|建立复合索引Q如果需要按q些列进行查询,性能会受影响。Q何子cL据的修改都涉及两张表Q超cȝ表和子类的表?/p>
涉及三张或更多的表,多态查询生成的SQL是这LQ?/p>
select p.id, p.amount, p.currency, p.rtn, p. credit_card_type, p.clazz from (select id, amount, currency, null as rtn,null as credit_card type, 1 as clazz from cash_payment union all select id, amount, null as currency, rtn,null as credit_card type, 2 as clazz from cheque_payment union all select id, amount, null as currency, null as rtn,credit_card type, 3 as clazz from credit_payment) p;
针对具体子类Q例如CashPaymentQ的查询生成的SQL是这LQ?/p>
select id, payment_type, amount, currency from cash_payment;
优点和上面的“每个子类一张表”{略怼。因c通常是抽象的Q所以具体的三张表是必须的[开头处说的3张或更多的表是必ȝ]QQ何子cȝ数据修改只涉及一张表Q运行v来更快?/p>
~点是SQLQfrom子句和union all子查询)太复杂。但是大多数数据库对此类SQL的调优都很好?/p>
如果一个类惛_Payment类兌Q数据库无法使用引用完整性(referential integrityQ来实现它;必须使用触发器来实现它。这Ҏ据库性能有些影响?/p>
只需要三张表。对于Payment的多态查询生成三条独立的SQL语句Q每个对应一个子cRHibernate引擎通过Java反射扑ևPayment的所有三个子cR?/p>
具体子类的查询只生成该子cȝSQL。这些SQL语句都很单,q里׃再阐qC?/p>
它的优点和上节类|紧凑数据表、跨三个具体子类的数据分Z及对子类L数据的修攚w只涉及一张表?/p>
~点是用三条独立的SQL语句代替了一条联合SQLQ这会带来更多网lIO。Java反射也需要时间。假讑֦果你有一大堆领域对象Q你从最上层的Objectc进行隐式选择查询Q那该需要多长时间啊Q?/p>
Ҏ你的映射{略制定合理的选择查询q易事Q这需要你仔细调优业务需求,Z特定的数据场景制定合理的设计决策?/p>
以下是一些徏议:
范例4
下面是一个交易描q应用程序的部分领域cdQ?/p>
开始时Q项目只有GasDeal和少数用P它?#8220;每个cdơ一张表”?/p>
OilDeal和ElectricityDeal是后期生更多业务需求后加入的。没有改变映策略。但是ElectricityDeal有太多自q属性,因此有很多电相关的可I字D加入了Deal表。因为用户量也在增长Q数据修改变得越来越慢?/p>
重新设计时我们用了两张单独的表Q分别针Ҏ/油和늛关的属性。新的映合了“每个cdơ一张表”?#8220;每个子类一张表”。我们还重新设计了查询,以便允许针对具体交易子类q行选择Q消除不必要的列和表q接?/p>
Z4.1?/strong>中对业务规则和设计的调优Q你得到了一个用POJO来表C的领域对象的类图。我们徏议:
范例5
我们有一个名为ElectricityDeals的核心POJO用于描述늚交易。从业务角度来看Q它有很多many-to-one兌Q例如和Portfolio、Strategy和Trader{的兌。因为引用数据十分稳定,它们被缓存在前端Q能Z其ID属性快速定位到它们?/p>
Z有好的加载性能QElectricityDeal只映元数据Q即那些引用POJO的值类型ID属性,因ؓ在需要时Q可以在前端通过portfolioKey从缓存中快速查找PortfolioQ?/p>
<property name="portfolioKey" column="PORTFOLIO_ID" type="integer"/>q种隐式兌避免了数据库表连接和额外的字D选择Q降低了数据传输的大?/p>
׃创徏物理数据库连接非常耗时Q你应该始终使用q接池,而且应该始终使用生U连接池而非Hibernate内置的基本连接池法?/p>
通常会ؓHibernate提供一个有q接池功能的数据源。Apache DBCP的BasicDataSource[13]是一个流行的开源生产数据源。大多数数据库厂商也实现了自q兼容JDBC 3.0的连接池。D例来_你也可以使用Oracle ReaApplication Cluster [15]提供的JDBCq接?sup>[14]以获得连接的负蝲均衡和失败{UR?/p>
不用多说Q你在网上能扑ֈ很多关于q接池调优的技术,因此我们只讨论那些大多数q接池所共有的通用调优参数Q?/p>
短数据库事务对Q何高性能、高可扩展性的应用E序来说都是必不可少的。你使用表示对话h的会话来处理单个工作单元Q以此来处理事务?/p>
考虑到工作单元的范围和事务边界的划分Q有3中模式:
你还应该注意以下几点?nbsp;
范例6
我们的应用程序有多个在大多数情况下只和数据库“A”打交道的服务层方法;它们偶尔也会从数据库“B”中获取只L据。因为数据库“B”只提供只L据,我们对这些方法在q两个数据库上仍然用本C务?/p>
服务层上有一个方法设计在两个数据库上执行数据变更。以下是伪代码:
//Make sure a local transaction on database A exists @Transactional (readOnly=false, propagation=Propagation.REQUIRED) public void saveIsoBids() { //it participates in the above annotated local transaction insertBidsInDatabaseA(); //it runs in its own local transaction on database B insertBidRequestsInDatabaseB(); //must be the last operation因ؓinsertBidRequestsInDatabaseB()是saveIsoBids ()中的最后一个方法,所以只有下面的场景会造成数据不一_
在saveIsoBids()执行q回Ӟ数据?#8220;A”的本C务提交失败?/p>
但是Q就saveIsoBids()使用JTAQ在两阶D|交(2PCQ的W二个提交阶D失败的时候,你还是会到数据不一致。因此如果你能处理好上述的数据不一致性,而且不想Z一个或数几个Ҏ引入JTA的复杂性,你应该用本C务?/p>
Q未完待l)