zoukankan      html  css  js  c++  java
  • Hibernate二级缓存


    Hibernate中的一级缓存是Session范围内的,而二级缓存是SessionFactory范围的,
    需要使用第三方的实现。本文通过注解的方式为Hibernate配置二级缓存,采用的
    第三方实现是Ehcache。

    项目的结构如下,本文主要用到了:
    Account.java
    CachedAccount.java
    SecondaryCache.java
    ehcache.xml
    hibernate.cfg.xml



    为一个实体类进行二级缓存配置可以分为三步:

    1.首先,要在hibernate.cfg.xml中开启二级缓存,并设置好Hibernate的provider。
    因为Hibernate没有自己实现二级缓存,而只是为不同的第三方缓存提供了不同
    的provider类。
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
            "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
    <hibernate-configuration>
        <session-factory>
            <property name="hibernate.connection.driver_class">com.microsoft.sqlserver.jdbc.SQLServerDriver</property>
            <property name="hibernate.connection.url">jdbc:sqlserver://192.168.1.102:1433;databaseName=Bank</property>
            <property name="hibernate.connection.username">sa</property>
            <property name="hibernate.connection.password">1qaz2wsx</property>
            <property name="hibernate.dialect">org.hibernate.dialect.SQLServerDialect</property>
            <property name="connection.pool_size">1</property>
            <property name="show_sql">true</property>
            <!-- <property name="hbm2ddl.auto">create</property> -->
            
            <property name="hibernate.cache.use_second_level_cache">true</property>  
            <property name="hibernate.cache.use_query_cache">true</property>
        	<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
        	  
        </session-factory>
    </hibernate-configuration>
    虽然已经启用了二级缓存,但是它不会默认就对所有实体类都进行缓存,那样
    的话内存开销太大,所有接下来我们还需要对具体的实体类进行缓存策略和
    并发策略的配置。

    2.编写ehcache.xml的配置文件,在这里除了可以对默认缓存策略进行配置外,
    还可以对每个实体类进行不同的配置。具体可以配置的选项请参加ehcache的
    xml schema文件:http://ehcache.org/ehcache.xsd 
    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache>
    
    	<!-- 如果内存放不下,就放到磁盘上的一个路径 -->
    	<!-- <diskStore path="e:/ehcache" /> -->
    
    	<!-- 内存中存放最多的对象个数 -->
    	<defaultCache maxElementsInMemory="2000" eternal="false"
    		timeToIdleSeconds="50" timeToLiveSeconds="60" overflowToDisk="false" />
    		
    	<!-- 保存的对象 -->
    	<cache name="com.cdai.orm.hibernate.annotation.Account" maxElementsInMemory="200"
    		eternal="false" timeToIdleSeconds="50" timeToLiveSeconds="60"
    		overflowToDisk="false" />
    		
    	<cache name="com.cdai.orm.hibernate.transaction.AccountVersion" maxElementsInMemory="0"/>
    	
    </ehcache>

    3.在实体类上加上Cache注解,并指定并发策略。因为二级缓存是SessionFactory
    范围内的,所以不同Session同时修改一个实体类就会产生并发问题。正因为对共享
    数据的并发访问从底层数据库提前到了应用程序中的二级缓存层,所以在数据库
    层面上涉及的各种并发问题,提前在二级缓存应用程序层上出现了。
    package com.cdai.orm.hibernate.cache;
    
    import java.io.Serializable;
    
    import javax.persistence.Column;
    import javax.persistence.Entity;
    import javax.persistence.Id;
    import javax.persistence.Table;
    
    import org.hibernate.annotations.Cache;
    import org.hibernate.annotations.CacheConcurrencyStrategy;
    
    @Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
    @Entity
    @Table(name = "tb_cached_account")
    public class CachedAccount implements Serializable {
    
    	private static final long serialVersionUID = 5018821760412231859L;
    
    	@Id
    	@Column(name = "col_id")
    	private long id;
    	
    	@Column(name = "col_balance")
    	private long balance;
    
    	public CachedAccount() {
    	}
    	
    	public CachedAccount(long id, long balance) {
    		this.id = id;
    		this.balance = balance;
    	}
    
    	public long getId() {
    		return id;
    	}
    
    	public void setId(long id) {
    		this.id = id;
    	}
    
    	public long getBalance() {
    		return balance;
    	}
    
    	public void setBalance(long balance) {
    		this.balance = balance;
    	}
    
    	@Override
    	public String toString() {
    		return "CachedAccount [id=" + id + ", balance=" + balance + "]";
    	}
    	
    }
    CachedAccount.java只是比Account.java多了Cache注解,其余代码完全相同。

    下面来看一个例子,验证二级缓存是否配置成功。
    package com.cdai.orm.hibernate.cache;
    
    import org.hibernate.Query;
    import org.hibernate.Session;
    import org.hibernate.SessionFactory;
    import org.hibernate.cfg.AnnotationConfiguration;
    
    import com.cdai.orm.hibernate.annotation.Account;
    
    public class SecondaryCache {
    
    	public static void main(String[] args) {
    
    		SessionFactory sessionFactory = 
    				new AnnotationConfiguration().
    					addFile("hibernate/hibernate.cfg.xml").				
    					configure().
    					addAnnotatedClass(CachedAccount.class).
    					addAnnotatedClass(Account.class).
    					buildSessionFactory();
    
    		Session session1 = sessionFactory.openSession();
    		Session session2 = sessionFactory.openSession();
    
    		// Cached get
    		CachedAccount accountc1 = (CachedAccount) session1.get(CachedAccount.class, new Long(1));
    		CachedAccount accountc2 = (CachedAccount) session2.get(CachedAccount.class, new Long(1));
    		CachedAccount accountc3 = (CachedAccount) session2.get(CachedAccount.class, new Long(1));
    		System.out.println(accountc1 == accountc2);
    		System.out.println(accountc3 == accountc2);
    
    		// Cached query
    		Query query = session1.createQuery(" from CachedAccount acct where acct.id=:id ");
    		query.setCacheable(true);
    		query.setParameter("id", new Long(1));
    		accountc1 = (CachedAccount) query.uniqueResult();
    		System.out.println(accountc1);
    		
    		query.setParameter("id", new Long(1));
    		accountc1 = (CachedAccount) query.uniqueResult();
    		System.out.println(accountc1);
    		
    		// Not-cached
    		Account account1 = (Account) session1.get(Account.class, new Long(1));
    		Account account2 = (Account) session2.get(Account.class, new Long(1));
    		System.out.println(account1 == account2);
    		
    		session1.close();
    		session2.close();
    		sessionFactory.close();
    	}
    
    }

    log输出为:

    Hibernate: select cachedacco0_.col_id as col1_0_0_, cachedacco0_.col_balance as col2_0_0_ from tb_cached_account cachedacco0_ where cachedacco0_.col_id=?
    false
    true
    Hibernate: select cachedacco0_.col_id as col1_0_, cachedacco0_.col_balance as col2_0_ from tb_cached_account cachedacco0_ where cachedacco0_.col_id=?
    CachedAccount [id=1, balance=1000]
    CachedAccount [id=1, balance=1000]
    Hibernate: select account0_.col_id as col1_1_0_, account0_.col_balance as col2_1_0_ from tb_account account0_ where account0_.col_id=?
    Hibernate: select account0_.col_id as col1_1_0_, account0_.col_balance as col2_1_0_ from tb_account account0_ where account0_.col_id=?
    false

    可以看到对实体类CachedAccount配置了Cache注解,二级缓存对它已经生效,
    三次get()调用只执行了一次真正的SQL查询语句。而之后的Account实体类每次
    调用get()都会执行一次SQL语句。

    另外我们也注意到,虽然CachedAccount已经保存在二级缓存中,但是我们在不同
    Session查询得到的却是不同的对象。CachedAccount不是直接缓存在二级缓存中的
    吗?这是为什么呢?

    因为如果直接将实体类对象缓存在二级缓存中,然后将同一个实体类返回给不同的
    Session的话,虽然比较节省缓存,但是当不同的Session都可能长时间操作这一个对象
    ,这样就需要对这些不同线程中的操作进行同步,性能会很差。

    所以二级缓存一般只是保存散装的数据(对象的属性),当Session加载时将散装数据
    组装成一个新的实体类对象返回给它。虽然耗费内存,但是不需要同步了,二级缓存
    只需要在每个Session获得对象时同步,之后每个Session的事务都操纵各自的对象,就
    无需同步了。

    此外,对查询缓存还要注意一点,除了在hibernate.cfg.xml中开启外,还要在查询前
    调用query.setCacheable(true);才能使用查询缓存。


    结束语

    摘录一段别人的总结:

    “不要想当然的以为缓存一定能提高性能,仅仅在你能够驾驭它并且条件合适的情况下才是这样的。
    hibernate的二级缓存限制还是比较多的,不方便用jdbc可能会大大的降低更新性能。在不了解原理
    的情况下乱用,可能会有1+N的问题。不当的使用还可能导致读出脏数据。 如果受不了hibernate的
    诸多限制,那么还是自己在应用程序的层面上做缓存吧。 

    在越高的层面上做缓存,效果就会越好。就好像尽管磁盘有缓存,数据库还是要实现自己的缓存,
    尽管数据库有缓存,咱们的应用程序还是要做缓存。因为底层的缓存它并不知道高层要用这些数据
    干什么,只能做的比较通用,而高层可以有针对性的实现缓存,所以在更高的级别上做缓存,效果也
    要好些吧。”

    今天先到这,具体什么是1+N问题以后再进行专门的研究。


  • 相关阅读:
    Scheduler踩坑记录
    关于RedisTemplate的map存储踩坑记录
    关于HashMap的加载因子相关理解
    Mybatis 分页插件PageHelper 遇坑
    Linux 下 Mysql忘记密码重置
    Eclipse MAT和jvisualvm分析内存溢出
    使用jdk自带工具jvisualvm 分析内存dump文件
    EUREKA 删除 or 强制下线/上线 实例
    Idea 远程调试jenkins 项目
    spring 事务传播行为类型
  • 原文地址:https://www.cnblogs.com/xiaomaohai/p/6157819.html
Copyright © 2011-2022 走看看