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
反正看一下官網就對了

以上

2012年3月7日 星期三

Subclipse



本文章介紹基本的合併流程, 以Subclipse為例


在要合併的目錄, 可以由 Team --> 列出Subclipse所有功能


假設bdp是trunk project, bdp2是branch project

1.首先將新專案bdp2分支 Trunk > Branch


2.Branch開發到一半, 若要跟Trunk合併, 這時要選擇bdp2(也就是branch的project)按右鍵, 
選Team -> 合併 ->
選擇 Merge a range of revisions













3.開發完成後, 要回主線 Trunk < Branch, 這時要選擇bdp(也就是trunk的project)按右鍵, 

選Team -> 合併 ->

選擇 Reintegrate a branch



4.選擇要回去的branch的同階目錄(若從WebContent開始合併, 就指到branch的WebContent)

5.每次合併的結果, 可從 Merge Results Tab查看


Ignored File - A file ignored by version control. You can control what resources will be ignored by going to Window → Preferences → Team → Ignored Resources.
Unversioned File - A file not under version control. These are typically new files that you have not committed to the repository yet.
New File - A versioned file that needs to be added to the remote repository. These are typically files you have either renamed, or moved to a different directory.
Deleted File - A deleted folder. These are folders that you have deleted locally without yet committing the changes to the repository. Note that files are usually removed from the view when they're deleted locally, so they are normally not seen with this icon.
Synchronized File - A file with no local changes.
Local Changes - A file with local, uncommitted changes.
Locked File - A locked file.
Needs Lock - A file that needs a lock to be edited. These are typically files that cannot easily be merged (i.e. binary files), but may have many editors. A file needs a lock when its svn:needs-lock property is set, and Subversion will attempt to make these files read-only in the file system to enforce proper lock negotation.
Conflicted File - A conflicted file. These are typically files that had a commit/update conflict that you marked to resolve later.
Tree Conflicted File - A file that has a tree conflict. These are typically files that have local changes, but have since been moved, removed, or renamed in the repository since the last local copy update.
External File - A file that is external to the project. Linked external files cannot be committed to the repository.
Switched File - A file that has been switched. These are files which belongs to a different working copy than their local parent directory.
In a synchronization view (e.g. via Synchronize with Repository or Commit), as previously described, there are icons which indicate which direction a change is occurring in as well as additional information about that change. These are described below:
Outgoing
Commit Changes - A file whose contents have been modified and will be committed to the repository.
Commit Addition - A file that will be newly added to the repository. This may coincide with a file removal in cases where a file is moved or renamed..
Commit Removal - A file that will be removed from the repository. This may coincide with a file addition in cases where a file is moved or renamed.
Property Change - A file with property changes, in the Commit dialog. On the Synchronize tab, property changes are currently reflected as a normal file modification (Commit Changes).

在上次commit後, 部份屬性有變動
由合併的目錄, 會記錄合併後相關訊息(svn:mergeinfo)
以trunk合併branch情境來看
假設 trunk/WebContent merge branch/WebContent
原本上次合併是由 100 ~ 120, 目前最新版本是121, 合併後也會記錄一個版本, 所以會變成122, 那麼mergeinfo這檔案就是記錄100 ~ 122版本, 但重點是我們還沒有commit
所以此訊息會提示, 我們上次commit是100 ~ 120, 合併後版本變成 100 ~ 122, 它希望我們能commit這版本
為了要記錄這次合併的版本, 所做的提示(因為我們可能在下一階又commit其他變動)
Incoming
Remote Changes - A file that has content changes committed to the repository that will be applied to the local copy.
Pull Addition - A new file that will be added to the local copy from the repository. Like the outgoing file addition, this may be the result of a move or rename.
Pull Removal - A file that will be removed from the local copy because it has been removed from the repository. Like the outgoing file removal, this may be the result of a move or rename.
Conflict
Conflicting Versions - A file that has been changed in both the local copy and repository independently, causing a need for conflict resolution. Fixing this condition involves opening up the conflict view or forcibly overwriting changes locally or remotely.
Synchronization Tree Conflict - A file that has a tree conflict. This can occur when there are new changes to a file on one end (either local or remote), and the file is moved, removed, or renamed on the other.
Finally, the Synchronize with Repository option opens the Synchronize tab (Synchronize Tab) with the following buttons:
Synchronize Source - Synchronizes the local copy with the currently selected repository when clicked. Selecting from the drop-down allows switching between different remote code bases.
Show Incoming - Shows only incoming changes (remote → local).
Show Outgoing - Shows only outgoing changes (local → remote).
Show Incoming and Outgoing - Shows both incoming and outgoing changes (remote ↔ local).
Show Conflicts - Shows conflicting changes.
Update Incoming - Updates all local resources with incoming changes after prompt.
Commit Outgoing - Brings up the commit dialog to commit all outgoing changes.
Show Changesets - Breaks up the change lists by revisions.

--

Conflict
Delete:只要是目錄有變動, 都算是
Add:
Edit:
Local:本機修改
Incoming:是别人修改, 你要Update或Merge進来

Tree Conflict
update情況
Local delete, incoming edit upon update
Local edit, incoming delete upon update
Local delete, incoming delete upon update (二邊都刪掉, 直接標記解決就行)

merge情況
Local missing, incoming edit upon merge
Local edit, incoming delete upon merge (當trunk合併branch, 若trunk修改此檔案, 但trunk刪除時)
Local add, incoming add upon merge (當branch回trunk, 若truck已有此檔案, 但trunk也新增)


測試:
Local delete, incoming edit upon update









最好是每次合併後, 都檢查一下是否有 Tree Conflict, 因為不是每次都有提示