第十二章 hibernate缓存

1、一级缓存(Session级缓存)

一级缓存很短和session的生命周期一致,因此也叫session级缓存或事务级缓存

 

那些方法支持一级缓存:

*get()

*load()

*iterate(查询实体对象)

 

如何管理一级缓存:

*session.clear(),session.evict()

 

如何避免一次性大量的实体数据入库导致内存溢出

方法1:先flush,再clear

 

for(int i = 0; i <1000000; i++){

		session.save(user);

		if(i % 20 == 0){

			session.flush();

			session.clear();

		}

	}


 

方法2:用StatelessSession接口

Hibernate提供了基于命令的API,可以用detachedobject(分离对象)的形式把数据以流的方法加入到数据库,或从数据库输出。StatelessSession没有持久化上下文,也不提供多少高层的生命周期语义。特别是,无状态session不实现第一级cache,也不和第二级缓存,或者查询缓存交互。用StatelessSession进行的操作甚至不级联到关联实例。

无状态session是低层的抽象,和低层JDBC相当接近。

 

StatelessSession statelessSession = sessionFactory.openStatelessSession();

		statelessSession.insert(user);


 

方法3:批量更新

 

session.beginTransaction();

Queryquery = session.createQuery("updateUser as u set u.password=:p");

query.setString("p","000000");

query.executeUpdate();

session.getTransaction().commit();


 

如果数据量特别大,考虑采用jdbc实现,如果jdbc也不能满足要求可以考虑采用数据本身的特定导入工具



2、二级缓存

Hibernate默认的二级缓存是开启的。

二级缓存也称为进程级的缓存,也可称为SessionFactory级的缓存(因为SessionFactory可以管理二级缓存),它与session级缓存不一样,一级缓存只要session关闭缓存就不存在了。而二级缓存则只要进程在二级缓存就可用。

二级缓存可以被所有的session共享

二级缓存的生命周期和SessionFactory的生命周期一样,SessionFactory可以管理二级缓存

二级缓存同session级缓存一样,只缓存实体对象,普通属性的查询不会缓存

二级缓存一般使用第三方的产品,如EHCache

2.1常见缓存提供商

Cache

Providerclass

Type

ClusterSafe

QueryCache Supported

Hashtable(not intended for production use)

org.hibernate.cache.HashtableCacheProvider

memory

 

yes

EHCache

org.hibernate.cache.EhCacheProvider

memory,disk

 

yes

OSCache

org.hibernate.cache.OSCacheProvider

memory,disk

 

yes

SwarmCache

org.hibernate.cache.SwarmCacheProvider

clustered(ip multicast)

yes(clustered invalidation)

 

JBossTreeCache

org.hibernate.cache.TreeCacheProvider

clustered(ip multicast), transactional

yes(replication)

yes(clock sync req.)


2.2二级缓存的用法

这里以常见的EHCache为例

 

(1)hibernate.cfg.xml中开启二级缓存。

 

设置启用二级缓存:

 

<propertyname="hibernate.cache.use_second_level_cache">true</property>


 

设置二级缓存的实现类(缓存提供商)

 

<propertyname="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider

</property>


 

(2)导入所使用的二级缓存JAR

ehcache-1.2.3.jarcommons-logging.jarcommons-logging-1.0.4.jar


(3)src下写缓存配置文件:ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>

<ehcache>

	<diskStore path="C:\\cache" />

	<defaultCache 

		maxElementsInMemory="10000"

	     eternal="false"

		overflowToDisk="true" 

		timeToIdleSeconds="120" 

		timeToLiveSeconds="120"

		diskPersistent="false" />

</ehcache> 

maxElementsInMemory属性用于指定缓存中最多可放多少个对象。

overflowToDisk当内存中缓存的记录达到maxElementsInMemory时是否被持久化到硬盘中。保存路径由diskStore决定的
eternal
属性指定缓存是否永久有效。
timeToIdleSeconds
属性指定缓存多久未被使用便清理掉。
timeToLiveSeconds
属性指定缓存的生命长度。
diskPersistent
属性指定缓存是否被持久化到硬盘中,保存路径由diskStore标签指定。

(4)设置所有缓存的实体类

ahibernate.cfg.xml中设置

 

<class-cacheclass="cn.ineeke.entity.User"usage="read-only"/>


 

b*.hbm.xml中设置
<hibernate-mapping>

    <class name="cn.framelife.hibernate.entity.User" table="user" catalog="hibernate">

        <cache usage="read-only"/>

        <id name="id" type="java.lang.Integer">

            <column name="id" />

            <generator class="native" />

        </id>

        <property name="username" type="java.lang.String">

            <column name="username" length="45" not-null="true" />

        </property>

        <property name="password" type="java.lang.String">

            <column name="password" length="45" not-null="true" />

        </property>

    </class>

</hibernate-mapping> 

cAnnotation方式
@Entity

@Cache(usage=CacheConcurrencyStrategy.READ_ONLY)

@Table(name = "user", catalog = "hibernate")

public class User implements java.io.Serializable {}

d、缓存策略

只读缓存(read-only:没有什么好说的 ,最常用也最简单的。
/写缓存(read-write:程序可能要的更新数据 
不严格的读/写缓存(nonstrict-read-write:需要更新数据,但是两个事务更新同一条记录的可能性很小,性能比读写缓存好 
事务缓存(transactional:缓存支持事务,发生异常的时候,缓存也能够回滚,只支持jta环境。 

读写缓存和不严格读写缓存在实现上的区别在于,读写缓存更新缓存的时候会把缓存里面的数据换成一个锁,其他事务如果去取相应的缓存数据,发现被锁住了,然后就直接取数据库查询。 
hibernate2.1ehcache实现中,如果锁住部分缓存的事务发生了异常,那么缓存会一直被锁住,直到60秒后超时。 
不严格读写缓存不锁定缓存中的数据。

(5)测试

测试代码:


List<User> uesrs = session.createQuery("from User").list();

			System.out.println("--------------");

			Session s2 = sessionFactory.openSession();

			User user = (User) s2.get(User.class, 2);

			System.out.println(user.getUsername());


结果:

Hibernate: select user0_.id as id0_, user0_.first_name as first2_0_, user0_.last_name as last3_0_, user0_.password as password0_, user0_.username as username0_ from hibernate.user user0_

--------------

zhangsan


2.3打开二级缓存统计信息

List<User> uesrs = session.createQuery("from User").list();

			System.out.println("--------------");

			Session s2 = sessionFactory.openSession();

			User user = (User) s2.get(User.class, 2);

			System.out.println(user.getUsername());

			

			Statistics st = sessionFactory.getStatistics();

			System.out.println(st);

						    System.out.println(st.getSecondLevelCacheStatistics("cn.framelife.hibernate.entity.User").getEntries());


3、查询缓存

         hibernate的查询缓存是主要是针对普通属性结果集的缓存,而对于实体对象的结果集只缓存id。在一级缓存,二级缓存和查询缓存都打开的情况下作查询操作时这样的:查询普通属性,会先到查询缓存中取,如果没有,则查询数据库;查询实体(对象),会先到查询缓存中取id,如果有,则根据id到缓存(一级/二级)中取实体(对象),如果缓存中取不到实体,再查询数据库。

        查询缓存的生命周期,是不确定的,当前关联的表发生改变时,查询缓存的生命周期结束。

配置和使用:

查询缓存的配置和使用也是很简单的:
        1>
查询缓存的启用不但要在配置文件中进行配置
            

<propertyname="hibernate.cache.use_query_cache">true</property>

 


        2>
还要在程序中显示的进行启用
            

query.setCacheable(true);

 

 

测试:

Query query = session.createQuery("select u.username from User u where u.id > 210");

		//query.setCacheable(true);

		List<String> names = query.list();

		for (String name : names) {

			System.out.println(name);

		}

		System.out.println("================================");

		query = session.createQuery("select u.username from User u where u.id > 210");

		//query.setCacheable(true);

		names = query.list();

		for (String name : names) {

			System.out.println(name);

		}


没开启查询缓存(query.setCacheable(false))时的结果:

Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210

abcd

1111

11111

================================

Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210

abcd

1111

11111

一模一样的查询,执行了两次SQL


开启查询缓存(query.setCacheable(true))时的结果

Hibernate: select user0_.username as col_0_0_ from hibernate.user user0_ where user0_.id>210

abcd

1111

11111

================================

abcd

1111

11111

开启查询缓存后,一模一样两次的查询,只需要执行一次sql



注意:

当只是用Hibernate查询缓存,而关闭二级缓存的时候:

第一:如果查询的是部分属性结果集:

那么当第二次查询的时候就不会发出SQL直接从Hibernate查询缓存中取数据

第二:如果查询的是实体结果集eg(fromStudent) 这个HQL那么查询出来的实体,首先Hibernate查询缓存存放实体的ID

第二次查询的时候就到Hibernate查询缓存中取出ID一条一条的到数据库查询这样 将发出NSQL造成了SQL泛滥

在只打开查询缓存,关闭二级缓存的情况下,不要去查询实体对象。这样会造成很大的资源浪费。


当都开启Hibernate查询缓存和二级缓存的时候

第一:如果查询的是部分属性结果集:这个和上面只是用Hibernate查询缓存而关闭 二级缓存的时候,一致 因为不涉及实体不会用到二级缓存

第二:如果查询的是实体结果集eg(fromStudent) 这个HQL那么查询出来的实体,首先Hibernate查询缓存存放实体的ID,第二次查询,的时候就到Hibernate查询缓存中取出ID,拿到二级缓存区找数据,如果有数据就不会发出SQL如果都有一条SQL都不会发出直接从二级缓存中取数据

例子:

/**

		 * 开启查询缓存,开启二级缓存, 开启两个session,分别调用query.list查询实体对象

		 */

		// 如果不用查询缓存的话,那两个都发出查询语句,这也是默认的情况.

		try {

			session = sessionFactory.openSession();

			t = session.beginTransaction();

			Query query = session.createQuery("from User as u where u.id < 90");

			// 启用查询缓存

			query.setCacheable(true);

			List<User> list = query.list();

			for (User user : list) {

				System.out.println(user.getUsername());

			}

			t.commit();

		} catch (Exception e) {

			e.printStackTrace();

			t.rollback();

		} finally {

			session.close();

		}



		System.out.println("================================");



		try {

			session = sessionFactory.openSession();

			t = session.beginTransaction();

			Query query = session.createQuery("from User as u where u.id < 90");

			// 启用查询缓存

			query.setCacheable(true);

			// 不会发出查询语句,因为这种情况下,查询过程是这样的:

			// 在第一次执行list时,会把查询对象的id缓存到查询缓存里

			// 第二次执行list时, 会遍历查询缓存里的id到缓存里去找实体对象,由于这里开启了二级缓存,可以找到目标实体对象,

			// 所以就不会再发出n条查询语句.

			List<User> list = query.list();

			for (User user : list) {

				System.out.println(user.getUsername());

			}

			t.commit();

		} catch (Exception e) {

			e.printStackTrace();

			t.rollback();

		} finally {

			session.close();

		}


 

4集群缓存

集群中的每一台机子上都有缓存。修改的成本高。

 



5中央缓存

服务中使用一台独立的机器作为缓存。获取数据的成本高,修改的成本低。

 



6使用缓存的条件

1、服务中读取数据多于修改数据。
2、数据量不能大于内存容量。
3、对数据要有独享的控制。
4、可以容忍无效数据。

 



7注意事项

        不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。 
        如果受不了hibernate的诸多限制,那么还是自己在应用程序的层面上做缓存吧。 
        在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也要好些吧。


 

 

你可能感兴趣的:(Hibernate)