蓬莱做网站哪家好,前程无忧深圳招聘网站,网站的宗旨,邢台市教育局官网为什么80%的码农都做不了架构师#xff1f; 1.引言 Hibernate是最流行的对象关系映射#xff08;ORM#xff09;引擎之一#xff0c;它提供了数据持久化和查询服务。 在你的项目中引入Hibernate并让它跑起来是很容易的。但是#xff0c;要让它跑得好却是需… 为什么80%的码农都做不了架构师 1.引言 Hibernate是最流行的对象关系映射ORM引擎之一它提供了数据持久化和查询服务。 在你的项目中引入Hibernate并让它跑起来是很容易的。但是要让它跑得好却是需要很多时间和经验的。 通过我们的使用Hibernate 3.3.1和Oracle 9i的能源项目中的一些例子本文涵盖了很多Hibernate调优技术。其中还提供了一些掌握Hibernate调优技术所必需的数据库知识。 我们假设读者对Hibernate有一个基本的了解。如果一个调优方法在Hibernate 参考文档下文简称HRD或其他调优文章中有详细描述我们仅提供一个对该文档的引用并从不同角度对其做简单说明。我们关注于那些行之有效但又缺乏文档的调优方法。 2.Hibernate性能调优 调优是一个迭代的、持续进行的过程涉及软件开发生命周期SDLC的所有阶段。在一个典型的使用Hibernate进行持久化的Java EE应用程序中调优会涉及以下几个方面 业务规则调优设计调优Hibernate调优Java GC调优应用程序容器调优底层系统调优包括数据库和OS。没有一套精心设计的方案就去进行以上调优是非常耗时的而且很可能收效甚微。好的调优方法的重要部分是为调优内容划分优先级。可以用Pareto定律又称“80/20法则”来解释这一点即通常80%的应用程序性能改善源自头20%的性能问题[5]。 相比基于磁盘和网络的访问基于内存和CPU的访问能提供更低的延迟和更高的吞吐量。这种基于IO的Hibernate调优与底层系统IO部分的调优应该优先于基于CPU和内存的底层系统GC、CPU和内存部分的调优。 范例1 我们调优了一个选择电流的HQL查询把它从30秒降到了1秒以内。如果我们在垃圾回收方面下功夫可能收效甚微——也许只有几毫秒或者最多几秒相比HQL的改进GC方面的改善可以忽略不计。 好的调优方法的另一个重要部分是决定何时优化[4]。 积极优化的提倡者主张开始时就进行调优例如在业务规则和设计阶段在整个SDLC都持续进行优化因为他们认为后期改变业务规则和重新设计代价太大。 另一派人提倡在SDLC末期进行调优因为他们抱怨前期调优经常会让设计和编码变得复杂。他们经常引用Donald Knuth的名言“过早优化是万恶之源” [6]。 为了平衡调优和编码需要一些权衡。根据笔者的经验适当的前期调优能带来更明智的设计和细致的编码。很多项目就失败在应用程序调优上因为上面提到的“过早优化”阶段在被引用时脱离了上下文而且相应的调优不是被推迟得太晚就是投入资源过少。 但是要做很多前期调优也不太可能因为没有经过剖析你并不能确定应用程序的瓶颈究竟在何处应用程序一般都是这样演化的。 对我们的多线程企业级应用程序的剖析也表现出大多数应用程序平均只有20-50%的CPU使用率。剩余的CPU开销只是在等待数据库和网络相关的IO。 基于上述分析我们得出这样一个结论结合业务规则和设计的Hibernate调优在Pareto定律中20%的那个部分相应的它们的优先级更高。 一种比较实际的做法是 1、识别出主要瓶颈可以预见其中多数是Hibernate、业务规则和设计方面的其数量视你的调优目标而定但三到五个是不错的开端。 2、修改应用程序以便消除这些瓶颈。 3、测试应用程序然后重复步骤1直到达到你的调优目标为止。 你能在Jack Shirazi的《Java Performance Tuning》 [7]一书中找到更多关于性能调优阶段的常见建议。 下面的章节中我们会按照调优的大致顺序列在前面的通常影响最大去解释一些特定的调优技术。 3. 监控和剖析 没有对Hibernate应用程序的有效监控和剖析你无法得知性能瓶颈以及何处需要调优。 3.1.1 监控SQL生成 尽管使用Hibernate的主要目的是将你从直接使用SQL的痛苦中解救出来为了对应用程序进行调优你必须知道Hibernate生成了哪些SQL。JoeSplosky在他的《The Law of Leaky Abstractions》一文中详细描述了这个问题。 你可以在log4j中将org.hibernate.SQL包的日志级别设为DEBUG这样便能看到生成的所有SQL。你还可以将其他包的日志级别设为DEBUG甚至TRACE来定位一些性能问题。 3.1.2 查看Hibernate统计 如果开启hibernate.generate.statisticsHibernate会导出实体、集合、会话、二级缓存、查询和会话工厂的统计信息这对通过SessionFactory.getStatistics()进行的调优很有帮助。为了简单起见Hibernate还可以使用MBean“org.hibernate.jmx.StatisticsService”通过JMX来导出统计信息。你可以在这个网站找到配置范例 。 3.1.3 剖析 一个好的剖析工具不仅有利于Hibernate调优还能为应用程序的其他部分带来好处。然而大多数商业工具例如JProbe [10]都很昂贵。幸运的是Sun/Oracle的JDK1.6自带了一个名为“Java VisualVM” [11]的调试接口。虽然比起那些商业竞争对手它还相当基础但它提供了很多调试和调优信息。 4. 调优技术 4.1 业务规则与设计调优 尽管业务规则和设计调优并不属于Hibernate调优的范畴但此处的决定对后面Hibernate的调优有很大影响。因此我们特意指出一些与Hibernate调优有关的点。 在业务需求收集与调优过程中你需要知道 数据获取特性包括引用数据reference data、只读数据、读分组read group、读取大小、搜索条件以及数据分组和聚合。数据修改特性包括数据变更、变更组、变更大小、无效修改补偿、数据库所有变更都在一个数据库中或在多个数据库中、变更频率和并发性以及变更响应和吞吐量要求。数据关系例如关联association、泛化generalization、实现realization和依赖dependency。基于业务需求你会得到一个最优设计其中决定了应用程序类型是OLTP还是数据仓库亦或者与其中某一种比较接近和分层结构将持久层和服务层分离还是合并创建领域对象通常是POJO决定数据聚合的地方在数据库中进行聚合能利用强大的数据库功能节省网络带宽但是除了像COUNT、SUM、AVG、MIN和MAX这样的标准聚合其他的聚合通常不具有移植性。在应用服务器上进行聚合允许你应用更复杂的业务逻辑但你需要先在应用程序中载入详细的数据。 范例2 分析员需要查看一个取自大数据表的电流ISOIndependent System Operator聚合列表。最开始他们想要显示大多数字段尽管数据库能在1分钟内做出响应应用程序也要花30分钟将1百万行数据加载到前端UI。经过重新分析分析员保留了14个字段。因为去掉了很多可选的高聚合度字段从剩下的字段中进行聚合分组返回的数据要少很多而且大多数情况下的数据加载时间也缩小到了可接受的范围内。 范例3 过24个“非标准”shaped表示每小时都可以有自己的电量和价格如果所有24小时的电量和价格相同我们称之为“标准”小时会修改小时电流交易其中包括2个属性每小时电量和价格。起初我们使用Hibernate的select-before-update特性就是更新24行数据需要24次选择。因为我们只需要2个属性而且如果不修改电量或价格的话也没有业务规则禁止无效修改我们就关闭了select-before-update特性避免了24次选择。 4.2继承映射调优 尽管继承映射是领域对象的一部分出于它的重要性我们将它单独出来。HRD [1]中的第9章“继承映射”已经说得很清楚了所以我们将关注SQL生成和针对每个策略的调优建议。 以下是HRD中范例的类图 4.2.1 每个类层次一张表 只需要一张表一条多态查询生成的SQL大概是这样的 select id, payment_type, amount, currency, rtn, credit_card_type from payment针对具体子类例如CashPayment的查询生成的SQL是这样的 select id, amount, currency from payment where payment_type’CASH’ 这样做的优点包括只有一张表、查询简单以及容易与其他表进行关联。第二个查询中不需要包含其他子类中的属性。所有这些特性让该策略的性能调优要比其他策略容易得多。这种方法通常比较适合数据仓库系统因为所有数据都在一张表里不需要做表连接。 主要的缺点整个类层次中的所有属性都挤在一张大表里如果有很多子类特有的属性数据库中就会有太多字段的取值为null这为当前基于行的数据库使用基于列的DBMS的数据仓库处理这个会更好些的SQL调优增加了难度。除非进行分区否则唯一的数据表会成为热点OLTP系统通常在这方面都不太好。 4.2.2每个子类一张表 需要4张表多态查询生成的SQL如下 select id, payment_type, amount, currency, rtn, credit_card type,case when c.payment_id is not null then 1when ck.payment_id is not null then 2when cc.payment_id is not null then 3when p.id is not null then 0 end as clazzfrom payment p left join cash_payment c on p.idc.payment_id left joincheque_payment ck on p.idck.payment_id left join credit_payment cc on p.idcc.payment_id; 针对具体子类例如CashPayment的查询生成的SQL是这样的 select id, payment_type, amount, currency
from payment p left join cash_payment c on p.idc.payment_id; 优点包括数据表比较紧凑没有不需要的可空字段数据跨三个子类的表进行分区容易使用超类的表与其他表进行关联。紧凑的数据表可以针对基于行的数据库做存储块优化让SQL执行得更好。数据分区增加了数据修改的并发性除了超类没有热点OLTP系统通常会更好些。 同样的第二个查询不需要包含其他子类的属性。 缺点是在所有策略中它使用的表和表连接最多SQL语句稍显复杂看看Hibernate动态鉴别器的长CASE子句。相比单张表数据库要花更多时间调优数据表连接数据仓库在使用该策略时通常不太理想。 因为不能跨超类和子类的字段来建立复合索引如果需要按这些列进行查询性能会受影响。任何子类数据的修改都涉及两张表超类的表和子类的表。 4.2.3每个具体类一张表 涉及三张或更多的表多态查询生成的SQL是这样的 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 allselect id, amount, null as currency, rtn,null as credit_card type,2 as clazz from cheque_payment union allselect id, amount, null as currency, null as rtn,credit_card type,3 as clazz from credit_payment) p; 针对具体子类例如CashPayment的查询生成的SQL是这样的 select id, payment_type, amount, currency from cash_payment; 优点和上面的“每个子类一张表”策略相似。因为超类通常是抽象的所以具体的三张表是必须的[开头处说的3张或更多的表是必须的]任何子类的数据修改只涉及一张表运行起来更快。 缺点是SQLfrom子句和union all子查询太复杂。但是大多数数据库对此类SQL的调优都很好。 如果一个类想和Payment超类关联数据库无法使用引用完整性referential integrity来实现它必须使用触发器来实现它。这对数据库性能有些影响。 4.2.4使用隐式多态实现每个具体类一张表 只需要三张表。对于Payment的多态查询生成三条独立的SQL语句每个对应一个子类。Hibernate引擎通过Java反射找出Payment的所有三个子类。 具体子类的查询只生成该子类的SQL。这些SQL语句都很简单这里就不再阐述了。 它的优点和上节类似紧凑数据表、跨三个具体子类的数据分区以及对子类任意数据的修改都只涉及一张表。 缺点是用三条独立的SQL语句代替了一条联合SQL这会带来更多网络IO。Java反射也需要时间。假设如果你有一大堆领域对象你从最上层的Object类进行隐式选择查询那该需要多长时间啊 根据你的映射策略制定合理的选择查询并非易事这需要你仔细调优业务需求基于特定的数据场景制定合理的设计决策。 以下是一些建议 设计细粒度的类层次和粗粒度的数据表。细粒度的数据表意味着更多数据表连接相应的查询也会更复杂。如非必要不要使用多态查询。正如上文所示对具体类的查询只选择需要的数据没有不必要的表连接和联合。“每个类层次一张表”对有高并发、简单查询并且没有共享列的OLTP系统来说是个不错的选择。如果你想用数据库的引用完整性来做关联那它也是个合适的选择。“每个具体类一张表”对有高并发、复杂查询并且没有共享列的OLTP系统来说是个不错的选择。当然你不得不牺牲超类与其他类之间的关联。采用混合策略例如“每个类层次一张表”中嵌入“每个子类一张表”这样可以利用不同策略的优势。随着你项目的进化如果你要反复重新映射那你可能也会采用该策略。“使用隐式多态实现每个具体类一张表”这种做法并不推荐因为其配置过于繁缛、使用“any”元素的复杂关联语法和隐式查询的潜在危险性。范例4 下面是一个交易描述应用程序的部分领域类图 开始时项目只有GasDeal和少数用户它使用“每个类层次一张表”。 OilDeal和ElectricityDeal是后期产生更多业务需求后加入的。没有改变映射策略。但是ElectricityDeal有太多自己的属性因此有很多电相关的可空字段加入了Deal表。因为用户量也在增长数据修改变得越来越慢。 重新设计时我们使用了两张单独的表分别针对气/油和电相关的属性。新的映射混合了“每个类层次一张表”和“每个子类一张表”。我们还重新设计了查询以便允许针对具体交易子类进行选择消除不必要的列和表连接。 4.3 领域对象调优 基于4.1节中对业务规则和设计的调优你得到了一个用POJO来表示的领域对象的类图。我们建议 4.3.1 POJO调优 从读写数据中将类似引用这样的只读数据和以读为主的数据分离出来。 只读数据的二级缓存是最有效的其次是以读为主的数据的非严格读写。将只读POJO标识为不可更改的immutable也是一个调优点。如果一个服务层方法只处理只读数据可以将它的事务标为只读这是优化Hibernate和底层JDBC驱动的一个方法。细粒度的POJO和粗粒度的数据表。 基于数据的修改并发量和频率等内容来分解大的POJO。尽管你可以定义一个粒度非常细的对象模型但粒度过细的表会导致大量表连接这对数据仓库来说是不能接受的。优先使用非final的类。 Hibernate只会针对非final的类使用CGLIB代理来实现延时关联获取。如果被关联的类是final的Hibernate会一次加载所有内容这对性能会有影响。使用业务键为分离detached实例实现equals()和hashCode()方法。 在多层系统中经常可以在分离对象上使用乐观锁来提升系统并发性达到更高的性能。定义一个版本或时间戳属性。 乐观锁需要这个字段来实现长对话应用程序事务[译注session译为会话conversion译为对话以示区别]。优先使用组合POJO。 你的前端UI经常需要来自多个不同POJO的数据。你应该向UI传递一个组合POJO而不是独立的POJO以获得更好的网络性能。 有两种方式在服务层构建组合POJO。一种是在开始时加3.2载所有需要的独立POJO随后抽取需要的属性放入组合POJO另一种是使用HQL投影直接从数据库中选择需要的属性。 如果其他地方也要查找这些独立POJO可以把它们放进二级缓存以便共享这时第一种方式更好其他情况下第二种方式更好。4.3.2 POJO之间关联的调优 如果可以用one-to-one、one-to-many或many-to-one的关联就不要使用many-to-many。many-to-many关联需要额外的映射表。 尽管你的Java代码只需要处理两端的POJO但查询时数据库需要额外地关联映射表修改时需要额外的删除和插入。单向关联优先于双向关联。 由于many-to-many的特性在双向关联的一端加载对象会触发另一端的加载这会进一步触发原始端加载更多的数据等等。 one-to-many和many-to-one的双向关联也是类似的当你从多端子实体定位到一端父实体。 这样的来回加载很耗时而且可能也不是你所期望的。不要为了关联而定义关联只在你需要一起加载它们时才这么做这应该由你的业务规则和设计来决定见范例5。 另外你要么不定义任何关联要么在子POJO中定义一个值类型的属性来表示父POJO的ID另一个方向也是类似的。集合调优 如果集合排序逻辑能由底层数据库实现就使用“order-by”属性来代替“sort”因为通常数据库在这方面做得比你好。 集合可以是值类型的元素或组合元素也可以是实体引用类型的one-to-many或many-to-many关联。对引用类型集合的调优主要是调优获取策略。对于值类型集合的调优HRD [1]中的20.5节“理解集合性能”已经做了很好的阐述。获取策略调优。请见4.7节的范例5。范例5 我们有一个名为ElectricityDeals的核心POJO用于描述电的交易。从业务角度来看它有很多many-to-one关联例如和Portfolio、Strategy和Trader等的关联。因为引用数据十分稳定它们被缓存在前端能基于其ID属性快速定位到它们。 为了有好的加载性能ElectricityDeal只映射元数据即那些引用POJO的值类型ID属性因为在需要时可以在前端通过portfolioKey从缓存中快速查找Portfolio property nameportfolioKey columnPORTFOLIO_ID typeinteger/ 这种隐式关联避免了数据库表连接和额外的字段选择降低了数据传输的大小。 4.4 连接池调优 由于创建物理数据库连接非常耗时你应该始终使用连接池而且应该始终使用生产级连接池而非Hibernate内置的基本连接池算法。 通常会为Hibernate提供一个有连接池功能的数据源。Apache DBCP的BasicDataSource[13]是一个流行的开源生产级数据源。大多数数据库厂商也实现了自己的兼容JDBC 3.0的连接池。举例来说你也可以使用Oracle ReaApplication Cluster [15]提供的JDBC连接池[14]以获得连接的负载均衡和失败转移。 不用多说你在网上能找到很多关于连接池调优的技术因此我们只讨论那些大多数连接池所共有的通用调优参数 最小池大小连接池中可保持的最小连接数。最大池大小连接池中可以分配的最大连接数。 如果应用程序有高并发而最大池大小又太小连接池就会经常等待。相反如果最小池大小太大又会分配不需要的连接。最大空闲时间连接池中的连接被物理关闭前能保持空闲的最大时间。最大等待时间连接池等待连接返回的最大时间。该参数可以预防失控事务runaway transaction。验证查询在将连接返回给调用方前用于验证连接的SQL查询。这是因为一些数据库被配置为会杀掉长时间空闲的连接网络或数据库相关的异常也可能会杀死连接。为了减少此类开销连接池在空闲时会运行该验证。4.5事务和并发的调优 短数据库事务对任何高性能、高可扩展性的应用程序来说都是必不可少的。你使用表示对话请求的会话来处理单个工作单元以此来处理事务。 考虑到工作单元的范围和事务边界的划分有3中模式 每次操作一个会话。 每次数据库调用需要一个新会话和事务。因为真实的业务事务通常包含多个此类操作和大量小事务这一般会引起更多数据库活动主要是数据库每次提交需要将变更刷新到磁盘上影响应用程序性能。这是一种反模式不该使用它。**使用分离对象每次请求一个会话。**每次Ke户端请求有一个新会话和一个事务使用Hibernate的“当前会话”特性将两者关联起来。 在一个多层系统中用户通常会发起长对话或应用程序事务。大多数时间我们使用Hibernate的自动版本和分离对象来实现乐观并发控制和高性能。**带扩展或长会话的每次对话一会话。**在一个也许会跨多个事务的长对话中保持会话开启。尽管这能把你从重新关联中解脱出来但会话可能会内存溢出在高并发系统中可能会有旧数据。你还应该注意以下几点。 如果不需要JTA就用本地事务因为JTA需要更多资源比本地事务更慢。就算你有多个数据源除非有跨多个数据库的事务否则也不需要JTA。在最后的一个场景下可以考虑在每个数据源中使用本地事务使用一种类似“Last Resource Commit Optimization”[16]的技术见下面的范例6。如果不涉及数据变更将事务标记为只读的就像4.3.1节提到的那样。总是设置默认事务超时。保证在没有响应返回给用户时没有行为不当的事务会完全占有资源。这对本地事务也同样有效。如果Hibernate不是独占数据库用户乐观锁会失效除非创建数据库触发器为其他应用程序对相同数据的变更增加版本字段值。范例6 我们的应用程序有多个在大多数情况下只和数据库“A”打交道的服务层方法它们偶尔也会从数据库“B”中获取只读数据。因为数据库“B”只提供只读数据我们对这些方法在这两个数据库上仍然使用本地事务。 服务层上有一个方法设计在两个数据库上执行数据变更。以下是伪代码 //Make sure a local transaction on database A exists
Transactional (readOnlyfalse, propagationPropagation.REQUIRED)
public void saveIsoBids() {//it participates in the above annotated local transactioninsertBidsInDatabaseA();//it runs in its own local transaction on database B insertBidRequestsInDatabaseB(); //must be the last operation 因为insertBidRequestsInDatabaseB()是saveIsoBids ()中的最后一个方法所以只有下面的场景会造成数据不一致 在saveIsoBids()执行返回时数据库“A”的本地事务提交失败。 但是就算saveIsoBids()使用JTA在两阶段提交2PC的第二个提交阶段失败的时候你还是会碰到数据不一致。因此如果你能处理好上述的数据不一致性而且不想为了一个或少数几个方法引入JTA的复杂性你应该使用本地事务。 未完待续 关于作者 Yongjun Jiao是SunGard Consulting Services的技术主管。过去10年中他一直是专业软件开发者他的专长包括Java SE、Java EE、Oracle和应用程序调优。他最近的关注点是高性能计算包括内存数据网格、并行计算和网格计算。 Stewart Clark是SunGard Consulting Services的负责人。过去15年中他一直是专业软件开发者和项目经理他的专长包括Java核心编程、Oracle和能源交易。 [译注由于原文较长中译版分两次发布] 转自http://www.infoq.com/cn/articles/hibernate_tuning 查看英文原文Revving Up Your Hibernate Engine 转载于:https://my.oschina.net/dabird/blog/604567