hibernate缓存策略---调整性能。
主要目的:提高查询效率
从内存中获取对象,不需要与数据库进行交互---提高查询性能
缓存:
*一级缓存 session级别 只在session打开有效 生命周期:与session相关
*二级缓存 sessionFactory级别(全局缓存)
*查询缓存 sessionFactory级别(全局缓存)
缓存的命中:
通过什么key放到内存中,就通过什么key到内存中取
一级、二级缓存,缓存的是key是ID,缓存的value是实体对象
查询缓存,缓存的key是HQL语句与参数,缓存的value:
查询的是普通结果集,则缓存value为这些结果集
特殊:查询的是实体对象,则缓存value为实体对象的ID列表[实体对象被放到了二级缓存中]
---------------------------------------------------------------------------------------
一级缓存---缓存对象:持久化对象:
hibernate中默认的缓存机制,不能取消,能对其进行管理
比如:
session.save() session.load() session.get() 加入到一级缓存
session.evict() session.clear() session.close() 从一级缓存中清除
两个特点:
*session级别 (session关闭后内存中就不存在了)
*缓存实体对象
get/load/list/iterate方法会把加载的实体对象放入一级缓存
get/load/iterate方法会利用缓存
即在加载实体对象之前,会先判断缓存中是否存在该实体对象(根据id),如果不存在,则发出查询语句
list方法不会利用缓存,每次使用list查询,都会发出查询语句
结合list+iterator实现对缓存的利用
首先使用list将数据加载到内存中,list只发出一条查询语句即可
session有效期内再使用iterate,就能利用缓存进行数据获取了。
此时iterate只会发出一条查询语句查询id列表,对象的获取直接从缓存中取,从而不会发生N+1现象
session.save() session.update() 也会将对象放入到一级缓存中
一级缓存:内存中处于持久化状态的对象
一级缓存的管理(重点理解):
*session.evict() 将一级缓存中某个实体对象清除出去
*session.clear() 清除一级缓存中所有的实体对象(flush + clear 做批量数据插入)
*session.close() session关闭,一级缓存中的实体对象全部无效
*flush 把一级缓存中对象属性更新到数据库记录中
强制hibernate即时执行insert update delete 操作
(如果需要hibernate按照程序逻辑进行DML操作,必须使用flush,
否则hibernate会按默认规则:commit的时候进行批量数据提交与更新
而批量操作不会按照程序逻辑执行,会批量执行完insert再批量执行update)
应用:目录树结构存储是对treeId的保存操作就需要使用flush强制hibernate即时更新数据
*refresh 把数据库中的记录同步到一级缓存中
大批量数据的插入:
hibernate一级缓存策略--凡是持久化对象就会放入一级缓存中
所以,在大数据批量插入时,必须及时清空缓存
save()+flush()+clear() 防止内存溢出[id生成策略不能使native]
--------------------------------------------------------------------------------------
二级缓存、查询缓存:
hibernate通过第三方框架实现,可以灵活配置[只用二级缓存、只用查询缓存、两个都用、都不用]
二级缓存:
能够跨越不同的session而存在(一级缓存是与session绑定的,session关闭缓存便清空)
1.sessionFactory级别的缓存
2.缓存实体对象[保存内存中实体对象的引用,一级缓存被清除,但内存中的对象仍被二级缓存持有引用]
使用步骤:
1.启用(配置) hibernate.cfg.xml
<property name="hibernate.cache.use_second_level_cache">true</property>
2.指定缓存策略提供商(配置)
<property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
实际开发中,需要使用专业的缓冲提供商的缓存策略!hashtable仅用于测试!
3.指定哪些实体类对象需要使用二级缓存,以及如何使用缓存(配置)
可以在hibernate.cfg.xml中指定
<class-cache usage="read-write" class="org.leadfar.hibernate.model.ContactPerson"/>
也可以在实体类对应的配置文件中进行配置
比如在ContactPerson.hbm.xml中配置<cache usage="read-write"/>
使用session与二级缓存的交互:
session.setCacheMode(CacheMode.NORMAL);
NORMAL:表示查询的时候会利用二级缓存(get)、查询结果会放入二级缓存(put)
清除二级缓存中某个类型的实体对象
session.getSessionFactory().getCache().evictEntityRegion(ContactPerson.class);
hibernate查询机制:
1.首先到一级缓存中找
2.如果该实体类启用了二级缓存机制,则到二级缓存中找
3.如果也没有,则发出查询语句
--------------------------------------------------------------------------------
查询缓存
1.HQL语句查询部分属性
2.sessionFactory级别
使用步骤:
1.启用(配置) hibernate.cfg.xml
<property name="hibernate.cache.use_query_cache">true</property>
2.指定缓存策略提供商(配置) --与二级缓存使用同一个缓存提供商[hibernate不能同时使用两个不同的提供商]
3.编程的时候指定哪些查询需要使用查询缓存(编程)
每次查询都需设置:query.setCacheable(true);
清除查询缓存:
session.getSessionFactory().getCache().evictDefaultQueryRegion();
注意:
慎用查询缓存
hql查询语句+查询参数 才唯一锁定一条查询,如果两者之一变化,则在内存中无法命中
所以,只有在hql语句及参数固定的情况下,才建议使用查询缓存
否则,容易导致内存溢出。。。
因为一旦某个参数变化了,就会生成一个新的对象,并放到查询缓存中,这样缓存会占用很大空间
如果要使用查询缓存,一定要配合二级缓存来使用!
当查询实体对象又使用查询缓存的时候,如果二级缓存被关闭,list()会发生N+1问题
因为查询缓存中存放的是ID列表,根据ID去二级缓存中找不到数据,就会按照ID发出查询语句
<class name="org.leadfar.hibernate.model.ContactPerson" table="t_person" >
<!-- 配置ContactPerson对象使用二级缓存 -->
<cache usage="read-write"/>
package org.leadfar.hibernate.model; import java.io.File; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Random; import junit.framework.TestCase; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.hibernate.cfg.Configuration; public class TestHQL extends TestCase { public void save_person() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { //开启事务 session.beginTransaction(); Random r = new Random(); for(int i=0;i<100;i++) { ContactPerson cp = new ContactPerson("李四"+i); cp.setAge(r.nextInt(99)); cp.setBirthday(new Date()); session.save(cp); } //提交事务 session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); //出现异常,回滚事务 session.getTransaction().rollback(); } finally { //关闭session session.close();//session关闭之后,user对象处于离线Detached状态 } } /** * 一级缓存有效期:session有效 * session.get()查询出来的对象被放到一级缓存中 * 在session有效期内,访问同一个对象将不再发出查询语句,直接到一级缓存中通过ID获取实体对象 * 一级缓存中的key:id,value:实体对象 */ public void cache_level_1_01() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); //hibernate发出查询语句,把id=1的实体对象加载到内存 ,并放到一级缓存中 ContactPerson cp1 = (ContactPerson)session.get(ContactPerson.class, 1); //对同一个对象的访问,不发出查询语句,直接从一级缓存中取 ContactPerson cp2 = (ContactPerson)session.get(ContactPerson.class, 1); //对同一个对象的访问,不发出查询语句,直接从一级缓存中取 //由于使用的是get()获取到的对象,这里使用load()从一级缓存中取出的也是普通对象,而不是代理对象 ContactPerson cp3 = (ContactPerson)session.load(ContactPerson.class, 1); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * session关闭之后,访问同一个对象会再次发出查询语句 * 因为一级缓存中的数据在session关闭后,就被清空了 */ public void cache_level_1_02() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); //hibernate发出查询语句,把id=1的实体对象加载到内存 ,并放到一级缓存中 ContactPerson cp1 = (ContactPerson)session.get(ContactPerson.class, 1); //由于一级缓存中已经缓存了id=1的实体对象,再次获取该实体对象,不会发出查询语句 ContactPerson cp2 = (ContactPerson)session.get(ContactPerson.class, 1); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } //打开一个新的session session = sfactory.openSession(); try { session.beginTransaction(); //由于是新的session,一级缓存中并没有id=1的实体对象,所以会发出查询语句 ContactPerson cp4 = (ContactPerson)session.get(ContactPerson.class, 1); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * list()会把查询的结果放入到一级缓存中 */ public void cache_level_1_03() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); List<ContactPerson> cps = session.createQuery("from ContactPerson").list(); //不会发出查询语句 ContactPerson cp1 = (ContactPerson)session.load(ContactPerson.class, 2); ContactPerson cp2 = (ContactPerson)session.get(ContactPerson.class, 30); ContactPerson cp3 = (ContactPerson)session.load(ContactPerson.class, 66); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * list()不会利用一级缓存 */ public void cache_level_1_04() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); List<ContactPerson> cps = session.createQuery("from ContactPerson").list(); //不会发出查询语句 ContactPerson cp1 = (ContactPerson)session.load(ContactPerson.class, 2); ContactPerson cp2 = (ContactPerson)session.get(ContactPerson.class, 30); ContactPerson cp3 = (ContactPerson)session.load(ContactPerson.class, 66); //list()不会利用缓存,再次发出查询语句 List<ContactPerson> cps2 = session.createQuery("from ContactPerson").list(); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * iterate() 存在N+1问题,但是其会利用缓存 * 由于iterate会利用缓存,再次使用iterate时将只发出一条查询id列表的语句,剩余N条查询语句不再发出 * 而是直接从缓存中获取对象 */ public void cache_level_1_05() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); //共发出:N+1 //iterate()查询出所有对象的id列表---1条查询语句 Iterator<ContactPerson> it = session.createQuery("from ContactPerson").iterate(); //迭代过程中,针对每个需要访问的对象再发出一条查询语句---N条查询语句 while(it.hasNext()) { ContactPerson cp = it.next(); //lazy=true,直到访问到对象非id属性才会发出查询语句 System.out.println(cp.getName()+","+cp.getId()); } //共发出:1条(查询ID列表) Iterator<ContactPerson> it2 = session.createQuery("from ContactPerson").iterate(); while(it2.hasNext()) { //iterate会利用缓存 ContactPerson cp = it2.next();//判断缓存是否有需要的对象,没有才会发出查询语句 //所以,不会再发出查询语句,直接从缓存中取 System.out.println(cp.getName()+","+cp.getId()); } session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * list() + iterate() * list()---发出1条查询语句查询所有对象 ,并将数据放入缓存(第一次使用iterate会发生N+1) * iterate()---发出1条查询语句 查询id列表,再利用缓存获取数据(list已经把数据放入缓存了) */ public void cache_level_1_06() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); List<ContactPerson> cps = session.createQuery("from ContactPerson").list(); //iterate()查询出所有对象的id列表---1条查询语句 Iterator<ContactPerson> it = session.createQuery("from ContactPerson").iterate(); //list()已经将数据放入缓存,iterate会利用缓存,所以,不再发出查询语句 while(it.hasNext()) { ContactPerson cp = it.next();//判断缓存是否有需要的对象,没有才会发出查询语句 System.out.println(cp.getName()+","+cp.getId()); } session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * 持久化对象---一定被放入到了一级缓存中 */ public void cache_level_1_07() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); ContactPerson cp = new ContactPerson(); cp.setAge(100); cp.setName("aa"); //对象状态更新为持久化对象 session.save(cp); //不会发出查询语句 ContactPerson cp2 = (ContactPerson)session.get(ContactPerson.class, cp.getId()); System.out.println(cp2.getId()+","+cp.getName()+","+cp.getAge()); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * 对一级缓存的管理:refresh() * refresh()操作会针对指定对象再次发出查询语句, * 获取其属性并将一级缓存中的持久化对象属性进行更新 * 保持与数据库记录的一致 */ public void cache_level_1_08() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); //查询某个对象的属性,get()会将结果放入到一级缓存中 ContactPerson cp = (ContactPerson)session.get(ContactPerson.class, 1); System.out.println("原始记录:name="+cp.getName()); //改变持久化对象的属性 cp.setName("xx"); System.out.println("改变持久化对象的属性:name="+cp.getName()); //refresh:再次发出查询语句,获取对象最新的数据,并更新内存中的持久化对象属性 session.refresh(cp); System.out.println("刷新持久化对象属性:name="+cp.getName()); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * 对一级缓存的管理:flush() * 某些情况下按照hibernate的性能优化方案--批量操作,将无法按照程序逻辑进行执行,所以需要flush * 强制hibernate马上更新数据 * 而不是等到commit的时候进行批量执行 * 因为批量执行的时候,不会按照程序逻辑进行数据库的DML操作 * 而程序逻辑一旦被打乱,将导致错误发生 * * 何时使用flush: * 如果程序逻辑必须严格按照书写顺序执行,那么必须使用flush操作 * 原因: * hibernate性能优化方案之一:批量数据提交,集中操作 * 但是,批量操作对应的执行顺序与程序逻辑不符 * 比如:需要每insert一条记录,马上update此条记录(需要先生成id,再根据id更新某个字段) * 但是hibernate批量数据执行的顺序是:批量insert,再批量update * 最终就会导致程序运行错误 */ public void cache_level_1_09() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); save(session,new File("e:/apache-tomcat-6.0.29"),null); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } //保存一个文件目录树到数据库---parent记录父子关系 private void save(Session session,File file,Node parent) { Node node = new Node(); node.setName(file.getName()); node.setParent(parent);//关键:在多的一方建立关联 node.setTreeId(""); session.save(node); if(parent!=null) { node.setTreeId(parent.getTreeId()+"|"+node.getId()); } else { node.setTreeId(node.getId()+""); } session.flush(); if(file.isDirectory()) { File[] files = file.listFiles(); if(files!=null) { for(File f : files) { save(session,f,node); } } } } /** * ContactPerson类配置二级缓存策略 * 在不同的session中获取实体对象,而没有发出查询语句 * 原因就在于:二级缓存中已存放了该对象的引用,可以在二级缓存中进行获取 */ public void cache_level_2_01() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); //hibernate发出查询语句,把id=1的实体对象加载到内存 ,并放到一级缓存中[默认] //同时会在二级缓存中增加对该实体对象的引用 ContactPerson cp1 = (ContactPerson)session.get(ContactPerson.class, 1); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } //打开一个新的session session = sfactory.openSession(); try { session.beginTransaction(); //虽然是新的session,但是id=1的ContactPerson对象已经被存放到二级缓存中 //所以不会发出查询语句 ContactPerson cp4 = (ContactPerson)session.get(ContactPerson.class, 1); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * 可以使用session对二级缓存中的对象进行管理:比如清除掉某种类型的实体对象 * session.getSessionFactory().getCache().evictEntityRegion(ContactPerson.class); * * 可以通过session设置配置了二级缓存策略的对象的缓存方式: * session.setCacheMode(CacheMode.NORMAL); 查询时会利用、查询结果会放入 * session.setCacheMode(CacheMode.PUT); 会将结果放入二级缓存 * session.setCacheMode(CacheMode.GET); 查询时才利用二级缓存 */ public void cache_level_2_02() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); //hibernate发出查询语句,把id=1的实体对象加载到内存 ,并放到一级缓存中 ContactPerson cp1 = (ContactPerson)session.get(ContactPerson.class, 1); //由于一级缓存中已经缓存了id=1的实体对象,再次获取该实体对象,不会发出查询语句 ContactPerson cp2 = (ContactPerson)session.get(ContactPerson.class, 1); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } //打开一个新的session session = sfactory.openSession(); try { session.beginTransaction(); //从二级缓存中将指定类型的对象清除 session.getSessionFactory().getCache().evictEntityRegion(ContactPerson.class); //由于二级缓存中已经清除了对ContactPerson对象的引用,所以会再次发出查询语句 ContactPerson cp4 = (ContactPerson)session.get(ContactPerson.class, 1); session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * 查询缓存的使用 */ public void cache_query_3_01() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); String hql = "select p.name,p.age from ContactPerson p where p.name like ?"; Query query = session.createQuery(hql);//声明使用查询缓存 query.setCacheable(true);//打开查询缓存 query.setParameter(0, "%1%"); List<Object[]> cps = query.list(); for(Object[] cp : cps) { System.out.println(cp[0]+","+cp[1]); } session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } //打开一个新的session session = sfactory.openSession(); try { session.beginTransaction(); String hql = "select p.name,p.age from ContactPerson p where p.name like ?"; Query query = session.createQuery(hql); query.setCacheable(true); query.setParameter(0, "%1%"); //由于前一个session中已将结果集放入查询缓存, //对于同一个hql语句和参数,这里不会发出查询语句 List<Object[]> cps = query.list(); for(Object[] cp : cps) { System.out.println(cp[0]+","+cp[1]); } session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * 清除查询缓存 */ public void cache_query_3_02() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); String hql = "select p.name,p.age from ContactPerson p where p.name like ?"; Query query = session.createQuery(hql); query.setCacheable(true); query.setParameter(0, "%1%"); List<Object[]> cps = query.list(); for(Object[] cp : cps) { System.out.println(cp[0]+","+cp[1]); } session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } //打开一个新的session session = sfactory.openSession(); try { session.beginTransaction(); //清除查询缓存 session.getSessionFactory().getCache().evictDefaultQueryRegion(); String hql = "select p.name,p.age from ContactPerson p where p.name like ?"; Query query = session.createQuery(hql); query.setCacheable(true); query.setParameter(0, "%1%"); //由于结果集已从查询缓存中清空,这里会重新发出查询语句 List<Object[]> cps = query.list(); for(Object[] cp : cps) { System.out.println(cp[0]+","+cp[1]); } session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } /** * list方法也可能发生N+1问题 */ public void cache_query_3_03() throws Exception { Configuration cfg = new Configuration().configure(); SessionFactory sfactory = cfg.buildSessionFactory(); Session session = sfactory.openSession(); try { session.beginTransaction(); //查询实体对象 String hql = "select p from ContactPerson p where p.name like ?"; Query query = session.createQuery(hql); query.setCacheable(true); query.setParameter(0, "%1%"); List<ContactPerson> cps = query.list(); for(ContactPerson cp : cps) { System.out.println(cp.getId()+","+cp.getName()); } session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } //打开一个新的session session = sfactory.openSession(); try { session.beginTransaction(); //查询实体对象,使用查询缓存进行存储,则查询缓存中存放的是对象的ID列表 //如果二级缓存没有打开,会根据ID列表发出N条查询语句---list的N+1问题 //实际发出N条,1条为查询ID列表,但ID列表已在查询缓存中存在了 //所以,查询实体对象,并且使用查询缓存时,必须同时使用二级缓存 //因为,实体对象会被放到二级缓存中,查询缓存中只存放对应的ID列表 String hql = "select p from ContactPerson p where p.name like ?"; Query query = session.createQuery(hql); query.setCacheable(true); query.setParameter(0, "%1%"); //由于结果集已从查询缓存中清空,这里会重新发出查询语句 List<ContactPerson> cps = query.list(); for(ContactPerson cp : cps) { System.out.println(cp.getId()+","+cp.getName()); } session.getTransaction().commit(); } catch(Exception e) { e.printStackTrace(); session.getTransaction().rollback(); } finally { session.close(); } } }