ibatis 緩存
顧名思義,就是將從數據庫中查詢出來的數據在某個緩衝區域暫時保存起來,在需要數據的時候從該緩衝區中讀取,而不是從數據庫中讀取,從而減少對數據庫訪問次數,達到減少系統開銷,提高性能的目的。
在本文中,我將結合實例講述ibatis的緩存使用及相關原理。
首先我們來看一個ibatis應用所需要的配置文件:
(注:由於我們只關注ibatis的緩存,所以在ibatis的配置文件中我們只討論與緩存相關的配置,其它的配置我們將省略!)
1.sql-map的配置,查看配置文件的dtd聲明:
<!ELEMENT sqlMap (typeAlias* | cacheModel* | resultMap* | parameterMap* | sql* | statement* | insert* | update* | delete* | select* | procedure*)+>
以上是sql-map配置文件可以使用的元素,其中有些元素還有子元素及屬性,這些都可以通過查看ibatis的dtd文件來知曉。在這個dtd聲明中有一個cacheModel元素特別耀眼,相信讀者已經猜測出來了,的確,該元素即爲緩存模型,再看看該元素的子元素及屬性:
<!ELEMENT cacheModel (flushInterval?, flushOnExecute*, property*)+>
<!ATTLIST cacheModel
id CDATA #REQUIRED
type CDATA #REQUIRED
readOnly (true | false) #IMPLIED
serialize (true | false) #IMPLIED
>
再看每個子元素的相關屬性:
<!ELEMENT flushInterval EMPTY>
<!ATTLIST flushInterval
milliseconds CDATA #IMPLIED
seconds CDATA #IMPLIED
minutes CDATA #IMPLIED
hours CDATA #IMPLIED
>
<!ELEMENT flushOnExecute EMPTY>
<!ATTLIST flushOnExecute
statement CDATA #REQUIRED
>
<!ELEMENT property EMPTY>
<!ATTLIST property
name CDATA #REQUIRED
value CDATA #REQUIRED
>
於是,通過查看ibatis的dtd聲明,我們即可得知在ibatis中是如何配置緩存管理的,將以上信息連接起來即可得到如下一段配置:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="User">
<cacheModel id="user-cache" type ="LRU" readOnly="true" serialize="false">
<flushInterval hours="24"/>
<flushOnExecute statement=” updateUser”/>
<flushOnExecute statement="insertUser"/>
<flushOnExecute statement="deleteUser"/>
<property value="500" name="size"/>
</cacheModel>
<!—其他配置信息 -->
。。。。。。。。。。
</sqlMap>
那麼這些配置都是什麼含義呢?繼續:
id:一個標識,在下面的select語句中將引用該標識。
Type: cacheModel的實現類型,目前有如下4種實現:
(1). "MEMORY” (com.ibatis.sqlmap.engine.cache.memory.MemoryCacheController) ,MEMORY cache 實現使用java的軟引用類型來管理cache 的行爲,使用一個HashMap來保存當前需要緩存的數據對象的引用,當內存不足時,java虛擬機將回收這些引用,從而清除cache。
(2).“LRU” (com.ibatis.sqlmap.engine.cache.lru.LruCacheController) ,LRU Cache 實現用“近期最少使用”原則來確定如何從Cache中清除對象,當Cache溢出時,最近最少使用的對象將被從Cache中清除。
(3).“FIFO” (com.ibatis.sqlmap.engine.cache.fifo.FifoCacheController) ,FIFO Cache 實現用“先進先出”原則來確定如何從 Cache 中清除對象。即最先進入 Cache 的對象將從 Cache 中清除。
(4).“OSCACHE” (com.ibatis.sqlmap.engine.cache.oscache.OSCacheController) ,OSCACHE Cache 實現是OSCache2.0緩存引擎的一個Plugin,它具有高度的可配置性,分佈式,高度的靈活性(很推薦使用該類型)OSCache可以通過oscache.properties文件進行緩存的相關配置。
readOnly:readOnly的值表示緩存中的數據對象是否只讀。若爲true,則當數據對象發生變化時,數據對象就將被從緩存中廢除,下次需要重新從數據庫讀取數據,構造新的數據對象。而若爲false,則意味着緩存中的數據對象可更新,不必從數據庫中讀取。
serialize:如果需要全局的數據緩存,CacheModel的serialize屬性必須被設爲true。否則數據緩存只對當前Session有效,局部緩存對系統的整體性能提升有限。在serialize="true"的情況下,如果有多個Session同時從Cache 中讀取某個數據對象,Cache將爲每個Session返回一個對象的複本,也就是說,每個Session將得到包含相同信息的不同對象實例。因而Session可以對其從Cache獲得的數據進行存取而無需擔心多線程併發情況下的同步衝突。
<flushInterval hours=”24”>:指定多長時間清除緩存,例如指定每24小時強行清空緩存區的所有內容。
<flushOnExecute statement="insertUser"/>:在執行指定的語句時將刷新數據庫。
Size:指定Cache的最大容量。
通過在一個SQL Map XML file配置以上信息,我們就可以在一個查詢中使用該緩存管理,配置如下:
<select resultMap="UserResult" cacheModel=”user-cache”>
select * from USER
</select>
2.sql-map-config的配置,查看配置文件的dtd聲明:
<!ELEMENT sqlMapConfig (properties?, settings?, resultObjectFactory?, typeAlias*, typeHandler*, transactionManager?, sqlMap+)+>
在這個dtd的聲明中,有一個settings元素,通過繼續查看該元素的屬性
<!ELEMENT settings EMPTY>
<!ATTLIST settings
classInfoCacheEnabled (true | false) #IMPLIED
lazyLoadingEnabled (true | false) #IMPLIED
statementCachingEnabled (true | false) #IMPLIED
cacheModelsEnabled (true | false) #IMPLIED
enhancementEnabled (true | false) #IMPLIED
errorTracingEnabled (true | false) #IMPLIED
useStatementNamespaces (true | false) #IMPLIED
useColumnLabel (true | false) #IMPLIED
forceMultipleResultSetSupport (true | false) #IMPLIED
maxSessions CDATA #IMPLIED
maxTransactions CDATA #IMPLIED
maxRequests CDATA #IMPLIED
defaultStatementTimeout CDATA #IMPLIED
>
顯然,屬性cacheModelsEnabled就表示是否啓用SqlMapClient上的緩存機制。將其設爲"true"則標識啓用緩存機制,反之則不啓用。
通過上面對兩個配置文件的分析,我們已經對在ibatis中如何使用緩存有了大致的瞭解和認識,下面我們將通過一個實例來演示ibatis緩存的使用並檢驗ibatis的緩存是否已經起作用。
我們的測試方法如下:
1.將數據庫中預先插入一些數據(通過控制檯或數據庫客戶端插入數據),然後利用編寫好的程序訪問數據庫,第一次訪問數據庫時將查詢出所有的數據,但當我們通過控制檯或數據庫客戶端(如mysql客戶端)再向數據庫中插入一些數據,這時再通過編寫好的程序去訪問數據庫,發現通過控制檯或數據庫客戶端新插入的數據並沒有被查詢出來,說明ibatis讀取的是第一次查詢所保存在緩存中的數據,這說明測試成功!
2.當我們使用程序而不是控制檯或客戶端再向數據庫插入數據,同時又通過程序訪問數據庫時,新插入數據庫的數據都被查詢了出來,而不是從緩存中讀取,這說明測試成功,因爲我們配置了<flushOnExecute statement="insertUser" />當插入數據時將刷新數據庫。
下面就開始我們的測試:
1.建立一個web工程,導入相關的jar包;
2.編寫具體代碼;
3.做緩存測試;
sql-map文件的配置:User.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMap
PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-2.dtd">
<sqlMap namespace="User">
<typeAlias alias="User" type="com.javaeye.hnylj.model.User" />
<!-- 配置緩存模型 -->
<cacheModel id="user-cache" type="OSCache" readOnly="true"
serialize="true">
<flushInterval hours="24" />
<flushOnExecute statement="insertUser" />
<property value="500" name="size" />
</cacheModel>
<resultMap >
<result property="id" column="ID" />
<result property="name" column="NAME" />
<result property="age" column="AGE" />
</resultMap>
<select resultMap="UserResult"
cacheModel="user-cache">
SELECT * FROM USER
</select>
<insert parameterClass="User">
INSERT INTO USER ( NAME, AGE, PASSWORD) VALUES ( #name#, #age#, #password# )
</insert>
</sqlMap>
sql-map-config文件的配置: SqlMapConfig.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE sqlMapConfig
PUBLIC "-//ibatis.apache.org//DTD SQL Map Config 2.0//EN"
"http://ibatis.apache.org/dtd/sql-map-config-2.dtd">
<sqlMapConfig>
<settings lazyLoadingEnabled="true" cacheModelsEnabled="true"
maxSessions="30" maxTransactions="100" maxRequests="1000"
defaultStatementTimeout="15" />
<transactionManager type="JDBC" commitRequired="false">
<dataSource type="SIMPLE">
<property name="JDBC.Driver" value="com.mysql.jdbc.Driver" />
<property name="JDBC.ConnectionURL"
value="jdbc:mysql://127.0.0.1:3306/ibatis" />
<property name="JDBC.Username" value="root" />
<property name="JDBC.Password" value="123" />
</dataSource>
</transactionManager>
<sqlMap resource="com/javaeye/hnylj/model/User.xml" />
</sqlMapConfig>
接下來就是編寫DAO,Action以及jsp頁面,在這裏,Action層使用了struts2。
Dao代碼:UserDAO
package com.javaeye.hnylj.dao;
import java.io.IOException;
import java.io.Reader;
import java.sql.SQLException;
import java.util.List;
import com.ibatis.common.resources.Resources;
import com.ibatis.sqlmap.client.SqlMapClient;
import com.ibatis.sqlmap.client.SqlMapClientBuilder;
import com.javaeye.hnylj.model.User;
public class UserDAO {
private static SqlMapClient sqlMapper;
static {
try {
Reader reader = Resources.getResourceAsReader("com/javaeye/hnylj/model/SqlMapConfig.xml");
sqlMapper = SqlMapClientBuilder.buildSqlMapClient(reader);
reader.close();
} catch (IOException e) {
throw new RuntimeException("building the SqlMapClient instance error."+ e, e);
}
}
public List<?> getAllUser() throws SQLException {
List<?> list = sqlMapper.queryForList("selectAllUser");
System.out.println(list.size());
return list;
}
public void insertUser(User user) throws SQLException {
sqlMapper.insert("insertUser", user);
}
}
Action代碼:UserAction
package com.javaeye.hnylj.action;
import java.util.List;
import com.javaeye.hnylj.dao.UserDAO;
import com.javaeye.hnylj.model.User;
import com.opensymphony.xwork2.ActionSupport;
public class UserAction extends ActionSupport {
private static final long serialVersionUID = 4689260572038875931L;
private UserDAO userDAO;
private List<User> userList;
private Integer id;
private String name;
private Integer age;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public List<User> getUserList() {
return userList;
}
public void setUserList(List<User> userList) {
this.userList = userList;
}
public String findAllUsers() throws Exception {
userDAO = new UserDAO();
userList = (List<User>)userDAO.getAllUser();
if (userList.size() > 0) {
return SUCCESS;
}
return ERROR;
}
public String add() throws Exception {
userDAO = new UserDAO();
User user = new User();
user.setName(name);
user.setAge(age);
user.setPassword(password);
userDAO.insertUser(user);
return SUCCESS;
}
}
還有其他一些代碼或配置,在此省略!
將工程部署成功以後,按照上面的測試方法即可進行測試!