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对象在被重新赋值时会触发更新操作。)
脱管状态: 在缓存中已经被持久化的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实例将不会在触发更新操作。
(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语句多少的一个因素。
部分信息来自网络,欢迎大家指出错误。