缓存它的作用在于提高性能系统性能,介于应用系统与数据库之间而存在于内存或磁盘上的数据。
我们编程的模式一般是这样的page-->filter-->action-->server-->dao-->db,可以在这一个请求过程中的任何一点加入缓存,上一篇介绍的是在server层加缓存:service利用aop加缓存。
为页面增加缓存:为页面加缓存
首先,来看一下一级缓存它默认开启且很常用。
一级缓存
当session关闭后缓存中的对象会丢失,也就是说两个不同的session中的缓存数据都是不一样的,缓存数据不能够跨session访问。
缓存数据的数据类型
load()测试:测试注意一定要在同一个事务里面,当我在Spring管理的session测试时调用两次load()总是查询两次发出两条SQL语句,还以为session级缓存没有起作用,原来是因为hibernate集成spring之后事务、session都由spring管理,每次调用前后事务一级session都自动打开和关闭,自己控制不了中间过程,于是将spring去掉拿到hibernate原session,再手动开发关闭事务这样做可以保证在同一个session、同一个事务里面操作方法,确实是发了一条SQL语句,看下面代码:
load()、get()方法:
@Test
public void testLoad()
{
Session session=sf.openSession();
session.beginTransaction();
Category category1=(Category)session.load(Category.class,1);
Category category2=(Category)session.load(Category.class,1);
System.out.println(category1);
System.out.println(category2);
session.getTransaction().commit();
session.close();
}
结果
Hibernate: select category0_.id as id0_0_, category0_.name as name0_0_ from Category category0_ where category0_.id= hibernate.Category@10efd7c hibernate.Category@10efd7c
结果不仅发送了一条语句而且两个对象打印出来也是一样的。
load()、get()第一次查询时会发出sql语句,从数据库表里面查询;第二次查询时会先去缓存里面查找,如果没有发生更新修改操作,那么将从缓存中读取数据,否则查询数据库。
save方法
@Test
public void testGet()
{
Session session=sf.openSession();
session.beginTransaction();
Category category1=new Category();
category1.setName("新闻");
session.save(category1);
Category category2=(Category)session.load(Category.class,category1.getId());
System.out.println(category2.getName());
session.getTransaction().commit();
session.close();
}
save也支持缓存,当执行save方法时首先往session缓存里面添加一条数据,等事务提交或者缓存刷新时才往数据库里面更新,从上面执行过程可以看出只发出了一条插入语句没有发查询语句,因为第二次是从缓存中查询出来的。
PS:save之后执行get或者load需要知道对象的ID,此时save方法执行后虽然数据库里没有数据,但是对象的ID已经生成可以通过这个ID查询对象。
批量插入数据
在批量插入数据的时候采取每次插入一部分数据,如下,每次插入20条数据不需要一条一条插入。
public void testInserBatch() {
Session session = sf.openSession();
session.beginTransaction();
for(int i=0; i<1000; i++) {
Category c = new Category();
c.setName("test" + i);
session.save(c);
if (i%20==0) {
session.flush();
}
}
session.getTransaction().commit();
session.close();
}
每次20条数据清理一下缓存,每次清理缓存调用session.flush()方法会发出20条insert语句,但是数据库里面还没有数据等所有数据都发出insert语句统一提交事务,事务同session是一个等级的因此需统一控制事务。
hibernate N+1问题
Hibernate 中常会用到 set 等集合表示 1 对多的关系,在我们做的这个铁科院项目中,在获取实体的时候就能根据关系将关联的对象或者对象集合取出,还可以设定 cacade 进行关联更新和删除。这不得不说 hibernate 的 orm 做得很好,很贴近 oo 的使用习惯了。
但是对数据库访问还是必须考虑性能问题的,在设定了 1 对多这种关系之后, 查询就会出现传说中的 n+1 问题。
一对多:在一方,查找得到了 n 个对象,那么又需要将 n 个对象关联的集合取出,于是本来的一条 sql 查询变成了 n+1 条;
多对一:在多方,查询得到了 m 个对象,那么也会将 m 个对象对应的 1 方的对象取出, 也变成了 m+1 ;
解决问题的方法:
1、 使用 fetch 抓取, Hibernate 抓取策略分为单端代理和集合代理的抓取策略。
Hibernate 抓取策略 ( 单端代理的抓取策略) :
保持默认也就是如下 :
fetch="select" 就是另外发送一条 select 语句抓取当前对象关联实体或者集合设置 fetch="join"
Hibernate 会通过 select 语句使用外连接来加载器关联实体活集合此时 lazy 会失效
Hibernate 抓取策略 ( 集合代理的抓取策略 ) :
保持默认( fetch="select" )也就是如下 :
1)fetch="select" 会另外发出一条语句查询集合
2) 设置fetch="join" 采用外连接集合的 lazy 失效
3) 这只fetch="subselect" 另外发出一条 select 语句抓取前面查询到的所有的实体对象的关联集合