本文借鉴至:http://www.cnblogs.com/xiaoluo501395377/p/3377604.html
一:N+1次查询问题。
首先,什么是N+1次查询,我的理解是在使用session.createQuery("HQL语句").iterator()查询时第一次查询会去查询数据库中所有符合条件的记录的id,然后根据id逐一查询出每条记录的现象。
下面来看看代码:
/**
* N+1次查询问题
*/
@Test
public void testOnePlusMore() {
Session session = null;
try{
session = HibernateUtils.getSession();
session.beginTransaction();
Query query = session.createQuery("from User");
Iterator userIterator = query.iterate();
User user;
while(userIterator.hasNext()) {
user = userIterator.next();
System.out.println(user);
}
}catch (Exception e) {
session.getTransaction().rollback();
}
}
测试结果:
Hibernate:
select
user0_.id as col_0_0_
from
t_user user0_
Hibernate:
select
user0_.id as id1_0_0_,
user0_.age as age2_0_0_,
user0_.name as name3_0_0_
from
t_user user0_
where
user0_.id=?
User{id=1, name='aaa', age=12}
Hibernate:
select
user0_.id as id1_0_0_,
user0_.age as age2_0_0_,
user0_.name as name3_0_0_
from
t_user user0_
where
user0_.id=?
User{id=2, name='历史', age=22}
Hibernate:
select
user0_.id as id1_0_0_,
user0_.age as age2_0_0_,
user0_.name as name3_0_0_
from
t_user user0_
where
user0_.id=?
User{id=3, name='333', age=33}
Hibernate:
select
user0_.id as id1_0_0_,
user0_.age as age2_0_0_,
user0_.name as name3_0_0_
from
t_user user0_
where
user0_.id=?
User{id=4, name='阿什顿', age=22}
Hibernate:
select
user0_.id as id1_0_0_,
user0_.age as age2_0_0_,
user0_.name as name3_0_0_
from
t_user user0_
where
user0_.id=?
User{id=5, name='奥斯达', age=22}
Hibernate:
select
user0_.id as id1_0_0_,
user0_.age as age2_0_0_,
user0_.name as name3_0_0_
from
t_user user0_
where
user0_.id=?
User{id=6, name='33', age=44}
Hibernate:
select
user0_.id as id1_0_0_,
user0_.age as age2_0_0_,
user0_.name as name3_0_0_
from
t_user user0_
where
user0_.id=?
User{id=7, name='333', age=434}
为了做好对比,来看看list()方法查询所有的记录产生的现象是怎样的呢?
/**
* 通过Query对象获取所有的记录
*/
@Test
public void testList() {
Session session = null;
try{
session = HibernateUtils.getSession();
session.beginTransaction();
Query query = session.createQuery("from User");
List userList = query.list();
for(User user:userList) {
System.out.println(user);
}
}catch (Exception e) {
session.getTransaction().rollback();
}
}
测试结果:
Hibernate:
select
user0_.id as id1_0_,
user0_.age as age2_0_,
user0_.name as name3_0_
from
t_user user0_
User{id=1, name='aaa', age=12}
User{id=2, name='历史', age=22}
User{id=3, name='333', age=33}
User{id=4, name='阿什顿', age=22}
User{id=5, name='奥斯达', age=22}
User{id=6, name='33', age=44}
User{id=7, name='333', age=434}
从两次测试结果可以看出,iterator方法产生了大量的sql语句,并且查询n条记录的时候会产生n+1次查询,而这多的一次是去查询所有记录的ID而产生的。
其实到这里,我也比较疑惑既然list()方法能够一次性将所有的数据加载出来,那还用iterator()方法来干什么呢?感觉完全是多此一举的行为。但是,看到此文关联文章上面描述了一种场景:就是在一个session中两次查询出很多对象的时候,如果单单使用list()方法会造成两次查询的语句一样,这样就有一点浪费了。这里如果我们第一次使用list()方法,第二次就使用iterator()方法的话,此时我们也会发两条sql语句,但是第二条语句只会将查询出对象的id,所以相对应取出所有的对象而已,显然这样可以节省内存,而如果再要获取对象的时候,因为第一条语句已经将对象都查询出来了,此时会将对象保存到session的一级缓存中去,所以再次查询时,就会首先去缓存中查找,如果找到,则不发sql语句了。嗯,感觉这种说法确实是实际存在的,虽然没有在实际中碰到过,这里先记下吧。
二:hibernate一级缓存
前面的描述中又提到一个新东西:hibernate一级缓存。那么这个一级缓存又是个什么样的情况呢?
先用测试代码来看看这个现象吧:
/**
* 测试一级缓存
*/
@Test
public void testFirstCache() {
Session session = null;
try{
session = HibernateUtils.getSession();
session.beginTransaction();
//第一次查询
User user = (User)session.get(User.class,1);
System.out.println(user);
System.out.println("----------------------------");
//第二次查询
User user1 = (User)session.get(User.class,1);
System.out.println(user1);
session.getTransaction().commit();
session.close();
}catch (Exception e) {
session.getTransaction().rollback();
session.close();
}
}
这里在一个session中进行两次查询(查询的对象要相同,或者第二次查询的对象已经在第一次查询中出现),这种情况下hibernate给我们的查询方式为:
Hibernate:
select
user0_.id as id1_0_0_,
user0_.age as age2_0_0_,
user0_.name as name3_0_0_
from
t_user user0_
where
user0_.id=?
User{id=1, name='aaa', age=12}
----------------------------
User{id=1, name='aaa', age=12}
可以看到这里至进行了一个查询,第二次查询的时候根本没有发送sql语句出去,可以说这就是我们的hibernate一级缓存产生的现象:hiberante会把查询的结果缓存在session中,session没有关闭之前要是有这个对象的缓存,那我们可以直接从这个session中取出这个对象而不用发送sql语句再次到数据库中去查询。注意:一级缓存的前提是session级别,也就是session范围内的缓存,在session没有关闭之前缓存的是有效的,但是session关闭之后缓存在那个session中的内容也不会存在了。
三:hibernate二级缓存(sessionFactory级别)
那么如果这样一种场景:我需要在session关闭之后还是能够取到之前查询出来的记录,这个该怎么办呢?这个就需要一个叫做二级缓存的东西的帮助了。
那什么是二级缓存呢?二级缓存是将查询的记录缓存在sessionFactory中。
先来说说具体使用吧:
1.hibernate并没有提供相应的二级缓存的组件,所以需要加入额外的二级缓存包,常用的二级缓存包是EHcache。这个我们在下载好的hibernate的lib->optional->ehcache下可以找到(我这里使用的hibernate4.1.7版本),然后将里面的几个jar包导入即可。
2.在hibernate.cfg.xml配置文件中配置我们二级缓存的一些属性:
true
org.hibernate.cache.ehcache.EhCacheRegionFactory
ehcache.xml
我这里使用的是hibernate4.3.8版本,如果是使用hibernate3的版本的话,那么二级缓存的提供类则要配置成这个:
net.sf.ehcache.hibernate.EhCacheProvider
3.配置hibernate的二级缓存是通过使用 ehcache的缓存包,所以我们需要创建一个 ehcache.xml 的配置文件,来配置我们的缓存信息,将其放到项目根目录下
4.开启我们的二级缓存
①如果使用xml配置,我们需要在 User.hbm.xml 中加上一下配置:
二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。
②如果使用annotation配置,我们需要在User这个类上加上这样一个注解:
package cn.bdx.po;
import org.hibernate.annotations.*;
import org.hibernate.annotations.Cache;
import javax.persistence.*;
import javax.persistence.Entity;
import javax.persistence.Table;
/**
* Created by Administrator on 2016/5/23.
*/
@Entity
@Table(name="t_user")
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)//表示开启二级缓存,并设置为只读
public class User{
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int age;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
到这里基本上所有的配置算是完毕了,接下来是测试的代码:
/**
* 测试二级缓存
*/
@Test
public void testEhcache() {
Session session = null;
try
{
session = HibernateUtils.getSession();
User user = (User) session.get(User.class, 1);
System.out.println(user.getName() + "-----------");
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
session.close();
}
try
{
/**
* 即使当session关闭以后,因为配置了二级缓存,而二级缓存是sessionFactory级别的,所以会从缓存中取出该数据
* 只会发出一条sql语句
*/
session = HibernateUtils.getSession();
User user = (User) session.get(User.class, 1);
System.out.println(user.getName() + "-----------");
/**
* 因为设置了二级缓存为read-only,所以不能对其进行修改
*//*
session.beginTransaction();
user.setName("aaa");
session.getTransaction().commit();*/
}
catch (Exception e)
{
e.printStackTrace();
session.getTransaction().rollback();
}
finally
{
session.close();
}
}
测试结果:
Hibernate:
select
user0_.id as id1_0_0_,
user0_.age as age2_0_0_,
user0_.name as name3_0_0_
from
t_user user0_
where
user0_.id=?
aaa-----------
aaa-----------
从结果可以看出即使session关闭,我们仍然不会发送sql语句查询数据库取获取记录,这就是二级缓存的存在,在配置了二级缓存之后,查询的记录也会缓存到sessionFactory中,这样无论session是否关闭我们只要sessionFactory存在我们还是可以从缓存中获取记录。
注意一点:二级缓存是缓存的对象,对于获取单个或多个字段这样的内容是不予以缓存的。