Hibernate 缓存机制

一、什么是缓存

缓存是内存中少部分数据的复制品,所以CPU到缓存中寻找数据时,也会出现找不到的情况(因为这些数据没有从内存复制到缓存中去),这时CPU还是会到内存中去找数据,这样系统的速率就慢下来了,不过CPU会把这些数据复制到缓存中去,以便下一次不要再到内存中去取。

二、Hibernate的一级缓存

(1)使用
Hibernate的一级缓存是默认开启的,当获取到一个Session对象,并执行save、update、saveOrUpdate、get方法时就会用到Hibernate一级缓存,当然也可以调用清除的放方法,Session为清除缓存提供了clear(清除所有的一级缓存)、evict(清除实例对象缓存)、refresh(重新查询数据并刷新缓存)。

例子:

//泛型查询实例
public E find(S s) {
    //使用Spring 获取一个Session对象
    Session session = getSession();
    //执行查询操作
    E bean = (E)session.get(entityClass, (Serializable) s);
    //清除实例对象缓存
    session.evict(bean);
    //返回查询的对象
    return bean;
}

(2)状态

Hibernate缓存状态分为瞬时状态、持久状态、脱管状态。

瞬时状态: 创建一个POJO,还未将对象数据保存到数据库时,Session中也没有当前POJO实例。

例子:

//创建一个POJO实例
Account account = new Account();
//给实例添加数据
account.setPhone("12345678931");
//这时实例并没有保存

持久状态: POJO对象被添加到Session缓存中,数据库也要有对应的数据。

例子:

public Integer saveStatus(Account entity) {
    //获取Session对象
    Session session = getSession();
    //执行保存方法
    Integer id = (Integer)session.save(entity);
    //修改被持久化的POJO对象
    entity.setState(4);
    //返回对象ID
    return id;
}

在上面的例子中,当保存事务还未提交,这时数据已经被持久化。这里会执行两条SQL,一条添加SQL,一条修改SQL(并没有调用修改方法为什么会执行修改SQL呢?因为Hibernate被持久化的POJO对象在被重新赋值时会触发更新操作。)
Hibernate 缓存机制
Hibernate 缓存机制

脱管状态: 在缓存中已经被持久化的POJO对象,接着POJO对象执行了evict方法,这时POJO对象会从缓存中托管,但数据库中是有对应的数据。

例子:

public Integer saveStatus(Account entity) {
    //获取Session对象
    Session session = getSession();
    //执行保存方法
    Integer id = (Integer)session.save(entity);
    //脱管当前POJO对象
    session.evict(entity);
    //修改被持久化的POJO对象
    entity.setState(4);
    //返回对象ID
    return id;
}

调用evict方法将POJO对象传入,清除实例的持久化,修改POJO实例将不会在触发更新操作。
Hibernate 缓存机制

(3)缓存绑定

Hibernate的一级缓存是绑定Session的,当获取到一个Session对象,在执行Sessinon里面的方法都能使用Hibernate默认提供的一级缓存,执行完成Session对象消亡即缓存数据也跟着消亡(一级缓存的数据是放在栈中)。

例子:

//通过id查询POJO实例
public E find(S s) {
    Session session = getSession();
    E bean = (E)session.get(entityClass, (Serializable) s);
    return bean;
}
//调用三次上面的方法
accountService.find(1);
accountService.find(1);
accountService.find(1);

这里会执行三条查询SQL,这是因为里面获取的Session对象是线程安全的,彼此并没有任何关联(这也是为什么Spring不能用到Hibernate的一级缓存,其实不是Spring的问题)。

//通过id查询POJO实例
public E find(S s) {
    Session session = getSession();
    E bean  = (E)session.get(entityClass, (Serializable) s);
    E bean1 = (E)session.get(entityClass, (Serializable) s);
    E bean2 = (E)session.get(entityClass, (Serializable) s);
    return bean;
}
//调用上面的方法
accountService.find(1);

这里就算方法体里面执行三个查询操作,也只会执行一条查询SQL,因为使用的同一个Session对象,这就有使用到Hibernate的一级缓存。

二、Hibernate的二级缓存
(1)使用

Hibernate的二级缓存是默认关闭的(二级缓存的数据是放在堆中),如果需要开启二级缓存则需要额外的配置。

(2)配置

第一步:POJO对象设置

@Entity//POJO注解
@Table(name = "account")//对应的数据库表名称
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)//二级缓存配置,读写模式
public class Account {
    ...
}

第二步:配置Hibernate session工厂

<bean id="sessionFactory"  class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.hbm2ddl.auto">update</prop>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
            <prop key="hibernate.show_sql">true</prop>
            <prop key="hibernate.format_sql">true</prop>
            <prop key=" hibernate.cache.use_second_level_cache">true</prop><!--开启二级缓存-->
            <prop key=" hibernate.cache.use_query_cache">true</prop><!--启用查询缓存-->
            <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop><!--配置二级缓存提供商-->
            <prop key="hibernate.net.sf.ehcache.configurationResourceName">classpath:applicationContext-ehcache.xml</prop><!--加载缓存所需配置文-->
        </props>
    </property>
    <property name="packagesToScan">
        <list>
            <value>com.test.entity</value><!--POJO包路径-->
        </list>
    </property>
</bean>

第三步:创建二级缓存配置文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="360" timeToLiveSeconds="360" overflowToDisk="false"/>
    //maxElementsInMemory--缓存对象的最大数目
    //eternal--对象是否永不过期,设置为true,过期时间则无效
    //timeToldleSeconds--对象空闲多长时间未被使用就失效
    //timeToLiveSeconds--对象被缓存的时间
    //overflowToDisk--内存溢出时是否刷盘,如果为true则需要配置一个刷盘路径<diskStore path="/ehcache/path/"/>
    //diskExpiryThreadIntervalSeconds--磁盘失效线程运行时间间隔
</ehcache>

三、选择正确的方法

(1) Hibernatne查询分为两类:一类是得到单个对象,另一类是得到结果集。

单个对象:

get()方法和load()方法的区别在于对二级缓存的使用上。load()方法会使用二级缓存,而get()方法在一级缓存没有找到的情况下会直接查询数据库,不会去二级缓存中查找。在使用中,对使用了二级缓存的对象进行查询时最好使用load()方法,以充分利用二级缓存来提高检索的效率。

结果集对象:

list方法介绍

list()方法在执行时,是直接运行查询结果所需要的查询语句,而iterator()方法则是先执行得到对象ID的查询,然后再根据每个ID值去取得所要查询的对象。因此,对于list()方式的查询通常只会执行一个SQL语句,而对于iterator()方法的查询则可能需要执行N+1条SQL语句(N为结果集中的记录数)。

list()方法只能使用二级缓存中的查询缓存,而无法使用二级缓存对单个对象的缓存(但是会把查询出的对象放入二级缓存中)。所以,除非重复执行相同的查询操作,否则无法利用缓存的机制来提高查询的效率。

list()方法会一次获得所有的结果集对象,而且它会依据查询的结果初始化所有的结果集对象。这在结果集非常大的时候必然会占据非常多的内存,甚至会造成内存溢出情况的发生。

iterator方法介绍

iterator()方法只是可能执行N+1条数据,具体执行SQL语句的数量取决于缓存的情况以及对结果集的访问情况。

iterator()方法则可以充分利用二级缓存,在根据ID检索对象的时候会首先到缓存中查找,只有在找不到的情况下才会执行相应的查询语句。所以,缓存中对象的存在与否会影响到SQL语句的执行数量。

iterator()方法在执行时不会一次初始化所有的对象,而是根据对结果集的访问情况来初始化对象。因此在访问中可以控制缓存中对象的数量,以避免占用过多缓存,导致内存溢出情况的发生。使用iterator()方法的另外一个好处是,如果只需要结果集中的部分记录,那么没有被用到的结果对象根本不会被初始化。所以,对结果集的访问情况也是调用iterator()方法时执行数据库SQL语句多少的一个因素。

部分信息来自网络,欢迎大家指出错误。

相关推荐