本文讲述HIbernate中一级、二级缓存的概念以及如何使用。
一、大纲
2.什么是一级缓存
3.一级缓存示例展示
4.二级缓存以及示例展示
5.总结
二、什么是一级缓存
在hibernate中所谓的一级缓存就是session对象,但是一级缓存对提高性能的作用性并不是很大,其session主要的目的是管理实体对象的状态(临时、离线、持久化)。
其缓存模型如下图:
三、一级缓存示例
3.1 查询(get,load, HQL)
3.1.1 get/load方法
@Test public void testGet() { Session session = factory.openSession(); Transaction tx = session.beginTransaction(); tx.begin(); // 第一次查询 User user1 = (User)session.get(User.class, 1); System.out.println(user1); // 如果session中存在id=1的User对象缓存,那么再次查询时,将不需要执行SQL User user2 = (User)session.get(User.class, 1); System.out.println(user2); tx.commit(); session.close(); }
如果执行第二次查询在session关闭之后,那么会抛出异常,session已经关闭。load方法类型,只是其支持懒加载查询。
3.1.2 Hql查询
@Test public void testHqlQueryFromCache() throws Exception { Session session = factory.openSession(); Transaction tx = session.beginTransaction(); tx.begin(); // 使用HQL查询id=1的User对象,此时会将id=1的对象写入到session缓存中 User user1 = (User)session.createQuery("FROM User WHERE id = 1").uniqueResult(); System.out.println(user1); System.out.println("================"); // 执行第二次同样查询,默认情况无法从session中获取id=1的对象,但是也会将id=1的对象写入session缓存中 User user2 = (User)session.createQuery("FROM User WHERE id = 1").uniqueResult(); System.out.println(user2); System.out.println("================"); // 使用get方法查询,由于session中已经存在id=1的对象,所以不会执行SQL User user3 = (User)session.get(User.class, 1); System.out.println(user3); tx.commit(); session.close(); }
3.2 Update/delete对Session的影响
在session执行查询之后,如果此时执行update或者delete操作,session中的数据是否会发生变化呢?通过下面这个例子展示:
@Test public void testUpdate() { Session session = factory.openSession(); Transaction tx = session.beginTransaction(); tx.begin(); // 执行查询操作,将id=1的数据,存放在session中 User user = (User)session.get(User.class, 1); System.out.println(user); // 执行update操作,将id=1的数据修改 session.createQuery("UPDATE User SET name = ? WHERE id = 1").setParameter(0, "updateOrDelete").executeUpdate(); // 如果执行的update操作,修改了session中的实体name属性,那么获取时将得到updateOrDelete值,否则保持变 User user2 = (User)session.get(User.class, 1); System.out.println(user2); tx.commit(); session.close(); }
得出结论update或者delete(这里未展示delete操作),不会修改当前session的数据,只能等待下次session查询,或者手动使用session.refresh()/clear等刷新session数据。
四、二级缓存以及示例展示
4.1 什么是二级缓存
二级缓存实际上是提供了一个公共数据缓存库,用于提高查询性能,当对数据的实时性要求非常高时,没有必要使用二级缓存,二级缓存模型如下:
可以理解为二级缓存是不同session的共享数据,而一级缓存是针对同一个session操作的。
4.2 配置二级缓存
Hibernate默认并不使用二级缓存,如果使用的话,需要配置一些参数提供使用;
1、配置二级缓存类型(提供者)《hibernate.cfg.xml配置中》
<property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
2、设置需要配置的实体类、集合、查询条件
<!-- 开启查询缓存 --> <property name="hibernate.cache.use_query_cache">true</property> <!-- 实体类 --> <class-cache usage="read-write" class="com.hibernate.seconde_cache.User" /> <!-- 注:也可以在实体映射文件中配置<cache>这里不做介绍 -->
4.3 各种类型缓存处理
在4.2中粗略的展示了一些基本的二级缓存配置,在此节中将主要展示三种情况二级缓存配置。
4.3.1 单个实体二级缓存
1)配置
<!-- 设置缓存提供者 --> <property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property> <!-- 实体类 --> <class-cache usage="read-write" class="com.hibernate.seconde_cache.User" />
2)示例展示
@Test public void testSingleEntity() throws Exception { // 第一个session,存储数据 Session session1 = factory.openSession(); Transaction tx1 = session1.beginTransaction(); tx1.begin(); User user1 = (User)session1.get(User.class, 1); System.out.println(user1); tx1.commit(); session1.close(); System.out.println("\n===================\n"); // 第二个session读取第一个session的数据 Session session2 = factory.openSession(); Transaction tx2 = session2.beginTransaction(); tx2.begin(); User user2 = (User)session2.get(User.class, 1); System.out.println(user2); tx2.commit(); session2.close(); }
3)update和delete对二级缓存的影响
@Test public void testSingleEntityOfUpdate() throws Exception { // 第一个session,存储数据 Session session1 = factory.openSession(); Transaction tx1 = session1.beginTransaction(); tx1.begin(); User user1 = (User)session1.get(User.class, 1); System.out.println(user1); tx1.commit(); session1.close(); System.out.println("\n===================\n"); Session updateSession = factory.openSession(); updateSession.createQuery("UPDATE User SET name=? WHERE id = 1").setParameter(0, "Second Cache").executeUpdate(); System.out.println("\n===================\n"); // 第二个session读取第一个session的数据 Session session2 = factory.openSession(); Transaction tx2 = session2.beginTransaction(); tx2.begin(); User user2 = (User)session2.get(User.class, 1); System.out.println(user2); tx2.commit(); session2.close(); }
由此可以update和delete的操作,会即使影响二级缓存的操作,其他session可以及时发现
4.3.2 查询条件缓存
在介绍如何缓存查询条件之前,如何通过Hibernate提供的方法实现缓存,并解释此种方法的不良效果,以及引入查询条件如何缓存。
1)非查询条件实现缓存(缓存配置同4.3.1)
使用list执行相同查询条件,依旧重复执行SQL
@Test public void testHql() throws Exception { // 第一个session,存储数据 Session session1 = factory.openSession(); Transaction tx1 = session1.beginTransaction(); tx1.begin(); List user1= session1.createQuery("FROM User").list(); System.out.println(user1); tx1.commit(); session1.close(); System.out.println("\n===================\n"); // 第二个session读取第一个session的数据 Session session2 = factory.openSession(); Transaction tx2 = session2.beginTransaction(); tx2.begin(); List user2 = session2.createQuery("FROM User").list(); System.out.println(user2); tx2.commit(); session2.close(); }
使用iterate方法替代list方法,但是会出现N+1此查询问题,先查询符合条件的Id,之后在根据Id查询。
@Test public void testHqlOfIterate() throws Exception { Session session1 = factory.openSession(); Transaction tx1 = session1.beginTransaction(); tx1.begin(); // 使用iterate方法替代List方法 Iterator<User> users1= session1.createQuery("FROM User").iterate(); while(users1.hasNext()) { System.out.println(users1.next()); } tx1.commit(); session1.close(); System.out.println("\n===================\n"); Session session2 = factory.openSession(); Transaction tx2 = session2.beginTransaction(); tx2.begin(); Iterator<User> users2= session2.createQuery("FROM User").iterate(); while(users2.hasNext()) { System.out.println(users2.next()); } tx2.commit(); session2.close(); }
2)使用查询条件缓存
配置相比于4.3.1需要添加:
<!-- 开启查询缓存 --> <property name="hibernate.cache.use_query_cache">true</property>
就如1)中所属,使用查询条件过滤,可以减少1)中的查询次数,大大提高查询效率:
@Test public void testHqlOfCondition() throws Exception { Session session1 = factory.openSession(); Transaction tx1 = session1.beginTransaction(); tx1.begin(); // 通过执行setCacheable(true)方法,表示将此次查询条件缓存 // 也就是以FROM Use WHERE id < 4为键存储,值:对应查询的结果集 List list1= session1.createQuery("FROM User WHERE id < 4").setCacheable(true).list(); System.out.println(list1); tx1.commit(); session1.close(); System.out.println("\n===================\n"); Session session2 = factory.openSession(); Transaction tx2 = session2.beginTransaction(); tx2.begin(); List list2= session2.createQuery("FROM User WHERE id < 4").setCacheable(true).list(); System.out.println(list2); tx2.commit(); session2.close(); }
注:查询条件必须保持一直,否则视为不同查询条件。delete和update操作与4.3.1的影响一致,这里不做效果展示。
4.3.3 集合元素查询缓存展示
示例实体映射模型为(班级、学生方式《多对1的关系》),具体的映射代码、实体类代码这里不展示。
1)配置(相比4.3.1添加如下配置)
<!-- 配置班级实体缓存 --> <class-cache usage="read-write" class="com.hibernate.seconde_cache.Classes"/> <!-- 配置学生实体缓存 --> <class-cache usage="read-write" class="com.hibernate.seconde_cache.Student"/> <!-- 配置班级中的学生集合缓存,注意使用的方式是类全限定名.属性名 --> <collection-cache usage="read-write" collection="com.hibernate.seconde_cache.Classes.students"/>
2)代码示例:
@Test public void testSet() throws Exception { Session session1 = factory.openSession(); Transaction tx1 = session1.beginTransaction(); tx1.begin(); // 查询id=1的班级 Classes classes1 = (Classes)session1.get(Classes.class, 1); System.out.println(classes1); // 执行懒加载获取数据;如果想将集合元素也缓存,那么必须添加集合配置 System.out.println(classes1.getStudents()); tx1.commit(); session1.close(); System.out.println("\n===================\n"); Session session2 = factory.openSession(); Transaction tx2 = session2.beginTransaction(); tx2.begin(); // 第二个session,获取二级缓存中的数据 Classes classes2 = (Classes)session2.get(Classes.class, 1); System.out.println(classes2); System.out.println(classes2.getStudents()); tx2.commit(); session2.close(); }
如果没有添加
<collection-cache usage="read-write" collection="com.hibernate.seconde_cache.Classes.students"/>
那么会出现如下结果:
五、总结
涉及到的二级缓存知识,远远比本文多,一般不会使用本文中的Hashtable提供者,通常使用:
1级缓存是必须使用的,因为任何操作都需要使用到session,但是其对性能提高影响不大,而二级缓存对那些对数据实时性以及准确性要求不高的情况,建议使用,可以提高查询性能。