hibernate 二級快取 + cluster

原文出自:http://mmmmtodd.blogspot.com/2012/03/hibernate-cluster.html

 

2012年3月15日星期四

hibernate 二級快取 + cluster

一級快取的認知

hibernate 所提供的一級快取(Session Level),在同一個 session 下,如果對主鍵 select 兩次 相同的資料,其只會對 db 進行一次的 select,這個即為一級快取,很好了解。
Session level 快取會隨著 Session 生命週期起始與消滅。
來看良葛格的解釋 :
Session會維護一個Map容器,並保留與目前Session發生關係的資料,當您透過 主鍵 來載入資料時,Session會先依據所要載入的類別與所給定的 主鍵 ,看看Map中是否已有資料,如果有的話就返回, 若沒有就對資料庫進行查詢 ,並在載入資料後在Map中維護。


來看一下程式內容
public String firstCache() {
    Users userA = usersService.getById(1L);
    Users userB = usersService.getById(1L);
    System.out.println("我們相同嗎 ? " + (userA == userB));
    
    System.out.println("我是一級快取");
    return SUCCESS;
  }
result as below
我們相同嗎 ? true
  我是一級快取

結果顯示 : from 良格葛 -- 第二次查詢資料時,在快取中找到資料物件,於是直接返回,這與第一次查詢到的資料物件是同一個實例,所以會顯示 true 的結果。

那要怎麼將快取清掉呢?
可以透過evict()將某個物件從快取中移去,或使用clear()清除快取中的所有物件,
例如 :
public String firstCache() {
    Users userA = usersService.getById(1L);
    // session.clear() || session.evict(userA);
    Users userB = usersService.getById(1L);
    System.out.println("我們相同嗎 ? " + (userA == userB));
    
    System.out.println("我是一級快取");
    return SUCCESS;
  }

則答案就會變成 false
我們相同嗎 ? false
  我是一級快取


小結:當讀取的資料很大時,每一次 request 進來就去讀取一次,會造成相當的負擔,所以有了二級快取這個東西。
簡單講就是當資料沒有修改時,不管讀幾次,只要有設定二級快取,session 就算關閉了以後,二級快取裡如果有的話,也不會對 DB select。


以下開始介紹二級快取:

首先,先來看一下程式片段:
public String noCache() {
    Users uA = usersService.getByAccount("markyeh");
    System.out.println("我沒有 快取 : " + uA.getUpdateTime());
    return SUCCESS;
  }
然後我對點了這個連結兩次 http://localhost:8080/Cache/noCache.do

result as below:
Hibernate: 
    /* 
FROM
    Users 
WHERE
    account = ? */ select
        users0_.id as id0_,
        users0_.account as account0_,
        users0_.address as address0_,
        users0_.area1 as area4_0_,
        users0_.area2 as area5_0_,
        users0_.create_time as create6_0_,
        users0_.email as email0_,
        users0_.icon as icon0_,
        users0_.mobile as mobile0_,
        users0_.name as name0_,
        users0_.password as password0_,
        users0_.reg_ip as reg12_0_,
        users0_.reg_random_code as reg13_0_,
        users0_.roc_id as roc14_0_,
        users0_.score as score0_,
        users0_.state as state0_,
        users0_.update_time as update17_0_ 
    from
        users users0_ 
    where
        users0_.account=?
我沒有 快取 : 2012-03-15 10:37:41.395
Hibernate: 
    /* 
FROM
    Users 
WHERE
    account = ? */ select
        users0_.id as id0_,
        users0_.account as account0_,
        users0_.address as address0_,
        users0_.area1 as area4_0_,
        users0_.area2 as area5_0_,
        users0_.create_time as create6_0_,
        users0_.email as email0_,
        users0_.icon as icon0_,
        users0_.mobile as mobile0_,
        users0_.name as name0_,
        users0_.password as password0_,
        users0_.reg_ip as reg12_0_,
        users0_.reg_random_code as reg13_0_,
        users0_.roc_id as roc14_0_,
        users0_.score as score0_,
        users0_.state as state0_,
        users0_.update_time as update17_0_ 
    from
        users users0_ 
    where
        users0_.account=?
我沒有 快取 : 2012-03-15 10:37:41.395

執行結果:hibernate 對 db 進行了兩次的 select,但明明相同的東西,怎麼做才能只對 db select 一次就好呢?


這裡就開始說明如何設定 hibernate 的二級快取:
我的環境如下
struts2 + hibernate + spring + ehcache(第三方實作 cache 的套件)


1. 在 spring 設定檔中,在 hibernate 注入的 bean 裡,增加二級快取的設定
<bean id="sessionFactory"
    class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="ihergoSqlDataSource" />
    <property name="hibernateProperties">
      <props>
        <!-- 
        <prop key="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</prop>
        -->
        <prop key="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</prop>
        <prop key="hibernate.connection.characterEncoding">utf-8</prop>
        <prop key="hibernate.show_sql">true</prop>
        <prop key="hibernate.format_sql">true</prop>
        <prop key="hibernate.use_sql_comments">true</prop>
        <prop key="hibernate.cache.use_second_level_cache">true</prop> <!-- 要不要使用二級快取 -->
        <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop> <!-- 指定為 EHCache -->
        <prop key="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory</prop> 
        <prop key="hibernate.cache.use_query_cache">true</prop> <!-- query 也要快取-->
        <prop key="hibernate.hbm2ddl.auto">update</prop>
      </props>
    </property>
    <property name="packagesToScan">
      <list>
        <value>mt.buyer.db.entity</value>
      </list>
    </property>
  </bean>
2. 設定 entity (以 annotation 為例)
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) // <---這裡, 只定為 read_write
@Table(name = "users")
public class Users implements Serializable {

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  private long id;

  /**
   * 帳號
   */
  @Column(name = "account", length = 20, nullable = false)
  private String account;
  ...
}
3. 編寫 dao
// 快取
  public Users getByAccountUserCache(final String account) {
    final String hql = "FROM Users WHERE account = :account ";
    
    List memberList = getHibernateTemplate().executeFind(
        new HibernateCallback>() {
          public List doInHibernate(Session hbmSs)
              throws HibernateException, SQLException {
            // TODO Auto-generated method stub
            Query query = hbmSs.createQuery(hql);
            query.setString("account", account);
            query.setCacheable(true); // <---- 這裡
            return query.list();
          }
        });

    if (memberList != null && memberList.size() > 0) {
      return (Users) memberList.get(0);
    }
    return null;
  }

  // 沒有快取
  public Users getByAccount(String account) {
    final String hql = "FROM Users WHERE account = ?";
    List memberList = getHibernateTemplate().find(hql, account);
    if (memberList != null && memberList.size() > 0) {
      return (Users) memberList.get(0);
    }
    return null;
  }
4. 增加設定檔 ehcache.xml 並放在 src 下(預設讀取入徑, 也可用 spring 注入)
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
	<diskStore path="java.io.tmpdir" />
	<defaultCache maxElementsInMemory="10000" eternal="false"
		overflowToDisk="true" timeToIdleSeconds="300" timeToLiveSeconds="180"
		diskPersistent="false" diskExpiryThreadIntervalSeconds="120" />

	<!-- Hibernate -->
	<cache name="org.hibernate.cache.StandardQueryCache"
		maxElementsInMemory="5" eternal="false" timeToLiveSeconds="120"
		overflowToDisk="true">
	</cache>

	<cache name="org.hibernate.cache.UpdateTimestampsCache"
		maxElementsInMemory="5000" eternal="true" overflowToDisk="true">
	</cache>
</ehcache>
5. 測試程式 (action)
public String cache() {
    Users uA = usersService.getByAccountUserCache("markyeh");
    Users uB = usersService.getByAccountUserCache("markyeh");
    System.out.println("快取 : " + uA.getUpdateTime());
    System.out.println("快取 : " + uB.getUpdateTime());
    return SUCCESS;
  }
6. 執行連結 : http://localhost:8080/Cache/cache.do
Hibernate: 
    /* 
FROM
    Users 
WHERE
    account = :account  */ select
        users0_.id as id0_,
        users0_.account as account0_,
        users0_.address as address0_,
        users0_.area1 as area4_0_,
        users0_.area2 as area5_0_,
        users0_.create_time as create6_0_,
        users0_.email as email0_,
        users0_.icon as icon0_,
        users0_.mobile as mobile0_,
        users0_.name as name0_,
        users0_.password as password0_,
        users0_.reg_ip as reg12_0_,
        users0_.reg_random_code as reg13_0_,
        users0_.roc_id as roc14_0_,
        users0_.score as score0_,
        users0_.state as state0_,
        users0_.update_time as update17_0_ 
    from
        users users0_ 
    where
        users0_.account=?
快取 : 2012-03-15 10:37:41.395
快取 : 2012-03-15 10:37:41.395
7. 可以發現, 第二次的查詢就已經沒有再進 db search 了, 如果再執行相同連結一次(也就是再發一個 request 到後台)
快取 : 2012-03-15 10:37:41.395
快取 : 2012-03-15 10:37:41.395
可以看到,都是從二級快取的地方取得資料,這時,大家會問,那修改時怎麼判斷要不要取得新資料 8. 我們加入下面的程式碼:
public String update() {
    // Users u = usersService.getByAccountUserCache("markyeh");
    Users u = usersService.getByAccount("markyeh");
    u.setArea1(3);
    u.setUpdateTime(new Date());
    usersService.update(u);
    System.out.println("我修改了 : " + u.getUpdateTime());
    
    return SUCCESS;
  }
9. 分別依順序執行 3 個連結 a) http://localhost:8080/Cache/cache.do b) http://localhost:8080/Cache/update.do c) http://localhost:8080/Cache/cache.do
// a) select
Hibernate: 
    /* 
FROM
    Users 
WHERE
    account = :account  */ select
        users0_.id as id0_,
        users0_.account as account0_,
        users0_.address as address0_,
        users0_.area1 as area4_0_,
        users0_.area2 as area5_0_,
        users0_.create_time as create6_0_,
        users0_.email as email0_,
        users0_.icon as icon0_,
        users0_.mobile as mobile0_,
        users0_.name as name0_,
        users0_.password as password0_,
        users0_.reg_ip as reg12_0_,
        users0_.reg_random_code as reg13_0_,
        users0_.roc_id as roc14_0_,
        users0_.score as score0_,
        users0_.state as state0_,
        users0_.update_time as update17_0_ 
    from
        users users0_ 
    where
        users0_.account=?
快取 : 2012-03-15 16:39:16.944
快取 : 2012-03-15 16:39:16.944
第一次執行, 結果一樣, 不會撈兩次

// b) update
Hibernate: 
    /* 
FROM
    Users 
WHERE
    account = ? */ select
        users0_.id as id0_,
        users0_.account as account0_,
        users0_.address as address0_,
        users0_.area1 as area4_0_,
        users0_.area2 as area5_0_,
        users0_.create_time as create6_0_,
        users0_.email as email0_,
        users0_.icon as icon0_,
        users0_.mobile as mobile0_,
        users0_.name as name0_,
        users0_.password as password0_,
        users0_.reg_ip as reg12_0_,
        users0_.reg_random_code as reg13_0_,
        users0_.roc_id as roc14_0_,
        users0_.score as score0_,
        users0_.state as state0_,
        users0_.update_time as update17_0_ 
    from
        users users0_ 
    where
        users0_.account=?
Hibernate: 
    /* update
        mt.buyer.db.entity.Users */ update
            users 
        set
            account=?,
            address=?,
            area1=?,
            area2=?,
            create_time=?,
            email=?,
            icon=?,
            mobile=?,
            name=?,
            password=?,
            reg_ip=?,
            reg_random_code=?,
            roc_id=?,
            score=?,
            state=?,
            update_time=? 
        where
            id=?
我修改了 : Thu Mar 15 16:40:19 CST 2012


// c) select
Hibernate: 
    /* 
FROM
    Users 
WHERE
    account = :account  */ select
        users0_.id as id0_,
        users0_.account as account0_,
        users0_.address as address0_,
        users0_.area1 as area4_0_,
        users0_.area2 as area5_0_,
        users0_.create_time as create6_0_,
        users0_.email as email0_,
        users0_.icon as icon0_,
        users0_.mobile as mobile0_,
        users0_.name as name0_,
        users0_.password as password0_,
        users0_.reg_ip as reg12_0_,
        users0_.reg_random_code as reg13_0_,
        users0_.roc_id as roc14_0_,
        users0_.score as score0_,
        users0_.state as state0_,
        users0_.update_time as update17_0_ 
    from
        users users0_ 
    where
        users0_.account=?
快取 : 2012-03-15 16:40:19.524
快取 : 2012-03-15 16:40:19.524
可以發現,ehcache 會自動判斷 cache 裡的東西跟 db 的記錄是不是不一樣了,如果不一樣會再 select 一次放入二級快取裡


小結: ehcache.xml 可以設定的方式很多,這裡只是 demo,而我也還在學習,官方表示還可以做到 tx 的管理,所以還是去看一下文件比較好。

參考文件:

http://www.ehcache.org/documentation/user-guide/hibernate

http://renren.it/a/JAVAbiancheng/Spring/20101022/55282.html

http://caterpillar.onlyfun.net/Gossip/HibernateGossip/SecondLevelCache.html





進階,因公司架構 tomcat 為 cluster ,那...二級快取有辦法共用嗎? 答案是可以的


以下介紹 cluster 環境下怎麼共用二級快取的資料:

參考了官方文件後,必須加入第三方的套件 terracotta 先來看看官方的說明:

http://terracotta.org/documentation/enterprise-ehcache/get-started

其這套件就是一個 server ,經由這個 server 幫你控管各 session

而我測試的流程大概如下

1. 先裝 2 個tomcat,分別設定 port 為 8080, 8081

2. 加入相關 jar 檔

3. 修改 ehcache.xml

4. depoly 程式

5. 將 terracotta server run 起來

6. 測試



就從第二點開始:

2. ehcache 官網有寫,要做到For Terracotta clustering, download a full Ehcache distribution. 所以先下載好檔案,並解壓縮,內容會長這樣
將相關的 lib 加入 ap 裡: ehcache-terracotta-2.5.1.jar terracotta-toolkit-1.4-runtime-4.1.0.jar ... 3.
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true"
	monitoring="autodetect">

	<terracottaConfig url="localhost:9510" /> <!-- 連接到 terracotta server 的 ip 與 port -->



	<defaultCache maxElementsInMemory="10000" eternal="false"
		overflowToDisk="false" timeToIdleSeconds="300" timeToLiveSeconds="180"
		diskPersistent="false" diskExpiryThreadIntervalSeconds="120" /> <!-- diskPersistent 必須設成 false -->

	<!-- Hibernate -->
	<cache name="org.hibernate.cache.StandardQueryCache"
		maxElementsInMemory="5" eternal="false" timeToLiveSeconds="120"
		overflowToDisk="false"> <!-- overflowToDisk 必須設成 false -->
		<terracotta />
	</cache>

	<cache name="org.hibernate.cache.UpdateTimestampsCache"
		maxElementsInMemory="5000" eternal="true" overflowToDisk="false"> <!-- overflowToDisk 必須設成 false -->
		<terracotta />
	</cache>
</ehcache>
5. 在剛那個資料夾裡, 找到 \ehcache-2.5.1\terracotta\bin\start-tc-server.bat 並執行, 將 server run 起來
6. 測試: 分別執行 a) http://localhost:8080/Cache/cache.do b) http://localhost:8081/Cache/cache.do 成功...這裡就不貼圖了 提供程式下載的連結 https://www.asuswebstorage.com/navigate/share/GYYVXWYN7Y
...

補充 :

分散式 cache 參考連結
http://www.informit.com/guides/content.aspx?g=java&seqNum=630
http://bbs.51osos.com/thread-4570-1-1.html
http://terracotta.org/documentation/enterprise-ehcache/configuration-guide

然後許多東西都沒有講, 如: terracotta 也可以加入 xml 設定檔, 設定 port, 而且 terracotta 本身也可以做 cluster
反正看一下官網就對了
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值