前言
博主github
博主个人博客http://blog.healerjean.com
1、hibernate get/load
1.1、get方法:
首先在session缓存中查找,然后在二级缓存中查找,还没有就查询数据库,数据库中没有就返回null
1.2、load方法:
根据是否懒加载分情况讨论
1.2.1、若为true
1、先在Session缓存中查找,看看该id对应的对象是否存在
2、不存在则使用延迟加载,返回实体的代理类对象(该代理类为实体类的子类,由CGLIB动态生成)。
3、等到具体使用该对象(除获取OID以外)的时候,再查询二级缓存和数据库,若仍没发现符合条件的记录,则会抛出一个ObjectNotFoundException。
1.2.2、若为false
和get方法查找顺序一样,首先在session缓存中查找,然后在二级缓存中查找,还没有就查询数据库,数据库中没有,则会抛出一个ObjectNotFoundException。
2、缓存分类
1.事务范围(单Session即一级缓存)
事务范围的缓存只能被当前事务访问,每个事务都有各自的缓存,缓存内的数据通常采用相互关联的对象形式.缓存的生命周期依赖于事务的生命周期,只有当事务结束时,缓存的生命周期才会结束.事务范围的缓存使用内存作为存储介质,一级缓存就属于事务范围.
2.应用范围(单SessionFactory即二级缓存)
应用程序的缓存可以被应用范围内的所有事务共享访问.缓存的生命周期依赖于应用的生命周期,只有当应用结束时,缓存的生命周期才会结束.应用范围的缓存可以使用内存或硬盘作为存储介质,二级缓存就属于应用范围.
3.集群范围(多SessionFactory)
在集群环境中,缓存被一个机器或多个机器的进程共享,缓存中的数据被复制到集群环境中的每个进程节点,进程间通过远程通信来保证缓存中的数据的一致,缓存中的数据通常采用对象的松散数据形式.比如redis
3、一级缓存(session的缓存,默认开启)
1.1、简单介绍
hibernate的一级缓存是session级别的,所以如果session关闭后,缓存就没了,此时就会再次发sql去查数据库。(session关闭还能访问,解决该问题----配置二级缓存)
由于Session对象的生命周期通常对应一个数据库事务或者一个应用事务,因此它的缓存是事务范围的缓存,
1.2、测试:真实项目
1.2.1、Jpa方法
public interface DemoEntityRepository extends CrudRepository<DemoEntity,Long> {
/**
* 为了验证该sql不会放入缓存
* @param id
* @return
*/
@Query(" FROM DemoEntity d where d.id = :id")
DemoEntity testCachefindById(@Param("id") Long id) ;
}
1.2.2、测试成功
/**
* hibernate 一级缓存
* hibernate的一级缓存是session级别的,所以如果session关闭后,缓存就没了,此时就会再次发sql去查数据库。
* 作用范围较小! 缓存的事件短。
* 缓存效果不明显
*
* @param id
*/
public void oneCache(Long id){
DemoEntity demoEntity = demoEntityRepository.findOne(id);
System.out.println(demoEntity);
//Hibernate: select demoentity0_.id as id1_0_0_, demoentity0_.age as age2_0_0_, demoentity0_.cdate as cdate3_0_0_, demoentity0_.name as name4_0_0_, demoentity0_.udate as udate5_0_0_ from demo_entity demoentity0_ where demoentity0_.id=?
//用到了缓存,没有执行sql语句
DemoEntity demoEntity12 = demoEntityRepository.findOne(id);
System.out.println(demoEntity12);
//因为是sql的形式,所以没有用到缓存
DemoEntity sqlEntity13 = demoEntityRepository.testCachefindById(id);
System.out.println(sqlEntity13);
//Hibernate: select demoentity0_.id as id1_0_, demoentity0_.age as age2_0_, demoentity0_.cdate as cdate3_0_, demoentity0_.name as name4_0_, demoentity0_.udate as udate5_0_ from demo_entity demoentity0_ where demoentity0_.id=?
}
Hibernate: select demoentity0_.id as id1_0_0_, demoentity0_.age as age2_0_0_, demoentity0_.cdate as cdate3_0_0_, demoentity0_.name as name4_0_0_, demoentity0_.udate as udate5_0_0_ from demo_entity demoentity0_ where demoentity0_.id=?
DemoEntity(id=1, name=healerjean, age=25, cdate=2019-03-04 16:22:58.0, udate=2019-03-04 16:22:58.0)
DemoEntity(id=1, name=healerjean, age=25, cdate=2019-03-04 16:22:58.0, udate=2019-03-04 16:22:58.0)
//下面这个是sql查询语句,不会放到缓存中去
Hibernate: select demoentity0_.id as id1_0_, demoentity0_.age as age2_0_, demoentity0_.cdate as cdate3_0_, demoentity0_.name as name4_0_, demoentity0_.udate as udate5_0_ from demo_entity demoentity0_ where demoentity0_.id=?
DemoEntity(id=1, name=healerjean, age=25, cdate=2019-03-04 16:22:58.0, udate=2019-03-04 16:22:58.0)
1.3、N+1(Hibernate原生)
1.3.1、list查询
如果通过list()方法来获得对象,,hibernate会发出一条sql语句,将所有的对象查询出来 ,查询出来的这些对象会根据主键Id放入session一集缓存中去(1条语句)
控制台
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.rid as rid2_, student0_.sex as sex2_ from t_student student0_ limit ?
/**
* 此时会发出一条sql,将30个学生全部查询出来,并放到session的一级缓存当中(按照主键Id的key缓存放入)
*
* 当再次查询某个Id的学生信息时,会首先去缓存中看是否存在,如果不存在,再去数据库中查询
* 这就是hibernate的一级缓存(session缓存)
*/
List<Student> ls = (List<Student>)session.createQuery("from Student")
.setFirstResult(0).setMaxResults(30).list();
Iterator<Student> stus = ls.iterator();
for(;stus.hasNext();)
{
Student stu = (Student)stus.next();
System.out.println(stu.getName());
}
//从缓存中拿
Student stu = (Student)session.load(Student.class, 1);
1.3.2、iterator()获取对象
1、如果使用iterator方法返回列表,对于hibernate而言,它仅仅只是发出取id列表的sql (1条语句)
2、在查询相应的具体的某个学生信息时,会发出相应的SQL去取学生信息(同事每个都放入session缓存中),
这就是典型的N+1问题 (N条语句)
ibernate: select student0_.id as col_0_0_ from t_student student0_ limit ?
Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=?
沈凡
Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=?
王志名
Hibernate: select student0_.id as id2_0_, student0_.name as name2_0_, student0_.rid as rid2_0_, student0_.sex as sex2_0_ from t_student student0_ where student0_.id=?
叶敦
.........
/**
* 如果使用iterator方法返回列表,对于hibernate而言,它仅仅只是发出取id列表的sql
* 在查询相应的具体的某个学生信息时,会发出相应的SQL去取学生信息
* 这就是典型的N+1问题
*
* 存在iterator的原因是,有可能会在一个session中查询两次数据,如果使用list每一次都会把所有的对象查询上来(根据Id主键放入session缓存), iterator仅仅只会查询id,这样关于id的所有的对象已经存储在一级缓存(session的缓存)中,可以直接获取
*/
Iterator<Student> stus = (Iterator<Student>)session.createQuery("from Student")
.setFirstResult(0).setMaxResults(30).iterate();
for(;stus.hasNext();)
{
Student stu = (Student)stus.next();
System.out.println(stu.getName());
}
1.3、 为什么存在iterator
list() 方法每次返回集合对象,而iterator()方法先返回对象id ,需要查询具体对象信息才会发出相应的sql 语句去查询 ,节省了内存花费;
例如:再一个session中要两次查询多个对象时,如果两条都用list() ,一定会发出两条sql(list集合查询语句不使用缓存存放,因为是根据对象的主键Id存放的缓存,list会将集合取出来,再根据Id放到session一级缓存中),而且两条语句一样
但是如果你第一条使用list() 方法(查出了所有对象),第二条使用iterator() 方法(查询出对象 id) ,也是两条sql(在执行iterator的第二条sql的是,开始使用lsit查询出来的每个对象的缓存进行查询) 但是明显第二条只是根据id 对应第一条查出来的对象而已,可以节省内存;
1.4、解决 N+1 ,使用二级缓存
1.5、哪些方法会放数据
1.5.1、get()、load()均会向缓存中存放以及读取数据,
1.5.2、query.list()和query.uniqueResult()会向缓存存放数据但不会从缓存中读取数据
1.5.3、save()和saveOrUpdate()进行添加时,均会向缓存中存放数据。
4、二级缓存(sessionFactory的缓存):默认不会开启(需要进行配置)
4.1、简单介绍
二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。
注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。
事务型(transactional):隔离级别最高,对于经常被读但很少被改的数据,可以采用此策略。因为它可以防止脏读与不可重复读的并发问题。发生异常的时候,缓存也能够回滚(系统开销大)。
读写型(read-write):对于经常被读但很少被改的数据,可以采用此策略。因为它可以防止脏读的并发问题。更新缓存的时候会锁定缓存中的数据。
非严格读写型(nonstrict-read-write):不保证缓存与数据库中数据的一致性。对于极少被改,并且允许偶尔脏读的数据,可采用此策略。不锁定缓存中的数据。
只读型(read-only):对于从来不会被修改的数据,可使用此策略。(只能放入一次)
1、二级缓存是sessionFactory级别的缓存
2、二级缓存缓存的仅仅是对象,如果查询出来的是对象的一些属性(name,age),则不会被加到缓存中去
4.2、解决N+1问题
只是说明这个迭代器不会再N+1,其实执行的sql还是 list(n) +1
@Test
public void testCache3()
{
Session session = null;
try
{
session = HibernateUtil.openSession();
/**
* 将查询出来的Student对象缓存到二级缓存中去
*/
List<Student> stus = (List<Student>) session.createQuery(
"select stu from Student stu").list();
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
HibernateUtil.close(session);
}
try
{
/**
* 由于学生的对象已经缓存在二级缓存中了,此时再使用iterate来获取对象的时候,首先会通过一条
* 取id的语句,然后在获取对象时去二级缓存中,如果发现就不会再发SQL,这样也就解决了N+1问题
* 而且内存占用也不多 */
session = HibernateUtil.openSession();
Iterator<Student> iterator = session.createQuery("from Student")
.iterate();
for (; iterator.hasNext();)
{
Student stu = (Student) iterator.next();
System.out.println(stu.getName());
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
4.3、二级缓存会缓存 hql 语句吗(除非配置查询缓存)
4.3。1、连续使用两个list查询,测试的时候,这里讲session关闭了,再查的
@Test
public void testCache4()
{
Session session = null;
try
{
//