在项目中使用Hibernate进行大数据量的性能测试
有一些总结
) 在处理大数据量时会有大量的数据缓沖保存在Session的一级缓存中这缓存大太时会严重显示性能所以在使用Hibernate处理大数据量的可以使用sessionclear()或者session Evict(Object) 在处理过程中清除全部的缓存或者清除某个对象
) 对大数据量查询时慎用list()或者iterator()返回查询结果
使用List()返回结果时Hibernate会所有查询结果初始化为持久化对象结果集较大时会占用很多的处理时间
而使用iterator()返回结果时在每次调用iteratornext()返回对象并使用对象时Hibernate才调用查询将对应的对象初始化对于大数据量时每调用一次查询都会花费较多的时间当结果集较大但是含有较大量相同的数据或者结果集不是全部都会使用时使用iterator()才有优势
对于大数据量使用qryscroll()可以得到较好的处理速度以及性能而且直接对结果集向前向后滚动
) 对于关联操作Hibernate虽然可以表达复杂的数据关系但请慎用使数据关系较为简单时会得到较好的效率特别是较深层次的关联时性能会很差
) 对含有关联的PO(持久化对象)时若defaultcascade=all或者 saveupdate新增PO时请注意对PO中的集合的赋值操作因为有可能使得多执行一次update操作
) 在一对多多对一的关系中使用延迟加载机制会使不少的对象在使用时方会初始化这样可使得节省内存空间以及减少的负荷而且若PO中的集合没有被使用时就可减少互数据库的交互从而减少处理时间 数据库
什么叫n+次select查询问题?
在Session的缓存中存放的是相互关联的对象图默认情况下当Hibernate从数据库中加载Customer对象时会同时加载所有关联的Order对象以Customer和Order类为例假定ORDERS表的CUSTOMER_ID外键允许为null图列出了CUSTOMERS表和ORDERS表中的记录
以下Session的find()方法用于到数据库中检索所有的Customer对象
List customerLists=sessionfind(from Customer as c)
运行以上find()方法时Hibernate将先查询CUSTOMERS表中所有的记录然后根据每条记录的ID到ORDERS表中查询有参照关系的记录Hibernate将依次执行以下select语句
select * from CUSTOMERS;
select * from ORDERS where CUSTOMER_ID=;
select * from ORDERS where CUSTOMER_ID=;
select * from ORDERS where CUSTOMER_ID=;
select * from ORDERS where CUSTOMER_ID=;
通过以上条select语句Hibernate最后加载了个Customer对象和个Order对象在内存中形成了一幅关联的对象图参见图
Hibernate在检索与Customer关联的Order对象时使用了默认的立即检索策略这种检索策略存在两大不足
(a) select语句的数目太多需要频繁的访问数据库会影响检索性能如果需要查询n个Customer对象那么必须执行n+次select查询语句这就是经典的n+次select查询问题这种检索策略没有利用SQL的连接查询功能例如以上条select语句完全可以通过以下条select语句来完成
select * from CUSTOMERS left outer join ORDERS
on CUSTOMERSID=ORDERSCUSTOMER_ID
以上select语句使用了SQL的左外连接查询功能能够在一条select语句中查询出CUSTOMERS表的所有记录以及匹配的ORDERS表的记录
(b)在应用逻辑只需要访问Customer对象而不需要访问Order对象的场合加载Order对象完全是多余的操作这些多余的Order对象白白浪费了许多内存空间
为了解决以上问题Hibernate提供了其他两种检索策略延迟检索策略和迫切左外连接检索策略延迟检索策略能避免多余加载应用程序不需要访问的关联对象迫切左外连接检索策略则充分利用了SQL的外连接查询功能能够减少select语句的数目
刚查阅了hibernate的文档
查询抓取(默认的)在N+查询的情况下是极其脆弱的因此我们可能会要求在映射文档中定义使用连接抓取
<set name=permissions
fetch=join>
<key column=userId/>
<onetomany class=Permission/>
</set
<manytoone name=mother class=Cat fetch=join/>
在映射文档中定义的抓取策略将会有产生以下影响
通过get()或load()方法取得数据
只有在关联之间进行导航时才会隐式的取得数据(延迟抓取)
条件查询
在映射文档中显式的声明 连接抓取做为抓取策略并不会影响到随后的HQL查询
通常情况下我们并不使用映射文档进行抓取策略的定制更多的是保持其默认值然后在特定的事务中 使用HQL的左连接抓取(left join fetch) 对其进行重载这将通知 Hibernate在第一次查询中使用外部关联(outer join)直接得到其关联数据 在条件查询 API中应该调用 setFetchMode(FetchModeJOIN)语句
) 对于大数据量新增修改删除操作或者是对大数据量的查询与数据库的交互次数是决定处理时间的最重要因素减少交互的次数是提升效率的最好途径所以在开发过程中请将show_sql设置为true深入了解Hibernate的处理过程尝试不同的方式可以使得效率提升
) Hibernate是以JDBC为基础但是Hibernate是对JDBC的优化其中使用Hibernate的缓沖机制会使性能提升如使用二级缓存以及查询缓存若命中率较高明性能会是到大幅提升
) Hibernate可以通过设置hibernatejdbcfetch_sizehibernatejdbcbatch_size等属性对Hibernate进行优化
hibernatejdbcfetch_size
hibernatejdbcbatch_size
这两个选项非常非常非常重要!!!将严重影响Hibernate的CRUD性能!
C = create R = read U = update D = delete
Fetch Size 是设定JDBC的Statement读取数据的时候每次从数据库中取出的记录条数
例如一次查询万条记录对于Oracle的JDBC驱动来说是不会次性把万条取出来的而只会取出Fetch Size条数当纪录集遍历完了这些记录以后再去数据库取Fetch Size条数据
因此大大节省了无谓的内存消耗当然Fetch Size设的越大读数据库的次数越少速度越快Fetch Size越小读数据库的次数越多速度越慢
这有点像平时我们写程序写硬盘文件一样设立一个Buffer每次写入Buffer等Buffer满了以后一次写入硬盘道理相同
Oracle数据库的JDBC驱动默认的Fetch Size=是一个非常保守的设定根据我的测试当Fetch Size=的时候性能会提升倍之多当Fetch Size=性能还能继续提升%Fetch Size继续增大性能提升的就不显着了
因此我建议使用Oracle的一定要将Fetch Size设到
不过并不是所有的数据库都支持Fetch Size特性例如MySQL就不支持
MySQL就像我上面说的那种最坏的情况他总是一下就把万条记录完全取出来内存消耗会非常非常惊人!这个情况就没有什么好办法了 :(
Batch Size是设定对数据库进行批量删除批量更新和批量插入的时候的批次大小有点相当于设置Buffer缓沖区大小的意思
Batch Size越大批量操作的向数据库发送sql的次数越少速度就越快我做的一个测试结果是当Batch Size=的时候使用Hibernate对Oracle数据库删除万条记录需要秒Batch Size = 的时候删除仅仅需要秒!!!
//
我们通常不会直接操作一个对象的标识符(identifier)因此标识符的setter方法应该被声明为私有的(private)这样当一个对象被保存的时候只有Hibernate可以为它分配标识符你会发现Hibernate可以直接访问被声明为publicprivate和protected等不同级别访问控制的方法(accessor method)和字段(field) 所以选择哪种方式来访问属性是完全取决于你你可以使你的选择与你的程序设计相吻合
所有的持久类(persistent classes)都要求有无参的构造器(noargument constructor)因为Hibernate必须要使用Java反射机制(Reflection)来实例化对象构造器(constructor)的访问控制可以是私有的(private)然而当生成运行时代理(runtime proxy)的时候将要求使用至少是package级别的访问控制这样在没有字节码编入(bytecode instrumentation)的情况下从持久化类里获取数据会更有效率一些
而
hibernatemax_fetch_depth 设置外连接抓取树的最大深度
取值 建议设置为到之间
就是每次你在查询时会级联查询的深度譬如你对关联vo设置了eager的话如果fetch_depth值太小的话会发多很多条sql
Hibernate的Reference之后可以采用批量处理的方法当插入的数据超过时就flush session并且clear
下面是一个测试method
/** */ /**
* 测试成批插入数据的事务处理返回是否成功
*
* @param objPO Object
* @return boolean
*/
public boolean insertBatch( final Object objPO) {
boolean isSuccess = false ;
Transaction transaction = null ;
Session session = openSession()
try {
transaction = sessionbeginTransaction()
for ( int i = ; i < ; i ++ ) {
sessionsave(objPO)
if (i % == ) {
// flush a batch of inserts and release memory
sessionflush()
sessionclear()
}
}
mit()
( transactionwasCommitted:
+ transactionwasCommitted())
isSuccess = true ;
} catch (HibernateException ex) {
if (transaction != null ) {
try {
transactionrollback()
loggererror( transactionwasRolledBack:
+ transactionwasRolledBack())
} catch (HibernateException ex) {
loggererror(exgetMessage())
exprintStackTrace()
}
}
loggererror( Insert Batch PO Error: + exgetMessage())
exprintStackTrace()
} finally {
if (transaction != null ) {
transaction = null ;
}
sessionclose()
}
return isSuccess;
}
这只是简单的测试实际项目中遇到的问题要比这个复杂得多
这时候我们可以让Spring来控制Transaction自己来控制Hibernate的Session随时更新数据
首先利用HibernateDaoSupport类来自定义个方法打开Session
public Session openSession(){
return getHibernateTemplate()getSessionFactory()openSession()
}
然后用打开的Session处理你的数据
protected void doBusiness(Session session) {
while (true) {
//do your business with the opening session
someMethod(session)
sessionflush()
sessionclear()
(good job!)
}
}
每做一次数据操作就更新一次Session这样可以保证每次数据操作都成功否则就让Spring去控制它roll back吧
最后记得关闭Session
Session session = openSession()
doBusiness(session)
sessionclose() // 关闭session