Hibernate缓存

原创
2016/10/25 20:54
阅读数 120

5、缓存

    有三种缓存非常重要(缓存是决定Hibernate的一个非常重要的指标,如果不正常的使用缓存可能存在
    N+1问题)。

    1、一级缓存:属于session内部的缓存,程序员无法关闭(session关闭,缓存清空),而且是针对对象
        的缓存。

    2、二级缓存:属于sessionFactory级别的缓存(当session关闭时缓存依然有效),也是针对对象的缓存

    3、查询缓存:属于sessionFactory级别的缓存(当session关闭时缓存依然有效),针对HQL查询语句的
        缓存。(例如:from User)

5.1、N+1问题

   1、使用iterate()查询可能发生N+1,因为iterate()仅仅是查询id,当使用到数据时才去相应的数据库中取象

			/**
			 * 如果使用iterate()方法返回列表,对于Hibernate而言,它仅仅只是发出取id列表的sql
			 * 在查询相应的具体的某个学生信息时,会发出相应的sql去取学生信息
			 * 这就是典型的N+1问题
			 * 存在iterate()的原因是,有可能会在一个session中查询两次数据,如果使用list每一次
			 * 都会把所有的对象查询出来,而使用iterate()仅仅只会查询id,此时所有的对象已经存储在
			 * 一级缓存(session的缓存)中,可以直接获取
			 * 特别注意:Hibernate5.2已经弃用iterate()方法
			 */
			session = HibernateUtil.openSession();
			Iterator<Student> stus = session.createQuery("from Student", Student.class)
					.setFirstResult(0).setMaxResults(50).iterate();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}

    2、使用list+iterator可以有效解决N+1

			/**
			 * 此时会发出1条SQL取出所有的学生信息
			 * list缓存的是对象,所以在目前这种情况不会存在N+1问题
			 * 此时会缓存所有对象
			 */
			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student", Student.class)
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
			/**
			 * id=1的Student对象已经存在session的缓存(一级缓存)中,此时就不会发sql去取Student
			 */
			Student stu = session.load(Student.class, 1);
			System.out.println(stu.getName()+",---");

    最佳实践:一般不使用iterate()方法。特别注意:Hibernate5.2已经弃用iterate()方法。

5.2、一级缓存

    1、一级缓存,缓存对象,并且session内有效,默认不会关闭,除非程序员控制。

			/**
			 * 此时会发出1条SQL取出所有的学生信息
			 * list缓存的是对象,所以在目前这种情况不会存在N+1问题
			 * 此时会缓存所有对象
			 */
			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student", Student.class)
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
			/**
			 * id=1的Student对象已经存在session的缓存(一级缓存)中,此时就不会发sql去取Student
			 */
			Student stu = session.load(Student.class, 1);
			System.out.println(stu.getName()+",---");

    2、一级缓存仅仅只是在session内部有效

		Session session = null;
		try {
			/**
			 * 此时会发出1条SQL取出所有的学生信息
			 * list缓存的是对象,所以在目前这种情况不会存在N+1问题
			 * 此时会缓存所有对象
			 */
            //打开第一个session
			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student", Student.class)
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
			/**
			 * id=1的Student对象已经存在session的缓存(一级缓存)中,此时就不会发sql去取Student
			 */
			Student stu = session.load(Student.class, 1);
			System.out.println(stu.getName()+",---");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//session关闭,缓存丢失
			HibernateUtil.closeSession(session);
		}
		try {
			//打开了第二个session
			session = HibernateUtil.openSession();
			/**
			 * 上一个session已经关闭,一级缓存清空,此时又得重新取Student
			 */
			Student stu = session.load(Student.class, 1);
			System.out.println(stu.getName()+",---");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}

    3、可以通过session.clear()来清空缓存

			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student", Student.class)
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			//清空session的所有缓存
			session.clear();
			/**
			 * 此时session已经清空,会发出sql
			 */
			Student stu = session.load(Student.class, 1);
			System.out.println(stu.getName());

    4、session.clear()的使用方式(只有在进行批量操作时才使用)

			//批量添加会全部存储到缓存中,可能缓存不够用
			for(int i=0;i<100000;i++) {
				if(i%200==0) {
					//必须先刷新
					session.flush();
					//之后再进行清空
					session.clear();
				}
				session.save(new Student("1","1",null));
			}

5.3、二级缓存:二级缓存属于SessionFactory级别的缓存,缓存的也是对象。使用二级缓存需要进行配置,
        并且会使用到一些其它的二级缓存开源依赖包。

1、启用二级缓存

		<!-- 设置二级缓存为true -->
		<property name="hibernate.cache.use_second_level_cache">true</property>
		<!-- 启动"查询缓存"如果想缓存使用findall()、list()、Iterator()、createCriteria()、createQuery()等方法获得的数据结果集,必须配置此项-->  
		<property name="hibernate.cache.use_query_cache">true</property>
		<!-- 高速缓存提供程序 -->
		<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory</property>
		<!-- 指定缓存配置文件位置 -->
		<property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>

2、在相应类中开启二级缓存

    2.1、在Annotation中开启

@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)
public class Student {

    2.2、在xml配置文件中开启

    	<!-- 开启二级缓存,使用read-only之后只能查询不能更新 -->
    	<cache usage="read-only"/>

3、二级缓存的使用

		//第一个session
		Session session = null;
		try {
			/**
			 * 此时会发出1条SQL取出所有的学生信息
			 */
			session = HibernateUtil.openSession();
			Student stu = session.load(Student.class, 1);
			System.out.println(stu.getName()+",+++");
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			//session关闭,一级缓存丢失
			HibernateUtil.closeSession(session);
		}
		try {
			//打开了第二个session
			session = HibernateUtil.openSession();
			session.beginTransaction();
			/**
			 * 此时session已经关闭了,但是Student在二级缓存中,所以也不会发出SQL语句
			 */
			Student stu = session.load(Student.class, 1);
			//会报错,因为二级缓存设置为read-only
//			stu.setName("abc");
			System.out.println(stu.getName()+",---");
			session.getTransaction().commit();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}

4、对于HQL而言,如果查询的是对象依然会将对象缓存到二级缓存中

		Session session = null;
		try {
			/**
			 * 此时会发出1条SQL取出所有的学生信息
			 */
			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student", Student.class)
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}
		try {
			//打开了第二个session
			session = HibernateUtil.openSession();
			session.beginTransaction();
			/**
			 * 此时session已经关闭了,但是Student在二级缓存中,所以也不会发出SQL语句
			 */
			Student stu = session.load(Student.class, 1);
			//会报错,因为二级缓存设置为read-only
//			stu.setName("abc");
			System.out.println(stu.getName()+",---");
			session.getTransaction().commit();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}

5、如果HQL查询的不是对象就无法缓存

		Session session = null;
		try {
			/**
			 * 此时会发出1条SQL取出所有的学生信息
			 */
			session = HibernateUtil.openSession();
			//查询的不是对象,无法缓存,此时如果希望缓存,得使用查询缓存
			List<Object[]> ls = session.createQuery("select stu.id,stu.name from Student stu", Object[].class)
					.setFirstResult(0).setMaxResults(50).getResultList();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}
		try {
			//打开了第二个session
			session = HibernateUtil.openSession();
			session.beginTransaction();
			/**
			 * 以上代码仅仅取了id和name,而二级缓存是缓存对象的,所以上一段代码不会将对象
			 * 加入二级缓存,此时就会发出相应的sql
			 */
			Student stu = session.load(Student.class, 1);
			//会报错,因为二级缓存设置为read-only
//			stu.setName("abc");
			System.out.println(stu.getName()+",---");
			session.getTransaction().commit();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}
		Session session = null;
		try {
			/**
			 * 此时会发出1条SQL取出所有的学生信息
			 */
			session = HibernateUtil.openSession();
			List<Object[]> ls = session.createQuery("select stu from Student stu", Object[].class)
					.setFirstResult(0).setMaxResults(50).getResultList();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}
		try {
			session = HibernateUtil.openSession();
			/**
			 * 使用list会发出两条一模一样的sql,此时如果希望不发sql就需要使用查询缓存
			 */
			List<Student> ls = session.createQuery("select stu from Student stu", Student.class)
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}

最佳实践:二级缓存的设置一般设置为read-only此时无法修改,所以一般要把不会修改的对象设置为二级缓
    存。诸如:学生管理系统中的部门信息、专业信息、班级信息等不容易修改的信息将其放到二级缓存中。
    二级缓存不建议使用read-write。(不同的项目,不同的考虑)

5.4、查询缓存:查询缓存不会缓存对象,仅仅只是会缓存HQL语句,如果HQL语句中查询的是一组对象,
        此时查询缓存也不会缓存对象,而是缓存对象的id。而且查询缓存是SessionFactory级别的缓存。

    查询缓存同样需要配置之后才能开启。

1、开启查询缓存

		<!-- 启动"查询缓存"如果想缓存使用findall()、list()、Iterator()、createCriteria()、createQuery()等方法获得的数据结果集,必须配置此项-->  
		<property name="hibernate.cache.use_query_cache">true</property>

2、在查询中设置查询缓存

		Session session = null;
		try {
			/**
			 * 此时会发出1条SQL取出所有的学生信息
			 */
			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student where name like ?", Student.class)
					.setCacheable(true) //开启查询缓存,查询缓存也是sessionFactory级别的缓存
					.setParameter(0, "%王%")
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}
		
		session = null;
		try {
			/**
			 * 此时会发出1条SQL取出所有的学生信息
			 */
			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student where name like ?", Student.class)
					.setCacheable(true) //开启查询缓存,查询缓存也是sessionFactory级别的缓存
					.setParameter(0, "%王%")
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}

3、查询缓存如果条件不一样也会发sql

		Session session = null;
		try {
			/**
			 * 此时开启了查询缓存,如果传递的条件参数不一样,也会发sql
			 */
			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student where name like ?", Student.class)
					.setCacheable(true) //开启查询缓存,查询缓存也是sessionFactory级别的缓存
					.setParameter(0, "%王%")
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}
		
		session = null;
		try {
			/**
			 * 由于条件不一样,虽然HQL是一样的,但是也会发sql
			 */
			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student where name like ?", Student.class)
					.setCacheable(true) //开启查询缓存,查询缓存也是sessionFactory级别的缓存
					.setParameter(0, "%张%")
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}

4、查询缓存的N+1问题演示(先关闭二级缓存)

		Session session = null;
		try {
			/**
			 * 查询缓存缓存的不是对象而是id
			 */
			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student where name like ?", Student.class)
					.setCacheable(true) //开启查询缓存,查询缓存也是sessionFactory级别的缓存
					.setParameter(0, "%王%")
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}
		
		session = null;
		try {
			/**
			 * 查询缓存缓存的是id,此时由于在缓存中已经存在了这样的一组学生数据,但是仅仅只是
			 * 缓存了id,所以此处会发大量的sql语句根据id取对象,这也是发现N+1问题的第二个原因
			 * 所以如果使用查询缓存必须开启二级缓存
			 */
			session = HibernateUtil.openSession();
			List<Student> ls = session.createQuery("from Student where name like ?", Student.class)
					.setCacheable(true) //开启查询缓存,查询缓存也是sessionFactory级别的缓存
					.setParameter(0, "%王%")
					.setFirstResult(0).setMaxResults(50).getResultList();
			Iterator<Student> stus = ls.iterator();
			for(;stus.hasNext();) {
				Student stu = stus.next();
				System.out.println(stu.getName());
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			HibernateUtil.closeSession(session);
		}

    但是如果打开二级缓存就不会出现N+1问题,因为二级缓存会缓存对象。

最佳实践:查询缓存一定要在查询语句固定的时候打开,而且如果要使用查询缓存,就必须开启二级缓存,
    否则会出现N+1问题。

展开阅读全文
打赏
1
6 收藏
分享
加载中
更多评论
打赏
0 评论
6 收藏
1
分享
返回顶部
顶部