Hibernate性能优化策略
使用乐观锁在表中加一个version的字段来解决并发性问题
悲观锁:就是多个人同时想修改某个数据,
第一个人读出数据,数据库就锁定了其他人对该数据的修改和删除,
但允许其它人查询该数据,直到第一个人提交保存后其它人才能修改,
否则其它人在提交时系统会一直等待第一个人完成提交
乐观锁:乐观锁需要在表中加一version(版本)字段,并要在对应的类中配置
就是多个人同时想修改某个数据,如果多人同时读出数据,那么他们的版本号都相同比如是1,
然后其中有一个人最先提交了修改,那么表中版本字段会自动加一,
当其它人提交时hibernat会自动判断自己的版本号是否与数据库中的版本号相同,
如果低于数据库中的版本号那么就不允许提交。
(如果是使用jdbc开发那么用户也要自己写个判断代码进行对版本号的判断)
采用hibernate_session代码测试:
一级缓存(用hibernate_one-to-many代码测试)
1、一级缓存很短,和session的生命周期一致,随着session的关闭而消失
*load/get/iterate(查询实体对象)可以使用缓存数据
2、一级缓存它缓存的是实体对象
3、如果管理缓存,如session.clear()/session.evict()
4、如何避免一次性大批量实体数据插入内存溢出的问题?
*先执行flush,在用clear清除缓存
//发出两次load()第一次发一条sql第二次不发,说明它是先在一级缓存找数据,如果没有再到数据库找。
/**
*发出两次load查询
*
*/
Studentstudent=(Student)session.load(Student.class,1);
System.out.println("学生姓名:"+student.getName());
//因为有一级缓存,load方法使用一级缓存,所以本次查询不再发出sql
student=(Student)session.load(Student.class,1);
System.out.println("学生姓名:"+student.getName());
-------------------------------------------------------------------------------------------
//发出两次get()同上
/**
*发出两次get查询
*
*/
Studentstudent=(Student)session.get(Student.class,1);
System.out.println("学生姓名:"+student.getName());
//因为有一级缓存,get方法使用一级缓存,所以本次查询不再发出sql
student=(Student)session.get(Student.class,1);
System.out.println("学生姓名:"+student.getName());
--------------------------------------------------------------------------------------------
/**
*发出两次iterate查询实体对象
*
*/
publicvoidtestCache3(){
Sessionsession=null;
try{
session=HibernateUtils.getSession();
Studentstudent=(Student)session.createQuery("fromStudentwhereid=1").iterate().next();
System.out.println("学生姓名:"+student.getName());
//因为有一级缓存,iterate方法使用一级缓存,发出查询id的sql,不再发出查询实体对象的sql
student=(Student)session.createQuery("fromStudentwhereid=1").iterate().next();
System.out.println("学生姓名:"+student.getName());
//发出两次iterate查询实体对象
(iterate会有n+1问题,如果缓存有发查id那1条)
先从数据库查出所有id,再根据ID查数据,所以会有n+1问题,但查实体对象会先在一级缓存查找。
sql:fromstudent
(实体对象就是说查询的不是单个或多个属性查询,而是整个对象,只要你不指定查询属性那就是实体对象)
第一次发两条,有一条查ID,有一条查数据
第二次发一条,只有查ID那条,说明查数据(就是查实体对像)也是先在缓存里找。
//发出两次iterate查询单个或多个属性sql:selectnamefromstudentwhereid=1
//它每次都会发sql说明它不使用一级缓存,只使用查询缓存
------------------------------------------------------------------------------------------------
/**
*发出两次iterate查询普通属性
*
*/
Stringname=(String)session.createQuery("selectnamefromStudentwhereid=1").iterate().next();
System.out.println("学生姓名:"+name);
//Iterate查询普通结果集,一级缓存不会缓存,它也不会发出查询id的sql
name=(String)session.createQuery("selectnamefromStudentwhereid=1").iterate().next();
System.out.println("学生姓名:"+name);
----------------------------------------------------
/**
*打开两次session,调用load测试
*
*/
publicvoidtestCache5(){
Sessionsession=null;
try{
session=HibernateUtils.getSession();
Studentstudent=(Student)session.load(Student.class,1);
System.out.println("学生姓名:"+student.getName());
}catch(Exceptione){
e.printStackTrace();
}finally{
HibernateUtils.closeSession(session);
}
//打开第二个session
try{
session=HibernateUtils.getSession();
//会发出sql,session间是不能共享一级缓存数据的
//因为它会伴随session的生命周期存在和消亡
Studentstudent=(Student)session.load(Student.class,1);
System.out.println("学生姓名:"+student.getName());
}catch(Exceptione){
e.printStackTrace();
}finally{
HibernateUtils.closeSession(session);
}
}
-----------------------------------------------
/**
*先执行save,再执行load进行测试
*
*/
session=HibernateUtils.getSession();
session.beginTransaction();
Studentstudent=newStudent();
student.setName("张三");
Serializableid=session.save(student);
//因为save会将实体对象的数据缓存到session中
//所以再次查询相同数据,不会发出sql
StudentnewStudent=(Student)session.load(Student.class,id);
System.out.println("学生姓名:"+newStudent.getName());
session.getTransaction().commit();
---------------------------------------------------------------------------
/**
*执行session.clear()或session.evict()方法后,再调用load
*/
Studentstudent=(Student)session.load(Student.class,1);
System.out.println("学生姓名:"+student.getName());
//管理session缓存(一级缓存机制无法取消的,但可以管理缓存,如:clear,evict方法)
session.evict(student);
//session.clear();
//发出sql,因为缓存中的student实体类,已经被逐出
student=(Student)session.load(Student.class,1);
System.out.println("学生姓名:"+student.getName());
-------------------------------------------------------------------------
publicvoidtestCache8(){
Sessionsession=null;
try{
session=HibernateUtils.getSession();
session.beginTransaction();
for(inti=0;i<10000;i++){
Studentstudent=newStudent();
student.setName("Student_"+i);
session.save(student);
//每100条数据就强制session将数据持久化
//同时清空缓存,以避免在大量的数据下,造成内存溢出
if(i%100==0){
session.flush();
session.clear();
}
}
session.getTransaction().commit();
}catch(Exceptione){
e.printStackTrace();
session.getTransaction().rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
==============================================================================
==============================================================================
二级缓存
定义步骤:
1、打开缓存,在hibernate.cfg.xm中加入:
<propertyname="hibernate.cache.use_second_level_cache">true</property>
2、指定缓存策略提供商,在hibernate.cfg.xm中加入:
<propertyname="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
3、拷贝echcahe.xml到src下,可以针对不同的策略配置缓存
4、指定那些类使用缓存(两种方式)
*在hibernate.cfg.xml
*在映射文件中
二级缓存中存储的也是实体对象,他们都属于SessionFactory级别,
是全局的,伴随SessionFactory的生命周期存在和消亡
用打开两个session代码测试
二级缓存只针对实体对象,不是实体对象它不会缓存
建议少用。
ehcache.jar+ehcache.xml拷到jar目录(ehcache.xml文件在hibernate包里有.)
ehcache.xml这个配置文件在ehcache.jar包里本身有一个配置文件,但在用jbpm时会出错,所以要将这个拷到src或其它目录下
在hibernate.hbm.xml中的配置代码:
<!--是否启用二级缓存-->
<propertyname="hibernate.cache.use_second_level_cache">true</property>
<!--设置缓存策略提供商-->
<propertyname="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<class-cacheclass="com.bjsxt.hibernate.Student"usage="read-only"/>
/**
*调用了次load,第一次调用完成后,清除sessionFactory中的二级缓存数据,
*再开启一个session,调用load
*
*/
publicvoidtestCache2(){
Sessionsession=null;
try{
session=HibernateUtils.getSession();
Studentstudent=(Student)session.load(Student.class,1);
System.out.println("学生姓名:"+student.getName());
}catch(Exceptione){
e.printStackTrace();
}finally{
HibernateUtils.closeSession(session);
}
//管理二级缓存
SessionFactoryfactory=HibernateUtils.getSessionFactory();
factory.evict(Student.class);
//打开第二个session
try{
session=HibernateUtils.getSession();
//因为二级缓存已经被清空,所以本次查询将发出一条新的sql
Studentstudent=(Student)session.load(Student.class,1);
System.out.println("学生姓名:"+student.getName());
}catch(Exceptione){
e.printStackTrace();
}finally{
HibernateUtils.closeSession(session);
}
}
---------------------------------------------
一级缓存与二级缓存的交互:
用factory.cacheMode="get"
==============================================================================
==============================================================================
查询缓存(只对.list()起作用,对iterate()不起作用)
配置:
在hibernate.cfg.xml文件中加入:<propertyname="hibernate.cache.use_query_cache">true</property>
1、针对普通属性结果集的缓存
2、对是实体对象的结果集,只缓存id
3、使用查询缓存,需要打开查询缓存,并且在调用list方法之前需要显示的调用query.setCacheable(true);
查询缓存的生命周期是由hibernate控制的,它的生命周期不一定,比如我查询了10条数据,但这10条数据在数据库中被别人修改了,那么查询缓存就会马上被清掉。如果查询缓存里有多个普通属性对像那么其中任何一个被修改查询缓存都会被清除
一级与二级缓存是对实体对象的,查询缓存是对普通属性的。
如果打开了查询缓存关闭二级缓存查询实例对象测试第二次list.iterator也会产生n+1问题
如果查询缓存与二级缓存同时打开查询实例对象测试第二次list.iterator不会发出任何sql
================================================================================
抓取策略:
抓取策略是对延迟加载的优化
可以配置在单端属性上(many-to-one和one-to-one)和集合属性上(<set>)。
fetch="select"
fetch="join"
fetch="subselect"
参见:hibernate_fetch_6
拷贝hibernate_fetch1项目测试batch-size
针对fetch="select"配置进行优化,加入batch,如:
<classname="com.bjsxt.hibernate.Classes"table="t_classes"batch-size="5">
批量抓取配置:Classes.hbm.xml中加:<class.....batch-size="10">
抓取策略使用默认,也就是fetch="select"
publicvoidtestFetch1(){
Sessionsession=null;
try{
session=HibernateUtils.getSession();
Liststudents=session.createQuery("selectsfromStudentswheres.idin(:ids)")
.setParameterList("ids",newObject[]{1,11,21,31,41,51,61,71,81,91})
.list();
for(Iteratoriter=students.iterator();iter.hasNext();){
Studentstudent=(Student)iter.next();
System.out.println("学生:"+student.getName());
System.out.println("班级:"+student.getClasses().getName());
}
}catch(Exceptione){
e.printStackTrace();
}finally{
HibernateUtils.closeSession(session);
}
}
上面运行结果,如果不加batch-size="10"那么查询每个班都会发一条sql加了之后每查询10条才发一条sql
参见:hibernate_fetch_7
拷贝hibernate_fetch3项目测试(针对集合设置的batch-size)