Hibernate基于配置文件(十七)缓存计策

Hibernate基于配置文件(十七)缓存策略

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发出查询语句

   

Hibernate基于配置文件(十七)缓存计策
 
Hibernate基于配置文件(十七)缓存计策
 


    <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();
		}
	}
}