在实际开发中,如何对基于Hibernate的持久层实现进行进一步的性能调整,其中涉及到怎样的技术和技巧,下面需要进行探讨。
性能调整的第一步,可能也是最关键的一步:性能检测。
1. 性能检测
从技术角度而言,除非设计上的严重缺陷,否砸,绝大部分时候,只要能发现性能瓶颈所在,总是能通过各种途径对系统进行调整,从而获得进一步的性能提升。
那么如何发现系统运行中的性能缺点,并寻出其中最关键的部分?在系统的不同层面往往有着不同的检测方式与技巧。
常用的有以下几种性能观测手段:
1)源代码评审
2)追加性能监测代码
3)基于工具的性能分析与检查


1)源代码评审
其中,源代码评审机制的监测力量无疑最为薄弱。特别在中大型项目中,代码动辄以10万行计,面对如此庞杂的代码群,基于人力的代码评审几乎形同虚设。
2)追加性能监测代码
通过追加性能监测代码进行性能分析可能是日常开发中最常用的一种手段。在之前“数据批量操作”部分内容中,通过在代码中追加计时功能,我们得到了代码片段的执行时间,并以此作为性能比较的依据。
最基础,也是最直接的观察渠道。不过缺陷也显而易见。
首先,它并不能称为一个工程化的性能监测手段,过多的监测代码混杂到在逻辑代码之间,使得代码可维护性降低。其次,不同开发人员对性能监测的认知程度并不一致,导致性能观察的结果较为混乱和片面,难以全面的反应出系统整体性能指标。
其次,性能观测结果难以进行分析和统计,我们得到的结果往往是某个代码片断所消耗的时间,而无法对系统性能进行全面的衡量(如,系统中哪些方法调用频率最高,哪些消耗时间最长,及其之间对等关系如何)。
最为关键的一点,我们甚至无法知道系统整体的并发容量到底如何。
3)基于工具的性能分析与检查
“工具”是技术群体工作经验积累的结晶。
在系统性能监测领域,目前市场上已经有了众多解决方案。其中包括商业的性能监测、评估系统(如Load Runner),以及免费甚至开源的性能监测软件,合理利用这些工具将对形成理性的、可衡量的性能分析策略有着莫大的帮助。
下面就围绕持久层中常用的通用性能监测工具P6SPY进行介绍。

2. P6SPY
P6SPY是针对数据库访问操作的动态监测框架(开源项目,项目首页:www.p6spy.com)。经过长时间的发展,P6SPY已经日渐成熟,具备丰富的文档和周边工具资源。
从实现原理上来讲,P6SPY模拟了一个标准的JDBC Driver,它代理了真正的底层JDBC驱动程序。
也就是说,我们只需将系统所用的JDBC驱动切换到P6SPY JDBC Driver,同时将P6SPY的JDBC配置到我们实际的JDBC驱动,即可使用其提供的数据访问性能监测功能。
以Hibernate配置(hibernate.cfg.xml)为例。首先,从P6SPY站点下载最新软件包p6spy-install.zip。从压缩文件中解压出:
a)    p6spy.jar
b)    spy.properties
其中p6spy.jar为运行包,spy.properties为对应的配置文件。
将p6spy.jar加入项目文件的CLASSPATH,同时将spy.properties放入运行环境的根目录(Eclipse中,将其置于src目录根节点之下)。
修改配置文件hibernate.cfg.xml,将hibernate.connection.driver_class修改为P6SPY提供的JDBC Driver Class:

<session-factory>
        <property name="hibernate.connection.url">
            jdbc:jtds:sqlserver://localhost/SampleDB
        </property。
        <property name="hibernate.connection.driver_class">
            com.p6spy.engine.spy.P6SpyDriver
            <!---net.sourceforge.jtds.jdbc.Driver-->
        </property>
        …
</session-factory>

完成了Hibernate中的JDBC切换,我们还需要对P6SPY进行配置(spy.properties),指定底层用于实际操作的JDBC驱动:

…
# the JTDS open source driver
realdriver = net.sourceforge.jtds.jdbc.Driver
…

这样,P6SPY的基本配置即告完成。可以看到,只需简单地切换JDBC驱动组件,我们就成功地将P6SPY与系统无缝衔接。
P6SPY通过JDBC代理的形式,在应用程序与底层JDBC之间嵌入了一个透明中间层。那么通过这样的机制,P6SPY能为我们带来些什么?
我们知道,Hibernate在执行底层数据库操作时,依然是基于JDBC访问接口,也就是说,Hibernate所有的持久化操作,最终必然通过JDBC提交。
那么,位于应用与JDBC层之间的这个P6SPY代理就显得别具意义,由于可以截获所有的JDBC操作,P6SPY可以轻易实现SQL执行效率的判定以及执行结果的分析统计。如某条SQL的执行时间,执行次数,及其在全部SQL中的相对执行频度,相对时间消耗比率。这样,我们就可以轻松找出对系统性能影响最大的SQL操作,并对其进行优化。
另外,对于使用占位符(如PreparedStatement中,我们以?作为占位符,之后填充参数值)的Statement而言,P6SPY可以输出SQL及其实际的参数值。这也大大提高了程序调试的直观性。
默认情况下,P6SPY的日志文件为spy.log,当然我们也可以通过调整spy.properties配置文件中的参数进行指定。
spy.log输出示例如下:
Hibernate深入浅出(十三)Hibernate性能优化_优化

其中”1109352762092|20|0|statement…”就是我们执行的Statement,以|分隔的第二栏,即此SQL执行所消耗的时间。
可以看到,这样琐碎的日志文件,解读工作非常繁琐,且难以统计,虽然我们可以看出其中每条Statement执行的时间,但是我们难以对全局进行把握。我们还需要某条特定Statement的执行频度以及资源消耗比等更加全局性的统计结果。
前面说过,p6spy经过长期发展,已经涌现了很多周边资源,其中,SQL  Profiler为我们提供了一个图形化的监控界面,它可以实时监控SQL执行过程,对执行结果进行统计并加以优化。
SQL Profiler不但为我们提供了直观的Statement统计信息,它还会根据当前SQL的执行效能给出进一步优化的建议(如建议在某些字段上建立索引)。
首先在www.jahia.org下载最新的SQL Profiler软件包。软件包中包含了两个主要文件:
a)    Sqlprofiler.jar
b)    spy.properties
spy.properties是一个P6SPY的示例配置文件。
sqlprofiler.jar则是可执行的jar文件包(可通过命令行”java –jar sqlprofiler.jar”运行),其中包含了一个SWING的用户控制界面。SQL  Profiler启动后,即开始监听本机4445端口。SQL Profiler正是通过这个端口监听来自P6SPY的Statement执行日志(这样即可在不同机器上分别运行监控与应用程序,同时也避免了互相之间的性能干扰)。
P6SPY通过log4j的SocketAppender向SQL Profiler发送日志信息,我们查看SQL Profiler软件包中的示例配置文件spy.properties,就可以发现如下SocketAppender配置片断:

log4j.appender.SQLPROFILER_CLIENT=org.apache.log4j.net.SocketAppender
log4j.appender.SQLPROFILER_CLIENT.RemoteHost=localhost
log4j.appender.SQLPROFILER_CLIENT.Port=4445
log4j.appender.SQLPROFILER_CLIENT.LocationInfo=true

为了简单起见,我们用SQL Profiler中的spy.properties文件覆盖之前的版本,更改realdriver配置以符合我们的实际情况。同时启动SQL Profiler.jar(如果报告OutOfMemoy错误,则以命令行”java –Xmx256m jar sqlprofiler.jar”启动)(如下图)。

Hibernate深入浅出(十三)Hibernate性能优化_优化_02

可以看到,界面上半部的SQL Satements栏目,显示了当前正在执行的Statement的实时信息况,每秒刷新。
中部的Profile results栏,用于显示优化器的优化结果。当我们按下工具栏上的暂停按钮时,SQL Profiler即开始进行优化分析工作,并将结果显示在这个栏目中,从示例图中可以看到,SQL Profiler建议我们在t_user表的name字段上建立索引。这些优化建议我们可以通过工具栏上左侧的第一个按钮保存,对于这里的实例,优化文件内容为:
CREATE INDEX t_user_index on t_user(name);
界面下部的detail栏目则显示了SQL Statements栏目中当前选定Statement的详细信息。
切换到logger页,我们可以看到P6SPY的运行日志(下图)。

Hibernate深入浅出(十三)Hibernate性能优化_Hibernate_03

切换到Analysis页,可以看到类似下图所示的结果:

Hibernate深入浅出(十三)Hibernate性能优化_优化_04

从以上图中,我们可以看到之前数据库操作过程中的统计信息,包括查询的次数统计,查询所导致的数据流量统计(越大的流量意味着越高的CPU和IO资源消耗)等。且在运行过程中,这些统计信息也会实时刷新。
虽然简单,但即使是这样简单的工具,也可以为我们的性能监测提供极为重要的参考依据。实际上,性能优化的关键往往也就主要集中在这几个有限的参数上,灵活地利用这些简单的工具,往往也可以达到意想不到的效果。
对于持久层的性能监测,暂且介绍这些内容。前面说过,无论在商业领域还是开源社区,类似的工具非常丰富,各具特色,如何利用这些工具达到我们所期望的目的,所有的限制可能仅仅是我们的现象力而已。
与SQL Profiler类似的工具还有IronTrack SQL以及商业性能监测工具——JDBInsight等,它们提供了更加丰富的特性和更为强大精准的监测功能。

3. Hibernate常见优化策略
针对不同类型的应用,具体的性能优化策略可能千差万别。这里,我们对之前的内容进行了总结,提供了一些通用的优化思路,希望有所启发。
〉在允许的情况下,选用最新版本的Hibernate发行版
Hibernate3提供了一些有助于性能提高的新特性,如经过优化的批量处理机制、代理机制、属性的延迟加载支持等。
〉制定合理的缓存策略
在之前的内容中,反复强调了缓存对于整体性能的关键性影响。制定合理的缓存策略将是提高性能的一个有效途径,在系统设计后期,建议根据应用中每个库表的实际情况,为其指定相匹配的缓存模式,同时通过系统压力测试以得到最佳的缓存性能。
〉采用合理的Session管理机制
在Hibernate实用技术中,我们介绍了基于ThreadLocal的Session管理机制,通过Thread级别的Session重用,我们可以通过充分利用一级缓存中的已有数据,避免无谓的数据库访问开销和临时对象的反复创建,请在您的系统中考虑追加这项特性,或根据情况制定符合实际情况的Session管理策略。
〉尽量使用延迟加载特性
对于无需立即加载的数据,应通过延迟加载特性应需加载,以避免系统资源的无谓消耗。
〉设定合理的批处理参数(batch_size)
〉如果可能,选用UUID作为主键生成器
〉如果可能,选用基于version的乐观锁策略替代悲观锁
〉开发过程中,打开Hibernate的SQL日志输出(hibernate.show_sql),通过观察Hibernate生成的SQL语句进一步了解其实现原理,从而指定更好的实现策略。
以上这些优化建议大多来自日常开发工作中的经验积累。值得注意的是,对于基于Hibernate实现的持久层,其性能表现取决于多个方面的综合因素。其中,数据库本身的优化也起着举足轻重的作用,合理的索引、缓存与数据分区策略都会对持久层性能带来可观提升。关于数据库本身的优化这里就不再加以展开。