来自https://blog.csdn.net/ls5718/article/details/52183086,感谢作者的无私分享。
一、一级缓存(session级别)
概念
我们来看看hibernate提供的一级缓存:
-
/**
-
* 此时会发出一条sql,将所有学生全部查询出来,并放到session的一级缓存当中
-
* 当再次查询学生信息时,会首先去缓存中看是否存在,如果不存在,再去数据库中查询
-
* 这就是hibernate的一级缓存(session缓存)
-
*/
-
List<Student> stus = (List<Student>)session.createQuery("from Student")
-
.setFirstResult(0).setMaxResults(30).list();
-
Student stu = (Student)session.load(Student.class, 1);
我们来看看控制台输出:
Hibernate: select student0_.id as id2_, student0_.name as name2_, student0_.rid as rid2_, student0_.sex as sex2_ from t_student student0_ limit ?
我们看到此时hibernate仅仅只会发出一条 sql 语句,因为第一行代码就会将整个的对象查询出来,放到session的一级缓存中去,当我如果需要再次查询学生对象时,此时首先会去缓存中看是否存在该对象,如果存在,则直接从缓存中取出,就不会再发sql了,但是要注意一点:hibernate的一级缓存是session级别的,所以如果session关闭后,缓存就没了,此时就会再次发sql去查数据库。
1.Session 级别的缓存,它同session邦定。它的生命周期和session相同。Session消毁,它也同时消毁;管理一级缓存,一级缓存无法取消,用两个方法管理,clear(),evict()
2.两个session 不能共享一级缓存,因它会伴随session的生命周期的创建和消毁;
3.Session缓存是实体级别的缓存,就是只有在查询对象级别的时候才使用,如果使用HQL和SQL是查询属性级别的,是不使用一级缓存的!切记!!!!
4.iterate 查询使用缓存,会发出查询Id的SQL和HQL语句,但不会发出查实体的,它查询完会把相应的实体放到缓存里边,一些实体查询如果缓存里边有,就从缓存中查询,但还是会发出查询id的SQL和HQL语句。如果缓存中没有它会数据库中查询,然后将查询到的实体一个一个放到缓存中去,所以会有N+1问题出现。
5.每一个Hibernate Session实例和一个数据库事务绑定通常将每一个Hibernate Session实例和一个数据处理库事务绑定就是说,每执行一个数据库事务(操作),都应该先创建一个新的Hibernate Session实例.
6.如果事务执行中出现异常,应该撤消事务.不论事务执行成功与否,最后都应该调用Session的close()方法,从而释放Hibernate Session实例占用的资源.
使用一级缓存中可能遇到的问题
批量插入时可能发生内存溢出:
-
Session session =SessionFactory.openSession();
-
Transaction tx =session.beginTransaction();
-
for(int i=0;i<10000;i++){
-
User u = new User(...);
-
session.save(u);
-
}
-
tx.commit();
-
session.close();
在进行大批量数据一次性操作的时候,会占用非常多的内存来缓存被更新的对象。这时就应该阶段性地调用clear()方法来清空一级缓存中的对象,控制一级缓存的大小,以避免产生内存溢出的情况。
-
Session session =SessionFactory.openSession();
-
Transaction tx =session.beginTransaction();
-
for(int i=0;i<10000;i++){
-
User u = new User(...);
-
session.save(u);
-
if(i%20 == 0){
-
session.flush();
-
session.clean();
-
}
-
}
-
tx.commit();
-
session.close();
所以程序最好跳过Hibernate API 而直接通过JDBC API来执来...
我们改一下上面的代码:
-
Session session=SessionFactory.openSession();
-
Transaction tx =session.beginTransaction();
-
Connection conn =session.connection();
-
PreparedStatement pstmt = conn.prepareStatement("update users set age=age+1 "+"where age >0");
-
pstmt.executeUpdate();
-
tx.commit();
虽说这是通过JDBC API.但本质上还是通过Hibernater Transaction的事务这个接口来声明事务的边界的...
其实最好的解决方法就是以创建存储过程,,用底层的数据库运行..这样性能好,速度快....
二、二级缓存
使用hibernate二级缓存,我们首先需要对其进行配置,配置步骤如下:
1.hibernate并没有提供相应的二级缓存的组件,所以需要加入额外的二级缓存包,常用的二级缓存包是EHcache。这个我们在下载好的hibernate的lib->optional->ehcache下可以找到(我这里使用的hibernate4.1.7版本),然后将里面的几个jar包导入即可。
2.在hibernate.cfg.xml配置文件中配置我们二级缓存的一些属性:
-
<!-- 开启二级缓存 -->
-
<property name="hibernate.cache.use_second_level_cache">true</property>
-
<!-- 二级缓存的提供类 在hibernate4.0版本以后我们都是配置这个属性来指定二级缓存的提供类-->
-
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
-
<!-- 二级缓存配置文件的位置 -->
-
<property name="hibernate.cache.provider_configuration_file_resource_path">ehcache.xml</property>
3.配置hibernate的二级缓存是通过使用 ehcache的缓存包,所以我们需要创建一个 ehcache.xml 的配置文件,来配置我们的缓存信息,将其放到项目根目录下
-
<ehcache>
-
<!-- Sets the path to the directory where cache .data files are created.
-
If the path is a Java System Property it is replaced by
-
its value in the running VM.
-
The following properties are translated:
-
user.home - User's home directory
-
user.dir - User's current working directory
-
java.io.tmpdir - Default temp file path -->
-
-
<!--指定二级缓存存放在磁盘上的位置-->
-
<diskStore path="user.dir"/>
-
<!--我们可以给每个实体类指定一个对应的缓存,如果没有匹配到该类,则使用这个默认的缓存配置-->
-
<defaultCache
-
maxElementsInMemory="10000" //在内存中存放的最大对象数
-
eternal="false" //是否永久保存缓存,设置成false
-
timeToIdleSeconds="120"
-
timeToLiveSeconds="120"
-
overflowToDisk="true" //如果对象数量超过内存中最大的数,是否将其保存到磁盘中,设置成true
-
/>
-
-
<!--
-
1、timeToLiveSeconds的定义是:以创建时间为基准开始计算的超时时长;
-
2、timeToIdleSeconds的定义是:在创建时间和最近访问时间中取出离现在最近的时间作为基准计算的超时时长;
-
3、如果仅设置了timeToLiveSeconds,则该对象的超时时间=创建时间+timeToLiveSeconds,假设为A;
-
4、如果没设置timeToLiveSeconds,则该对象的超时时间=max(创建时间,最近访问时间)+timeToIdleSeconds,假设为B;
-
5、如果两者都设置了,则取出A、B最少的值,即min(A,B),表示只要有一个超时成立即算超时。
-
-->
-
<!--可以给每个实体类指定一个配置文件,通过name属性指定,要使用类的全名-->
-
<cache name="com.xiaoluo.bean.Student"
-
maxElementsInMemory="10000"
-
eternal="false"
-
timeToIdleSeconds="300"
-
timeToLiveSeconds="600"
-
overflowToDisk="true"
-
/>
-
<cache name="sampleCache2"
-
maxElementsInMemory="1000"
-
eternal="true"
-
timeToIdleSeconds="0"
-
timeToLiveSeconds="0"
-
overflowToDisk="false"
-
/> -->
-
</ehcache>
4.开启我们的二级缓存
①如果使用xml配置,我们需要在 Student.hbm.xml 中加上一下配置:
-
<hibernate-mapping package="com.xiaoluo.bean">
-
<class name="Student" table="t_student">
-
<!-- 二级缓存一般设置为只读的 -->
-
<cache usage="read-only"/>
-
<id name="id" type="int" column="id">
-
<generator class="native"/>
-
</id>
-
<property name="name" column="name" type="string"></property>
-
<property name="sex" column="sex" type="string"></property>
-
<many-to-one name="room" column="rid" fetch="join"></many-to-one>
-
</class>
-
</hibernate-mapping>
二级缓存的使用策略一般有这几种:read-only、nonstrict-read-write、read-write、transactional。注意:我们通常使用二级缓存都是将其配置成 read-only ,即我们应当在那些不需要进行修改的实体类上使用二级缓存,否则如果对缓存进行读写的话,性能会变差,这样设置缓存就失去了意义。
②如果使用annotation配置,我们需要在Student这个类上加上这样一个注解:
-
@Entity
-
@Table(name="t_student")
-
@Cache(usage=CacheConcurrencyStrategy.READ_ONLY) // 表示开启二级缓存,并使用read-only策略
-
public class Student
-
{
-
private int id;
-
private String name;
-
private String sex;
-
private Classroom room;
-
.......
-
}
这样我们的二级缓存配置就算完成了
需要注意的是:
①二级缓存是sessionFactory级别的缓存
因为二级缓存是sessionFactory级别的缓存,我们看到,在配置了二级缓存以后,当我们session关闭以后,我们再去查询对象的时候,此时hibernate首先会去二级缓存中查询是否有该对象,有就不会再发sql了。
②二级缓存缓存的仅仅是对象,如果查询出来的是对象的一些属性,则不会被加到缓存中去
③通过二级缓存来解决 N+1 的问题
当我们如果需要查询出两次对象的时候,可以使用二级缓存来解决N+1的问题。