ibatis 開發指南 3
接 ibatis 開發指南 2Cache
在特定硬件基礎上(同時假設系統不存在設計上的缺漏和糟糕低效的 SQL 語句)
Cache往往是提升系統性能的最關鍵因素)。
相對 Hibernate 等封裝較為嚴密的 ORM 實現而言(因為對數據對象的操作實現
了較為嚴密的封裝,可以保證其作用范圍內的緩存同步,而 ibatis 提供的是半封閉
的封裝實現,因此對緩存的操作難以做到完全的自動化同步)。
ibatis 的緩存機制使用必須特別謹慎。特別是 flushOnExecute 的設定(見
“ibatis配置”一節中的相關內容),需要考慮到所有可能引起實際數據與緩存數據
不符的操作。如本模塊中其他Statement對數據的更新,其他模塊對數據的更新,甚
至第三方系統對數據的更新。否則,臟數據的出現將為系統的正常運行造成極大隱患。
如果不能完全確定數據更新操作的波及范圍,建議避免 Cache的盲目使用。
結合cacheModel來看:
<cacheModel >
id="product-cache"
type ="LRU"
readOnly="true"
serialize="false">
</cacheModel>
可以看到,Cache有如下幾個比較重要的屬性:
ÿ readOnly
ÿ serialize
ÿ type
readOnly
readOnly值的是緩存中的數據對象是否只讀。 這里的只讀并不是意味著數據對象一
旦放入緩存中就無法再對數據進行修改。而是當數據對象發生變化的時候,如數據對
象的某個屬性發生了變化,則此數據對象就將被從緩存中廢除,下次需要重新從數據
庫讀取數據,構造新的數據對象。
而 readOnly="false"則意味著緩存中的數據對象可更新,如 user 對象的 name
屬性發生改變。
只讀Cache能提供更高的讀取性能,但一旦數據發生改變,則效率降低。系統設計
時需根據系統的實際情況(數據發生更新的概率有多大)來決定 Cache的讀寫策略。
serialize
如果需要全局的數據緩存,CacheModel的 serialize屬性必須被設為true。否則數據
緩存只對當前 Session(可簡單理解為當前線程)有效,局部緩存對系統的整體性能提
升有限。
在 serialize="true"的情況下,如果有多個 Session同時從 Cache 中讀取某個
數據對象,Cache 將為每個 Session返回一個對象的復本,也就是說,每個 Session將
得到包含相同信息的不同對象實例。因而 Session 可以對其從 Cache 獲得的數據進行
存取而無需擔心多線程并發情況下的同步沖突。
Cache Type:
與hibernate類似,ibatis通過緩沖接口的插件式實現,提供了多種 Cache的實現機
制可供選擇:
1. MEMORY
2. LRU
3. FIFO
4. OSCACHE
MEMORY類型Cache與 WeakReference
MEMORY 類型的 Cache 實現,實際上是通過 Java 對象引用進行。ibatis 中,其實現類
為com.ibatis.db.sqlmap.cache.memory.MemoryCacheController, MemoryCacheController 內部,
使用一個HashMap來保存當前需要緩存的數據對象的引用。
這里需要注意的是Java2中的三種對象引用關系:
a SoftReference
b WeakReference
c PhantomReference
傳統的Java 對象引用,如:
public void doSomeThing(){
User user = new User()
……
}
當doSomeThing方法結束時,user 對象的引用丟失,其所占的內存空間將由JVM在下
次垃圾回收時收回。如果我們將user 對象的引用保存在一個全局的 HashMap中,如:
Map map = new HashMap();
public void doSomeThing(){
User user = new User();
map.put("user",user);
}
此時,user 對象由于在 map 中保存了引用,只要這個引用存在,那么 JVM 永遠也不會
收回user 對象所占用的內存。
這樣的內存管理機制相信諸位都已經耳熟能詳,在絕大多數情況下,這幾乎是一種完美 的解決方案。但在某些情況下,卻有些不便。如對于這里的 Cache 而言,當 user 對象使用
之后,我們希望保留其引用以供下次需要的時候可以重復使用,但又不希望這個引用長期保
存,如果每個對象的引用都長期保存下去的話,那隨著時間推移,有限的內存空間將立即被
這些數據所消耗殆盡。最好的方式,莫過于有一種引用方式,可以在對象沒有被垃圾回收器
回收之前, 依然能夠訪問此對象,當垃圾回收器啟動時, 如果此對象沒有被其他對象所使用,
則按照常規對其進行回收。
SoftReference、WeakReference、PhantomReference為上面的思路提供了有力支持。
這三種類型的引用都屬于“非持續性引用”,也就是說,這種引用關系并非持續存在,
它們所代表的引用的生命周期與JVM 的運行密切相關,而非與傳統意義上的引用一樣依賴
于編碼階段的預先規劃。
先看一個SoftReference的例子:
SoftReference ref;
public void doSomeThing(){
User user = new User();
ref = new SoftReference(user);
}
public void doAnotherThing(){
User user = (User)ref.get();//通過SoftReference獲得對象引用
System.out.println(user.getName());
}
假設我們先執行了 doSomeThing 方法,產生了一個 User 對象,并為其創建了一個
SoftReference引用。
之后的某個時刻,我們調用了doAnotherThing方法,并通過 SoftReference獲取 User 對
象的引用。
此時我們是否還能取得user 對象的引用?這要看JVM的運行情況。對于 SoftReference
而言,只有當目前內存不足的情況下,JVM 在垃圾回收時才會收回其包含的引用(JVM 并
不是只有當內存不足時才啟動垃圾回收機制,何時進行垃圾回收取決于各版本 JVM 的垃圾
回收策略。如某這垃圾回收策略為:當系統目前較為空閑,且無效對象達到一定比率時啟動
垃圾回收機制,此時的空余內存倒可能還比較充裕)。這里可能出現兩種情況,即:
ÿ JVM 目前還未出現過因內存不足所引起的垃圾回收,user 對象的引用可以通過
SoftReference從JVM Heap中收回。
ÿ JVM已經因為內存不足啟動了垃圾回收機制,SoftReference所包含的 user 對象的
引用被JVM所廢棄。此時 ref.get方法將返回一個空引用(null),對于上面
的代碼而言,也就意味著這里可能拋出一個 NullPointerException。
WeakReference比SoftReference在引用的維持性上來看更加微弱。 無需等到內存不足的
情況, 只要JVM啟動了垃圾回收機制, 那么WeakReference所對應的對象就將被JVM回收。
也就是說,相對SoftReference而言,WeakReference 被 JVM回收的概率更大。
PhantomReference 比 WeakReference 的引用維持性更弱。與 WeakReference 和
SoftReference不同,PhantomReference所引用的對象幾乎無法被回收重用。它指向的對象實
際上已經被JVM銷毀(finalize方法已經被執行),只是暫時還沒被垃圾回收器收回而已。
PhantomReference主要用于輔助對象的銷毀過程,在實際應用層研發中,幾乎不會涉及。
MEMORY類型的Cache正是借助SoftReference、 WeakReference以及通常意義上的Java
Reference實現了對象的緩存管理。
下面是一個典型的MEMORY類型 Cache配置:
<cacheModel id="user_cache" type="MEMORY">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="reference-type" value="WEAK" />
</cacheModel>
其中 flushInterval 指定了多長時間清除緩存,上例中指定每 24 小時強行清空緩存
區的所有內容。
reference-type屬性可以有以下幾種配置:
1. STRONG
即基于傳統的Java對象引用機制,除非對Cache顯式清空(如到了flushInterval
設定的時間;執行了flushOnExecute所指定的方法;或代碼中對Cache執行了清除
操作等),否則引用將被持續保留。
此類型的設定適用于緩存常用的數據對象,或者當前系統內存非常充裕的情況。
2. SOFT
基于 SoftReference 的緩存實現,只有 JVM 內存不足的時候,才會對緩沖池中的數
據對象進行回收。
此類型的設定適用于系統內存較為充裕,且系統并發量比較穩定的情況。
3. WEAK
基于 WeakReference 的緩存實現,當 JVM 垃圾回收時,緩存中的數據對象將被 JVM
收回。
一般情況下,可以采用WEAK的MEMORY型Cache配置。
LRU型 Cache
當 Cache達到預先設定的最大容量時,ibatis會按照“最少使用”原則將使用頻率最少
的對象從緩沖中清除。
<cacheModel id="userCache" type="LRU">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
可配置的參數有:
u flushInterval
指定了多長時間清除緩存, 上例中指定每24小時強行清空緩存區的所有內容。
u size Cache的最大容量。
FIFO型 Cache
先進先出型緩存,最先放入Cache中的數據將被最先廢除。可配置參數與 LRU型相同:
<cacheModel id="userCache" type="FIFO">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
OSCache
與上面幾種類型的Cache不同,OSCache來自第三方組織 Opensymphony。可以通過以
下網址獲得OSCache的最新版本(http://www.opensymphony.com/oscache/)。
在生產部署時,建議采用 OSCache,OSCache 是得到了廣泛使用的開源 Cache 實現
(Hibernate 中也提供了對 OSCache 的支持),它基于更加可靠高效的設計,更重要的是,
最新版本的 OSCache 已經支持 Cache 集群。如果系統需要部署在集群中,或者需要部署在
多機負載均衡模式的環境中以獲得性能上的優勢,那么OSCache在這里則是不二之選。
Ibatis中對于OSCache的配置相當簡單:
<cacheModel id="userCache" type="OSCACHE">
<flushInterval hours="24"/>
<flushOnExecute statement="updateUser"/>
<property name="size" value="1000" />
</cacheModel>
之所以配置簡單,原因在于,OSCache擁有自己的配置文件(oscache.properties)J。
下面是一個典型的OSCache配置文件:
#是否使用內存作為緩存空間
cache.memory=true
#緩存管理事件監聽器,通過這個監聽器可以獲知當前Cache的運行情況
cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JMSBroa
dcastingListener
#如果使用磁盤緩存(cache.memory=false),則需要指定磁盤存儲接口實現
#cache.persistence.class=com.opensymphony.oscache.plugins.diskpersistence.Disk
PersistenceListener
# 磁盤緩存所使用的文件存儲路徑
# cache.path=c:\\myapp\\cache
# 緩存調度算法,可選的有LRU,FIFO和無限緩存(UnlimitedCache) # cache.algorithm=com.opensymphony.oscache.base.algorithm.FIFOCache
# cache.algorithm=com.opensymphony.oscache.base.algorithm.UnlimitedCache
cache.algorithm=com.opensymphony.oscache.base.algorithm.LRUCache
#內存緩存的最大容量
cache.capacity=1000
# 是否限制磁盤緩存的容量
# cache.unlimited.disk=false
# 基于JMS的集群緩存同步配置
#cache.cluster.jms.topic.factory=java:comp/env/jms/TopicConnectionFactory
#cache.cluster.jms.topic.name=java:comp/env/jms/OSCacheTopic
#cache.cluster.jms.node.name=node1
# 基于JAVAGROUP的集群緩存同步配置
#cache.cluster.properties=UDP(mcast_addr=231.12.21.132;mcast_port=45566;ip_
ttl=32;mcast_send_buf_size=150000;mcast_recv_buf_size=80000):PING(timeout
=2000;num_initial_members=3):MERGE2(min_interval=5000;max_interval=10000
):FD_SOCK:VERIFY_SUSPECT(timeout=1500):pbcast.NAKACK(gc_lag=50;retransm
it_timeout=300,600,1200,2400,4800):pbcast.STABLE(desired_avg_gossip=20000):
UNICAST(timeout=5000):FRAG(frag_size=8096;down_thread=false;up_thread=fal
se):pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_loc
al_addr=true)
#cache.cluster.multicast.ip=231.12.21.132
配置好之后,將此文件放在 CLASSPATH 中,OSCache 在初始化時會自動找到此
文件并根據其中的配置創建緩存實例。
ibatis in Spring
這里我們重點探討Spring框架下的ibatis應用,特別是在容器事務管理模式下的ibatis
應用開發。
關于Spring Framework,請參見筆者另一篇文檔:
《Spring 開發指南》http://www.xiaxin.net/Spring_Dev_Guide.rar
針對ibatis,Spring配置文件如下:
Ibatis-Context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" >
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="dataSource" >
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName">
<value>net.sourceforge.jtds.jdbc.Driver</value>
</property>
<property name="url">
<value>jdbc:jtds:sqlserver://127.0.0.1:1433/Sample</value>
</property>
<property name="username">
<value>test</value>
</property>
<property name="password">
<value>changeit</value>
</property>
</bean>
<bean id="sqlMapClient" >
class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation">
<value>SqlMapConfig.xml</value>
</property>
</bean>
<bean id="transactionManager" >
class="org.springframework.jdbc.datasource.DataSourceTransactio
nManager"> <property name="dataSource">
<ref local="dataSource"/>
</property>
</bean>
<bean id="userDAO" class="net.xiaxin.dao.UserDAO">
<property name="dataSource">
<ref local="dataSource" />
</property>
<property name="sqlMapClient">
<ref local="sqlMapClient" />
</property>
</bean>
<bean id="userDAOProxy" >
class="org.springframework.transaction.interceptor.TransactionPro
xyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref local="userDAO" />
</property>
<property name="transactionAttributes">
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
</beans>
可以看到:
1. sqlMapClient節點
sqlMapClient節點實際上配置了一個sqlMapClient的創建工廠類。
configLocation屬性配置了ibatis映射文件的名稱。
2. transactionManager節點
transactionManager采用了Spring中的DataSourceTransactionManager。
3. userDAO節點 對應的,UserDAO需要配置兩個屬性,sqlMapClient和DataSource,
sqlMapClient將從指定的DataSource中獲取數據庫連接。
本例中Ibatis映射文件非常簡單:
sqlMapConfig.xml:
<sqlMapConfig>
<sqlMap resource="net/xiaxin/dao/entity/user.xml"/>
</sqlMapConfig>
net/xiaxin/dao/entity/user .xml
<sqlMap namespace="User">
<typeAlias alias="user" type="net.xiaxin.dao.entity.User" />
<insert id="insertUser" parameterClass="user">
INSERT INTO users ( username, password) VALUES ( #username#,
#password# )
</insert>
</sqlMap>
UserDAO.java:
public class UserDAO extends SqlMapClientDaoSupport implements
IUserDAO {
public void insertUser(User user) {
getSqlMapClientTemplate().update("insertUser", user);
}
}
SqlMapClientDaoSupport(如果使用ibatis 1.x版本,對應支持類是
SqlMapDaoSupport)是Spring中面向ibatis的輔助類,它負責調度DataSource、
SqlMapClientTemplate(對應ibatis 1.x版本是SqlMapTemplate)完成ibatis操作,
而DAO則通過對此類進行擴展獲得上述功能。上面配置文件中針對UserDAO的屬性設
置部分,其中的屬性也是繼承自于這個基類。
SqlMapClientTemplate對傳統SqlMapClient調用模式進行了封裝,簡化了上層訪問
代碼。
User .java:
public class User {
public Integer id;
public String username; public String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
測試代碼:
InputStream is = new FileInputStream("Ibatis-Context.xml");
XmlBeanFactory factory = new XmlBeanFactory(is);
IUserDAO userdao = (IUserDAO)factory.getBean("userDAOProxy");
User user = new User();
user.setUsername("Sofia");
user.setPassword("mypass");
userdao.insertUser(user);
對比前面 ibatis 程序代碼,我們可以發現 UserDAO.java 變得異常簡潔,這也正是
Spring Framework為我們帶來的巨大幫助。
Spring以及其他IOC 框架對傳統的應用框架進行了顛覆性的革新, 也許這樣的評價有
點過于煽情,但是這確是筆者第一次跑通 Spring HelloWorld 時的切身感受。有興趣的
讀者可以研究一下Spring framework,enjoy it!
posted on 2012-09-20 00:56 奮斗成就男人 閱讀(579) 評論(0) 編輯 收藏 所屬分類: J2EE