Hibernate part 17:二级缓存
Session级别的缓存,事务范围的
SessionFactory级别的缓存,进程范围的
SessionFactory缓存:
内置:Hibernate自带的,只能缓存一些配置的SQL语句,如命名查询配置在*.hbm.xml中的SQL语句
外置:需要配置第三方插件使用,自己内有实现
二级缓存的结构
二级缓存提供商:
EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持
OpenSymphony `:可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
二级缓存并发访问策略:
非严格读写(Nonstrict-read-write): 不保证缓存与数据库中数据的一致性. 提供 Read Uncommited 事务隔离级别, 对于极少被修改, 而且允许脏读的数据, 可以采用这种策略
读写型(Read-write): 提供 Read Commited 数据隔离级别.对于经常读但是很少被修改的数据, 可以采用这种隔离类型, 因为它可以防止脏读
事务型(Transactional): 仅在受管理环境下适用. 它提供了 Repeatable Read 事务隔离级别. 对于经常读但是很少被修改的数据, 可以采用这种隔离类型, 因为它可以防止脏读和不可重复读
只读型(Read-Only):提供 Serializable 数据隔离级别, 对于从来不会被修改的数据, 可以采用这种访问策略(很强,但是性能低
二级缓存的配置:
1、ehcache依赖jar包:ehcache-1.5.0.jar、commons-logging.jar、backport-util-concurrent.jar
2、在hibernate.cfg.cml配置文件中配置使用二级缓存
<property name="hibernate.cache.use_second_level_cache">true</property>
3、 配置二级缓存提供商
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
4、配置二级缓存的并发策略
位置一:*.hbm.xml中
<hibernate-mapping> <class name="rock.lee.bean.Customer" table="customer" catalog="test" > <cache usage="read-write"/>
位置二:hibernate.cfg.xml
<class-cache usage="read-write" class="rock.lee.bean.Customer"/> <class-cache usage="read-write" class="rock.lee.bean.Order"/> <collection-cache usage="read-write" collection="rock.lee.bean.Customer.orders"/>
5、配置ehcache.xml,解压ehcache-1.5.0.jar中获取
配置完成
案例一:证明二级缓存是存在的
@Test public void test01() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); //SQL Customer c1 = (Customer) session.get(Customer.class, 1); System.out.println(c1); //从一级缓存中获取 Customer c2 = (Customer) session.get(Customer.class, 1); System.out.println(c2); transaction.commit();//session close session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); //从二级缓存中获取 Customer c3 = (Customer) session.get(Customer.class, 1); System.out.println(c3); transaction.commit(); }
修改hibernate.cfg.xml关闭二级缓存, 再次执行查询
<property name="hibernate.cache.use_second_level_cache">false</property>
案例二:类级别缓冲区的散装数据结构
在Customer类中的toString()中调用Object的toString()打印地址
@Override public String toString() { return "Customer [id=" + id + ", name=" + name + ", city=" + city+"]" + super.toString(); }
还是运行案例一的程序,输出结果
Customer [id=1, name=林允儿, city=SH]rock.lee.bean.Customer@3003e926 Customer [id=1, name=林允儿, city=SH]rock.lee.bean.Customer@3003e926//来自一级缓存的对象 Customer [id=1, name=林允儿, city=SH]rock.lee.bean.Customer@4cf221f6//来自二级缓存的对象
案例三:get/load可以对二级缓存读写,Query的list对二级缓存只能写不能读
@Test public void test02() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); //查询SQL,将数据写入二级缓存 List<Customer> list = session.createQuery("from Customer").list(); System.out.println(list.size()); transaction.commit();// session close session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); //不能读取二级缓存数据,执行SQL查询,将数据写入二级缓存 list = session.createQuery("from Customer").list(); System.out.println(list.size()); transaction.commit();// session close session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); //从二级缓存中获取 Customer c1 = (Customer) session.get(Customer.class, 1); System.out.println(c1); transaction.commit(); }
案例四:集合级别缓冲区的存在
@Test public void test03() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); Customer c1 = (Customer) session.get(Customer.class, 1); System.out.println(c1.getOrders()); transaction.commit(); session = HibernateUtils.getCurrentSession(); session.beginTransaction(); Customer c2 = (Customer) session.get(Customer.class, 1); //通过二级换获得Order数据 System.out.println(c2.getOrders()); transaction.commit(); }
案例五:集合级别缓冲区依赖类级别缓冲区
在hibernate.cfg.xml中注释掉Order类级别缓存
<!-- <class-cache usage="read-write" class="rock.lee.bean.Order" /> -->
执行案例四同样的代码,如果Order类级别缓冲区关系,集合缓冲区也无法使用
@Test public void test03() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); Customer c1 = (Customer) session.get(Customer.class, 1); System.out.println(c1.getOrders()); transaction.commit(); session = HibernateUtils.getCurrentSession(); session.beginTransaction(); Customer c2 = (Customer) session.get(Customer.class, 1); //通SQL获得数据 System.out.println(c2.getOrders()); transaction.commit(); }
集合级别的缓冲区只会缓存id,然后去类级别缓冲区里查询数据
案例六:一级缓存数据自动同步到二级缓存
@Test public void test04() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); Customer c1 = (Customer) session.get(Customer.class, 1); System.out.println(c1); //快照比对后自动更新,并且同步到二级缓存 c1.setCity("GD"); transaction.commit(); session = HibernateUtils.getCurrentSession(); session.beginTransaction(); //从二级缓存中获取修改后的数据 Customer c2 = (Customer) session.get(Customer.class, 1); System.out.println(c2); }
案例七:将二级换数据保存到硬盘
保存到硬盘中的位置在ehcache.xml中配置
#默认目录C:\Users\ADMINI~1\AppData\Local\Temp\ <diskStore path="java.io.tmpdir"/>
将D:\ehcache目录作为缓存目录,maxElementsInMemory配置为3
<diskStore path="D:\ehcache"/> <defaultCache maxElementsInMemory="3" 内存中最大元素数量(当内存对数量超过这个数,才会缓存到硬盘) eternal="false" 缓存数据是否永久有效 timeToIdleSeconds="120" 设置对象空闲最长时间 ,超过时间缓存对象如果没用,回收 timeToLiveSeconds="120" 设置对象存活最长时间, 无论对象是否使用,到时间回收 overflowToDisk="true" 当内存缓存数据达到上限,是否缓存到硬盘 maxElementsOnDisk="10000000" 硬盘上最大缓存对象数量 diskPersistent="false" 当jvm结束时是否持久化对象 true false 默认是false diskExpiryThreadIntervalSeconds="120" 专门用于清除过期对象的监听线程的轮询时 memoryStoreEvictionPolicy="LRU" />由于JUnit执行后会System.exit(0),所以磁盘文件大小为0,通过断点,让程序有时间将数据保存到磁盘,加休眠也可以
@Test @SuppressWarnings("unchecked") public void test05() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); System.out.println(System.getProperty("java.io.tmpdir")); List<Order> list = session.createQuery("from Order").list(); System.out.println(list.size()); transaction.commit(); }
案例八:更新时间戳缓冲区
时间戳缓存区域存放了对于查询结果相关的表进行插入, 更新或删除操作的时间戳. Hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期
@Test public void test06() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); Customer c1 = (Customer) session.get(Customer.class, 2); // 不能这么修改,一级缓存会自动同步到二缓存 // c1.setCity("BJ"); session.createSQLQuery("update customer set city='BJ' where id=1").executeUpdate(); transaction.commit(); session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); //判断类别缓冲区中的时间戳和时间戳缓冲区的是否一样,不一样,重新发送SQL查询 c1 = (Customer) session.get(Customer.class, 1); System.out.println(c1); transaction.commit(); }
案例九:Query接口的iterator(),返回迭代器代理对象,数据中只有id,在访问每个元素时,先查找二级缓存,如果找不到,生成SQL语句
@Test public void test07() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); List<Order> list = (List<Order>) session.createQuery("from Order where id < 5").list(); transaction.commit(); session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); //不在缓存里的,会执行SQL查询 Iterator<Order> iterate = session.createQuery("from Order where id <10").iterate(); while (iterate.hasNext()) { Order o = (Order) iterate.next(); System.out.println(o.getId()+"-->"+o.getAddress()); } }
案例十:查询缓存
二级缓存查询结果,比如以OID作为key ,以对象作为Value 进行缓存 , 查询缓存 以SQL语句为key ,以查询结果作为Value
在hibernate.cfg.xml中配置开启查询缓存
<property name="hibernate.cache.use_query_cache">true</property>
代码中也要启用查询缓存
@Test public void test08() { Session session = HibernateUtils.getCurrentSession(); Transaction transaction = session.beginTransaction(); Query query = session.createQuery("select name from Customer"); query.setCacheable(true);// 启用查询缓存 List<String> list = query.list(); transaction.commit(); session = HibernateUtils.getCurrentSession(); transaction = session.beginTransaction(); //从查询缓存中获取数据,不会有SQL产生 query = session.createQuery("select name from Customer"); query.setCacheable(true);// 启用查询缓存 List<String> lst = query.list(); System.out.println(lst); }
案例十一:二级缓存性能检测
在hibernate.cfg.xml配置开启性能检测
<property name="hibernate.generate_statistics">true</property>
二级缓存命中率
// 二级缓存的性能监测 public void test09() { Configuration configuration = new Configuration().configure(); SessionFactory sessionFactory = configuration.buildSessionFactory(); // sessionFactory.getStatistics().setStatisticsEnabled(true); Session session = sessionFactory.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 放入二级缓存 Customer customer1 = (Customer) session.get(Customer.class, 1); // 不在一级缓存,不在二级缓存 miss+1 Customer customer2 = (Customer) session.get(Customer.class, 2); // 不在一级缓存,不在二级缓存 miss+1 Customer customer3 = (Customer) session.get(Customer.class, 2); // 在一级缓存找到,hit和miss都不+1 transaction.commit(); session = sessionFactory.getCurrentSession(); transaction = session.beginTransaction(); // 读二级缓存 Customer customer4 = (Customer) session.get(Customer.class, 1); // 在二级缓存找到hit+1 Customer customer5 = (Customer) session.get(Customer.class, 2); // 在二级缓存找到 hit+1 transaction.commit(); // 监测 System.out.println("命中次数: " + sessionFactory.getStatistics().getSecondLevelCacheHitCount()); // 命中次数 System.out.println("MISS次数:" + sessionFactory.getStatistics().getSecondLevelCacheMissCount()); // 丢失次数 }