xiecc:
我们的项目从去年12月份启动,采用了Struts+Hibernate的架构,一开始使用Hibernate的时候速度极快,对象操作异常方便,大家都说爽歪歪。
可惜好景不长,随着我们对象关系的不断复杂,数据量的不断增加,Hibernate的性能急剧下降。具体表现为:我们在设计对象时采用了很多的one-to-many和many-to-one的关系,在取某个对象的几个简单的属性时,它会把所有关联的子对象都取出来,经常出在取一个简单属性的时候,调试窗口的SQL语句一屏一屏地往下闪。到最后我的一个test跑完需要12分钟。
在忍无可忍之下,我们开始性能优化方案,以下我们优化所做的一些事情:
1、将所以one-to-many的关系里将lazy设成true
2、修改hibernate.properties,增加了以下两句:
hibernate.jdbc.fetch_size=50
hibernate.jdbc.batch_size=100
3、调整WebLogic的pool
4、利用Hibernate提供的CGLIB Proxy机制,使many-to-one关系的子对象也可以lazy initialization
(但是我发现调试窗口里仍会有取子对象的SQL语句,但速度确实快了)。
5、利用Hibernate提供的Cache机制,对关键对象使用Cache
结果优化以后,我的test可以从原来的12分钟变成50秒钟跑完。
原以为万事大吉了,但当我们面对客户的时候,才发现我们系统的性能还远远不够。
我们现在系统试运行约两个月,经常在数据保存或者查询时等上一分钟甚至两分钟。
由于客户原来的系统用asp+SQL Server写的,速度很快。二者一对比,我们就被客户骂得惨不忍睹。
优化真是一件很烦人的事,在不改动系统框架的情况下,不知还有哪些提高系统性能的方法?
freecode:
同感,虽然我不用,不懂hibernate.
前段时间,我们做了个项目,对一些取数的过程,采用了javascript脚本,再通过bsf编译,运行时,时间巨长,人家说以前用foxpro做的,快多了,弄得我们很没面子。
dhj1:
我也用 Struts+Hibernate 做大型项目,在并发很高时,每天4500人次访问量的情况下,性能也相当不错.
做的时间有几点考虑:
1.大东东.如果很多很多的one-to-many和many-to-one的关系. 必定会影响性能,我刚学习Hibernate 时就有这种直觉,所以我们没有用one-to-many和many-to-one的关系.而是象SQL一样的去操作表的关系标识符.
2.如果超大的系统,最终必须生成HTML的文件.就是有数据库中有数据更新时,自动生成一个HTML文件.大多数用户是在只读状态.在只读状态下就只去显示HTML文件,节省很多资源.
3.更用CHACHE表技术,把访问量高的记录自动提到CACHE表中.
xiecc:
谢谢dhj1给我提的建议。
在hibernate网站上看到的好多资源几乎都说hibernate的性能不错,而且很多人开发的系统性能也不错,包括dhj1的,看来hibernate无罪,是我们设计得太滥了。
不过还是有点疑问。
1、dhj1提到的第一点很有道理。我们确实在有些关键的地方用了标识符来关联。但是我们这个系统的关联实现太多了,如果所以有东西都用标识符作关联的话,那我们的实体层设计就退化成为面向关系的ER图设计,那我们要Hibernate何用?我用感觉用Hibernate时最大的便利不是在写代码的时候用对象的操作代替SQL语句,而是在建模的时候可以用面向对象的思维把很复杂的逻辑用UML图表示出来,然后直接转化成实体。所以我们在性能影响太大的地方采用了面向对象和关系相结合的方式,但在更多的地方仍然只能采用对象关联的方式。
2、生成静态HTML,对特定的系统确实有用,但我们系统中的数据几乎都是动态的,所以实现起来有困难。而且我也不太清楚这样做的难度有多高,具体怎么实现,请dhj1指点迷津。
3、Cache我们已经采用了。但proxy的用法我至今仍然有点迷糊。
在Hibernate的文档里似乎只要在定义文件里加这么一句就可以了:
但实际使用时我们发现这样做之后,Hibernate取数据时的SQL语句似乎一句都没有少。
我们现在的想法是自已来实现Proxy类,它的接口与实体完全一样,在Proxy里SQL语句来实现取数据操作。不知是否可行?
sanwa:
我在设计系统时,对每个对象图都会采用两套映射模型,
一套用于映射实际的UML,当然包括引用,这套对象图主要用于根据关联对象的属性来查询对象时使用,很少用于更新数据,除非是聚集类.
另一套映射,把对象引用映射为Long型的属性,传递到业务层或表示层,需要相关的对象引用时,再用这个引用来load对象.而且,对于需要load的对象可以使用代理或直接多映射一个简单类来处理.
另外,对于大数据量的查询或表的关联层次较深(超过三层),建议采用jdbc直接处理.
方世玉:
我们现在也正在用hibernate进行项目开发
我认为不是所有相关的表都要做one Many,many one映射的
比如说一个用户和他的定购业务,我们做了双向关联。
但是这个用户的话单就不能和用户做任何关联了。话单表每天都有数十万条,就是做manyone关联也非常吓人,宁可到时候用hql来查询
xiecc:
呵呵,请教sanwa,这两套映射模型如何在一个应用中同时使用?
dhj1:
我用hibernate,是可以减少开发工作量. 特别在开发中或维护过程中对表结构的改动,用HIBERNATE是很方便的.
做了DAO后,对表的父子关系等的处理,通过ID标识,也只是两三句程序语句.操作也很方便,而且更灵活.
生成静态HTML,以后我做过这样的系统,并在XXX省信息港网站上大量使用,性能当然不错,同时1400人在线(真正的在线,不是那种用网页搞个几分钟刷新一次延时的那种在线),性能也不错,开发当然会有一些难度.
我说的CACHE不是说用HIBERNATE的CACHE.而是自已开发的,把访问量高的信息自动放到一个表中去,这个表保证只有100访问量最高的条记录.多于100条记录的就出去了.
sanwa:
举个例子,比如用户的权限,在我们的系统中涉及用户(User)、权限(Acl)、组件(Component)、组件类(componentDomain)等几个对象的关联。
第一套映射图,反映他们的实际关系,即对应UML模型。User和Acl的关联映射为idbag,不要直接映射为set,因为在我设计的关联表中存在代理主键,代理主键在第二套映射图中实际为用户权限的id.
第二套映射图,只映射用户(UserSO)和用户拥有的权限(UserAclSO),是one to many的关系。
后缀SO表示相关类的简单对象映射类。
这样,当客户端需要获取某个用户的所有权限时,直接用第二套映射图。返回的集合中就只包括Acl的id。如果要获取用户对某个组件域的权限,则用第一套映射图,用强大的HQL查询,再转换为第二套映射图返回到客户端。
当更新用户的权限时,也用第二套映射图,直接操作UserSO和UserAclSO,传回更新。
使用类似的设计策略时,对many to many的关联表,都采用代理主键,而不是联合主键,这样,两套映射图都较容易存取数据。只是,多数情况下用第二套映射图。
当然,我的程序架构是Swing + Session Bean + DAO + Hibernate + DB,Swing和Session Bean的通信可以用HTTP或RMI,在我的架构中,lazy loading发挥不了多大的作用,才采取这种策略。如果在lazy loading可以发挥作用的地方,对大多数对象图,是没有必要采取这种策略的。
另外,在我的架构中,swing层有一个组件是可以根据一个ID来加载这个类的属性,直接用jdbc实现的,独立于Hibernate
xiaoyu:
我也说上一句吧。
虽然HB是好,方便,但有关数据库设计的一些性能原则还是要考虑的。
毕竟它只是Mapping而已。
所以也要设置索引等东西。
jxb8901:
上面的proxy和cache没有任何关系,只是用于lazy loading,请看hibernate中文文档第5章:
java代码:
proxy (可选): 指定一个接口,在延迟装载时作为代理使用。你可以在这里使用该类自己的名字。
coolwyc:
为了在性能上得到平行,对many-to-one不直接使用关连,例如:user&ACL,取user时,不取ACL,要取ACL就先取user,再取ACL,毕竟应用取user的频率比取ACL高,没必要在取user是硬要把ACL一起取出来.
还有其他many-to-one都和这个很类似,如果many很少的话就没问题,例如:人&宠物,通常一个人只有少于等于一个宠物,这种情况取人的时候把人跟宠物一起取出对系统影响不大
willmac:
hibernate本身的性能非常好,关键在设计本身
和你的数据库本身,以及你的访问量和数据库的性质
针对不同的环境一定要有不同的设计的,一套设计肯定不能适用于全部的环境
我举个典型例子来说吧
你的hibernate设计可以采用单session的方式,也可以采用多session的方式,应用环境不同,结果也大大的不同。当用户人群少,数据库记录两低的时候,多session的设计是非常有优势的,这就是为什么那么多人
反对使用threadlocal管理session的主要理由把,的却
把两种设计都放在这里,你会发现多session的性效要比
threadlocal的强太多了,并且编程也异常的容易,这种例子,我不再举了,你在这个论坛上都可以下的到。可是
当人群变多,数据库记录海量增长的时候,我们发现问题来了,我们做的应用无限制的吃去内存,应用的响应开始变慢,这是为什么呢?不知道大家是否理解究竟什么是session,其实从根本上说就是一张hashmap,这样大家就好理解了,当不同的用户,同时访问应用的时候,不同的人建立不同的连表,然后释放,而当并发人群急速增长的时候,于是问题就来了。那怎么办,threadlocal没有其他的办法,你要解决一个同步的问题,我们只有一个session,你不能够同时往里读取数据,然后又写入数据的,并发用户这么多,你只可以让他们一个一个得来!!!
哇,这样效率不是太低了,的确,我一开始就说了,小型系统,使用threadlocal绝对是低效的做法的,可是当规模上来了之后,我们再来看,内存不再无限制的开销,cpu德负荷也降下来了,唯一一点发生变化的是,用户的客户端可能多等了0.001秒钟,一个在服务段队列的时间,这就是设计。
讲了一个设计的例子,我们来看多对一,一对多,会让我们应用的效率降低么?
请注意hibernate只是帮助我们完成了mapping 的工作
原来的一对多也好或者多对一也好,本来就存在的,之所以让你觉得效率低,是因为在得到父亲后,还要去把每一个儿子给读出来,可是注意,你只是多执行了一条sql而已,为什么会慢的呢,我想问题还是在设计之上
=====================
1、针对oracle数据库而言,Fetch Size 是设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数,一般设置为30、50、100。Oracle数据库的JDBC驱动默认的Fetch Size=15,设置Fetch Size设置为:30、50,性能会有明显提升,如果继续增大,超出100,性能提升不明显,反而会消耗内存。
即在hibernate配制文件中进行配制:
1
2
3 org.hibernate.dialect.Oracle9Dialect
4 false
5
7
9 50
10
11 Fetch Size设的越大,读数据库的次数越少,速度越快;Fetch Size越小,读数据库的次数越多,速度越慢。
2、如果是超大的系统,建议生成htm文件。加快页面提升速度。
3、不要把所有的责任推在hibernate上,对代码进行重构,减少对数据库的操作,尽量避免在数据库查询时使用in操作,以及避免递归查询操作,代码质量、系统设计的合理性决定系统性能的高低。
4、 对大数据量查询时,慎用list()或者iterator()返回查询结果,
(1). 使用List()返回结果时,Hibernate会所有查询结果初始化为持久化对象,结果集较大时,会占用很多的处理时间。
(2). 而使用iterator()返回结果时,在每次调用iterator.next()返回对象并使用对象时,Hibernate才调用查询将对应的对象初始化,对于大数据量时,每调用一次查询都会花费较多的时间。当结果集较大,但是含有较大量相同的数据,或者结果集不是全部都会使用时,使用iterator()才有优势。
5、在一对多、多对一的关系中,使用延迟加载机制,会使不少的对象在使用时方会初始化,这样可使得节省内存空间以及减少数据库的负荷,而且若PO中的集合没有被使用时,就可减少互数据库的交互从而减少处理时间。
6、对含有关联的PO(持久化对象)时,若default-cascade="all"或者 “save-update”,新增PO时,请注意对PO中的集合的赋值操作,因为有可能使得多执行一次update操作。
7、 对于大数据量新增、修改、删除操作或者是对大数据量的查询,与数据库的交互次数是决定处理时间的最重要因素,减少交互的次数是提升效率的最好途径,所以在开发过程中,请将show_sql设置为true,深入了解Hibernate的处理过程,尝试不同的方式,可以使得效率提升。尽可能对每个页面的显示,对数据库的操作减少到100----150条以内。越少越好。
===========================
HIBERNATE性能测试(LOAD10000条记录的简单测试 仅供参考) |
|
|
|
性能测试: 对一个9036条记录的表进行load测试,表字段有8个,有一个字段是text型,每条记录的该字段约有1300多字节,其余都是简单字段 软硬件:AMD XP1600+, 512M内存(测试时内存有余),winXP SP1, JDK1.4.2_06, Mysql4.1.9,InnoDB,GBK, mm.3.1.6驱动, tomcat5.0.28(启动设置最大内存256M,最小内存64M),hibernate2.1.8, 打开二级缓存Ehcache(配置二级缓存最多10000个对象)关闭所有hibernate log信息和sql输出 方法1:先用 List idList = session.find("select o.id from MyClass o");找出idList然后 list.add(session.load(MyClass.class, (Long)idList.get(i)));组成list 方法2:直接用hibernate的session.find(from MyClass )得到list 方法3:直接用hibernate的session.iterator(from MyClass ),然后list = new ArrayList(9100); while(iter.hasNext()) { list.add(iter.next()); }构造list 每个方法连续调用5次。(第1次调用是没有二级缓存的结果,第2次以后是有二次缓存的结果) 结果如下: 结束第1次loadAll1()方法, 用时15763ms~14762ms 结束第2次loadAll1()方法, 用时771ms~580ms 结束第3次loadAll1()方法, 用时631ms~611ms 结束第4次loadAll1()方法, 用时641ms~591ms 结束第5次loadAll1()方法, 用时601ms~551ms 结束第1次loadAll2()方法, 用时2965ms~3025ms 结束第2次loadAll2()方法, 用时2854ms~2950ms 结束第3次loadAll2()方法, 用时2613ms~ 结束第4次loadAll2()方法, 用时2815ms~ 结束第5次loadAll2()方法, 用时2653ms~ 结束第1次loadAll3()方法, 用时16664ms~16514ms 结束第2次loadAll3()方法, 用时651ms~661ms 结束第3次loadAll3()方法, 用时621ms~591ms 结束第4次loadAll3()方法, 用时571ms~571ms 结束第5次loadAll3()方法, 用时641ms~631ms
分析: 方法1和方法3在访问数据库的策略是一样的,所以运行时间基本差不多。(方法3稍慢一点可能是iterator遍历再组成list用的时间较多)这两种方法第一次运行都要进行9036+1次数据库访问,所以时间最长。而第二次以后运行只需访问一次数据库,并只取出数据的id不取别的字段,因为此时所有对象都已经存在二级缓存中,剩下的是访问二级缓存组成list,所以速度最快。 而方法2每次都会访问数据库load所有数据的所有字段,打开cache的log信息,可以看出第2次以后会通过二级缓存得到数据,但是由于load所有字段占用时间占了此方法较大比例的开销,所以,即使访问二级缓存,性能提高也非常微小,每次运行的时间花费几乎一样。
注意事项: (1)不要在机器运行较长时间后运行tomcat进行测试,特别是机器启动后频繁地开启tomcat,java程序,打开较大应用程序占用很多内存的时候。那时候测试结果一定不准。比如:我昨天晚上测试先用方法1的时间是200S,60S,后运行方法2,竟然用了8000S,200S! (2)设置log4j的LOG级别,不要hibernate和sql的信息。时间会减少很多。(log4j的配置文件难道有这个规矩:一行结尾不能有空格的说???FT!!!) (3)在tomcat启动时进行测试,并在测试方法前调用一个会用到hibernate的方法,让hibernate初始化先完成
|
===================================
优化hibernate性能的几点建议发文时间:2006-3-18 点击数:
15
|
摘要: 有时候,在Java应用程序开发中,如:远程监控或远程教学,常常需要对计算机的屏幕进行截取,由于屏幕截取是比较接近操作系统的操作,在Windows操作系统下,该操作几乎成了VC、VB等的专利,事实上,使用Java JDK1.4 的Robot对象,来完成"屏幕截取操作,更加简单。Java JDK1.4 的Robot对象,该对象可以完成对"屏幕"像素的拷贝,完成屏幕图像截取操作。Java应用程序中可以直接调用此对象,完成对特定应用程序的屏幕截取,如果将此功能配合网络,便可以轻而易举地实现远程服务器屏幕的监视。本文向大家介绍如何用Java构建屏幕"照相机"并实现远程服务器屏幕的监视,并给出了相应的Java源代码。 关键词: Java, Robot, 屏幕截取 1 引言 在Java应用软件演示或相关教学培训,或远程监控过程中,我们常常要截取软件运行GUI界面,并将其保存到一个或一系列图像文件中。目前,在Windows平台下,有关屏幕截取的工具软件有许多,比如:HyperCam等,当然还可以直接利用Windows操作系统支持的屏幕拷贝Print Screen键,将屏幕拷贝到剪贴板,在保存为图像文件。这些工具软件一定要屏幕截取者,在操作过程中要"精力集中"并且"伺机捕获"所需要的软件运行界面。事实上,有时候我们需要Java应用程序,自动对运行的GUI界面进行"拍照",比如:一台计算机要获取网络上另一台计算机(可能是网络服务器)正在运行的GUI界面,要看看对方计算机上软件运行情况。这就需要在Java应用程序中,自动将运行的GUI界面保存到一个图像文件中,然后通过网络传输到另一台计算机上。而上述HyperCam等工具软件无法与我们的Java应用融合为一体。因此,我们需要在Java应用程序中编写一个屏幕"照相机"。 2 Java屏幕"照相机"的编写原理 "屏幕的截取"是比较接近操作系统底层的操作,在Windows平台下,该操作似乎成了VC、VB等语言开发的专利。事实上,"屏幕的截取"在Java应用程序中,及其简单,核心代码只需要几行。在Java JDK1.4 中提供了一个"机器人"Robot类。该类用于产生与本地操作系统有关的底层输入、测试应用程序运行或自动控制应用程序运行。Robot类提供了一个方法:.createScreenCapture(..),可以直接将全屏幕或某个屏幕区域的像素拷贝到一个BufferedImage对象中,我们只需要将该对象写入到一个图像文件之中,就完成了屏幕到图像的拷贝过程。 3 Java屏幕"照相机"的实现 为了构造一个比较完善的Java屏幕"照相机",我们构造了一个GuiCamera JavaBean,其源代码和说明如下:package Camera; import java.awt.image.BufferedImage; import java.io.*; import javax.imageio.*; import java.awt.*; /******************************************************************* * 该JavaBean可以直接在其他Java应用程序中调用,实现屏幕的"拍照" * This JavaBean is used to snapshot the GUI in a * Java application! You can embeded * it in to your java application source code, and us * it to snapshot the right GUI of the application * @see javax.ImageIO * @author liluqun (liluqun@263.net) * @version 1.0 * *****************************************************/ public class GuiCamera { private String fileName; //文件的前缀 private String defaultName = "GuiCamera"; static int serialNum=0; private String imageFormat; //图像文件的格式 private String defaultImageFormat="png"; Dimension d=Toolkit.getDefaultToolkit().getScreenSize(); /**************************************************************** * 默认的文件前缀为GuiCamera,文件格式为PNG格式 * The default construct will use the default * Image file surname "GuiCamera", * and default image format "png" ****************************************************************/ public GuiCamera() { fileName = defaultName; imageFormat=defaultImageFormat; } /**************************************************************** * @param s the surname of the snapshot file * @param format the format of the image file, * it can be "jpg" or "png" * 本构造支持JPG和PNG文件的存储 ****************************************************************/ public GuiCamera(String s,String format) { fileName = s; imageFormat=format; } /**************************************************************** * 对屏幕进行拍照 * snapShot the Gui once ****************************************************************/ public void snapShot() { try { //拷贝屏幕到一个BufferedImage对象screenshot BufferedImage screenshot = (new Robot()).createScreenCapture(new Rectangle(0, 0, (int) d.getWidth(), (int) d.getHeight())); serialNum++; //根据文件前缀变量和文件格式变量,自动生成文件名 String name=fileName+String.valueOf(serialNum)+"."+imageFormat; File f = new File(name); System.out.print("Save File "+name); //将screenshot对象写入图像文件 ImageIO.write(screenshot, imageFormat, f); System.out.print("..Finished!\n"); } catch (Exception ex) { System.out.println(ex); } } } 4 Java屏幕"照相机"的应用 直接使用上述GuiCamera JavaBean,构造一个对象,在需要截取屏幕的地方,调用一下这个对象的.snapShot()方法即可对屏幕进行自动"拍照"!由于对屏幕的截取是程序自动进行的,我们无需象使用HyperCam工具软件那样,在手工操作过程中要"精力集中"并且"伺机捕获"所需要的软件运行界面了。 如:GuiCamera cam= new GuiCamera("d:\\Hello", "png"); cam.snapShot(); 就可以的到文件名为:Hello**.png等一系列所截取的屏幕图像文件。 上述代码旨在"抛砖引玉",Java应用程序开发人员,可以在此基础上,如果将此GuiCamera JavaBean与增加多线程和网络功能,可以实现远程监控网络上另一台计算机屏幕。
|
====================================
Hibernate有哪些优化查询性能的手段?
Hibernate主要从以下几方面来优化查询性能:
1.降低访问数据库的频率,减少select语句的数目。实现手段包括:
(1) 使用迫切左外连接或迫切内连接检索策略。 (2) 对延迟检索或立即检索策略设置批量检索数目。 (3) 使用查询缓存。
2.避免多余加载程序不需要访问的数据。实现手段包括: (1) 使用延迟检索策略 (2) 使用集合过滤。
3.避免报表查询数据占用缓存。实现手段为利用投影查询功能,查询出实体的部分属性。
4.减少select语句中的字段,从而降低访问数据库的数据量。实现手段为利用Query的iterate()方法。
|
阅读(1067) | 评论(0) | 转发(0) |