日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

struts+spring +ibatis

 孫中熙——路 2012-02-16
不同F(xiàn)ramework中DAO層的分析

1 言前

兩、三年前說過要堅(jiān)持寫文剖析各種算法、面向?qū)ο?、框架、架?gòu)及各種應(yīng)用技術(shù),然進(jìn)展緩慢,一者力有不逮,二者工作變動(dòng),無暇它顧。如今咬牙還債,算給自己個(gè)交代。這次就從最常用的DAO開始吧,

J2EE應(yīng)用開發(fā)中,數(shù)據(jù)庫的使用非常頻繁,大多公司里所使用的Framework都有對數(shù)據(jù)庫操作進(jìn)行封裝的被稱為DAO層的部分。下面,我結(jié)合自己在不同時(shí)期所參與的項(xiàng)目,就各種DAO層的封裝方式進(jìn)行一下比照。

2 項(xiàng)目1 (久遠(yuǎn),尚無SSH流行)

2.1 使用方式

  1. 用戶DAO繼承BaseDAO
  2. 在業(yè)務(wù)模塊初始化用戶DAO,并傳遞給如下3個(gè)參數(shù)
    - String dataSourceName 數(shù)據(jù)源名稱(JNDI).
    - boolean autoReleaseConnection 是否自動(dòng)釋放鏈接.
    - boolean useStoreProcedure 是否使用存儲(chǔ)過程.
  3. 在業(yè)務(wù)模塊根據(jù)需要調(diào)用用戶DAO對象的相應(yīng)方法.
  4. 例子: 業(yè)務(wù)邏輯中調(diào)用:
    UserDAO userDAO = new UserDAO(null,false,false);
    List allUser = userDAO.getAllUser();

2.2 原理

  • 主要實(shí)現(xiàn)了對SQL執(zhí)行過程的封裝。以一個(gè)查詢SQL為例,它的書寫只需要這么一句:
    DataSet ds = execQuery(querySQL);

    在execQuery(String sql)中,做了3件事:

    1. 獲取連接
    2. 使用獲取的連接執(zhí)行SQL
    3. 把從數(shù)據(jù)庫中取得的數(shù)據(jù)封裝進(jìn)DateSet對象中,返回給用戶。程序員在用戶DAO對象里面進(jìn)行組裝SQL的工作,然后調(diào)用父類提供的執(zhí)行 execQuery方法,返回封裝的DataSet對象,該對象以List方式存放著所有查詢結(jié)果。

2.3 UML


2.4 優(yōu)點(diǎn)

省卻了婆婆媽媽的RecordSet, Statement的聲明和繁瑣重復(fù)的異常捕獲。把查詢結(jié)果放進(jìn)緩存,不用一直維持著數(shù)據(jù)庫的連接,可以減少數(shù)據(jù)庫的數(shù)據(jù)連接壓力。

2.5 缺點(diǎn)

  1. 把DAO的模式控制放進(jìn)業(yè)務(wù)模塊里不合適。
  2. 只支持單一數(shù)據(jù)庫。
  3. 數(shù)據(jù)量太大的查詢,會(huì)導(dǎo)致內(nèi)存不足。

3 項(xiàng)目2

3.1 使用方式(步驟)

  1. 業(yè)務(wù)邏輯中聲明用戶自定義的DAO,它必須繼承自framework中的 BaseDAO 比如
    IndexDAO dao = new IndexDAO();
  2. 在業(yè)務(wù)邏輯中把請求數(shù)據(jù)封裝為DataSet 比如:
    DataSet dataset=this.getDataSet(request);
  3. 業(yè)務(wù)邏輯中使用DAO處理DataSet 比如
    dao.addData(dataset,user);
  4. 在用戶自定義DAO的addData方法中繼續(xù)對DataSet對象進(jìn)行補(bǔ)充,比如:
    ds.set("userstatus", "0");
    ds.tablename = "T_Pop_User";
  5. 設(shè)置完DataSet后,最關(guān)鍵的一步來了,就是DBOperator直接對DataSet 進(jìn)行操作,比如:
    dboperator.insertData(ds);

3.2 原理

  1. 對客戶端(瀏覽器)數(shù)據(jù)進(jìn)行自動(dòng)封裝
  2. 在服務(wù)端對封裝的數(shù)據(jù)進(jìn)行自動(dòng)解析,解析成SQL,并執(zhí)行.
  3. 實(shí)際上,這相當(dāng)于建立了從客戶端(一般而言是Form表單)到數(shù)據(jù)庫表間的映射.

3.3 UML

3.4 優(yōu)點(diǎn):

  1. 使用過程中不但不需要寫繁復(fù)的連接釋放等代碼,而且連SQL語句都不用寫了,這使得整個(gè)代碼顯得清晰簡潔。根據(jù)用戶的DataSet對象動(dòng)態(tài)、智能的生成SQL的工作都被隱藏到了框架內(nèi)部.
  2. 把用戶請求被自動(dòng)封裝為DataSet,免去了用戶使用request對象方法一個(gè)一個(gè)的去接收數(shù)據(jù)。DAO層自動(dòng)根據(jù)DataSet生成SQL去執(zhí)行。在這個(gè)過程中,程序員做的工作被極大的簡化。
  3. 支持多庫。

3.5 缺點(diǎn)

  1. 被封裝的DataSet里需要包含數(shù)據(jù)庫字段信息,才能被程序自動(dòng)解析為 SQL,這要求前端Form中的Field名稱必須和數(shù)據(jù)庫字段名稱一致(嚴(yán)格的說,要求傳遞給Server端的數(shù)據(jù)的Field字段必須與數(shù)據(jù)庫字段名稱一致)。這或多或少限制了前端開發(fā)的靈活性。但考慮到整個(gè)過程的便利性,這點(diǎn)限制并非不值。
  2. 前端Form直接暴露數(shù)據(jù)庫字段信息不安全。對于信息安全要求高的系統(tǒng)這樣的做法會(huì)直接Pass掉。

4 項(xiàng)目3(大約04年,hibernate)

4.1 使用方式

基本上式是直接使用hibernate進(jìn)行DAO層操作,未對hibernate進(jìn)行二次封裝;

  1. 在Action中使用如下方式得到User對象,
    User user = new User();
    user.setID(23);
    user.setName("Dapple");
    UserManager.updateUser(user);
  2. 在UserManager中,通過SessionManager直接使用session進(jìn)行數(shù)據(jù)對象存取;
    Session s = SessionManager.getSession();
    s.update(buyer);

4.2 原理( session 的獲取方式 )

  1. 在SessionManager中,session的獲取方式如下,
    SessionFactory  sessionFactory = new Configuration().configure().buildSessionFactory();
    Session s = sessionFactory.openSession();

這段代碼會(huì)讀取hibernate配置文件(hibernate.cfg.xml ), hibernate配置文件中有數(shù)據(jù)庫連接信息,對象與數(shù)據(jù)庫之間的映射文件的地址;

  1. Hibernate的原理請參照hibernate相關(guān)文檔;

4.3 UML

4.4 優(yōu)缺點(diǎn)

其實(shí)就是hibernate的優(yōu)缺點(diǎn)。這個(gè)大家可以參考已經(jīng)非常豐富的hibernate 資料;從審美角度上看,hibernate使數(shù)據(jù)存取代碼簡潔、清晰。而簡潔清晰的代碼總是更容易使用。

4.5 兩個(gè)對hibernate使用的例子

在下一個(gè)緊接著的項(xiàng)目中,有的模塊對hibernate又進(jìn)行了一層簡單的封裝,這樣做的目的是為了使之?dāng)U展性更強(qiáng)。但實(shí)際上這也同時(shí)使得代碼復(fù)雜度增加。這中間的平衡取舍往往不單單是技術(shù)問題。

下面我們這里舉兩種封裝例子。

  1. 先看UML

    從這個(gè)UML中,我們可以看出它的思路,就是塵歸塵、土歸土,就是user的業(yè)務(wù)處理歸UserManager,user的數(shù)據(jù)處理歸UserManagerDAO.兩者互不干擾。假如現(xiàn)在需求有變,數(shù)據(jù)被要求存在文件里而非數(shù)據(jù)庫中,要做哪些工作呢?

    1. 新寫一個(gè)實(shí)現(xiàn)了UserManagerDAO接口的UserManagerDAOFileImpl實(shí)體類。
    2. DAOFactory中需要增加一個(gè)生成負(fù)責(zé)文件存儲(chǔ)的實(shí)現(xiàn)類的方法,我們暫叫它為方法B;(原先負(fù)責(zé)生成數(shù)據(jù)庫存儲(chǔ)實(shí)現(xiàn)類的方法叫做方法A;)
    3. UserManager中需要把原來DAOFactory中調(diào)用方法A的代碼修改為調(diào)用方法B;

      可以看出,已有代碼中修改了2處。這正是簡單工廠模式的特點(diǎn)。

  2. 先看UML

    它減少了Manager這一層,而讓用戶直接使用DAO.它使用的模式是工廠模式。假如需求也有變,要求把數(shù)據(jù)存進(jìn)文件里而非數(shù)據(jù)庫中,要做哪些工作呢?

    1. 新寫兩個(gè)類,即繼承了DAOFactory抽象類的DAOFileFactory和實(shí)現(xiàn)了 UserDAO接口的UserDAOFileImpl實(shí)體類。
    2. 在實(shí)例化DAOHBFactory的地方(這里實(shí)際上是DAOFactory抽象類中有一個(gè)實(shí)現(xiàn)具體的實(shí)現(xiàn)方法)做出修改。該為實(shí)例化DAOFileFactory。

      我們可以看出,這里對已有代碼修改了一處。大家都知道OCP原則,即對擴(kuò)展開放,對修改關(guān)閉。其本質(zhì)就是對于已經(jīng)存在的代碼盡量不去動(dòng)就能實(shí)現(xiàn)新的功能(擴(kuò)展),其完全實(shí)現(xiàn)必然伴隨形成熱插拔的能力。

      這里,仍然對已有代碼做了一處修改,必然還需要重新編譯。如果把實(shí)例化的控制開關(guān)放進(jìn)配置文件中又配置人員來設(shè)定,就可以不需要再修改代碼再編譯,則基本可以看做可熱插拔了。

5 項(xiàng)目4 (struts+spring+ibatis)

補(bǔ)充一點(diǎn),所有項(xiàng)目的業(yè)務(wù)含義就不說了。因?yàn)槟菢幼霾缓谩?/P>

5.1 架構(gòu)說明

架構(gòu)采用 struts+spring+ibatis 組合。

請求被Action接收后, Action–>Logic–>Dao–>Ibatis. 符號(hào)"–>"代表調(diào)用. 關(guān)鍵的地方在于,所有的調(diào)用都是通過Spring的依賴注入實(shí)現(xiàn),而不用程序員去寫被調(diào)用對象的創(chuàng)建代碼(比如寫一個(gè)工廠類用來創(chuàng)建對象)。省了好多事哦。

Struts是最著名的MVC Framework,它的實(shí)現(xiàn)實(shí)質(zhì)就是用一個(gè)Servlet統(tǒng)一接收請求,然后根據(jù)配置文件分發(fā)給不同的action,程序員在action中再調(diào)用自己的邏輯模塊,處理完后,struts又根據(jù)配置文件把結(jié)果轉(zhuǎn)發(fā)到相應(yīng)的頁面。這就完成了MVC的一個(gè)全過程。它是這么著名,以至于到處都是它的資料。所以欲知其詳,請google.

Ibatis是半自動(dòng)化數(shù)據(jù)封裝方案。它的實(shí)現(xiàn)實(shí)質(zhì)就是通過讀取寫在配置文件中的SQL語句自動(dòng)生成數(shù)據(jù)對象.

Spring則發(fā)展的最火,因?yàn)樗拇_最棒。但要說它的實(shí)現(xiàn)實(shí)質(zhì),則也是通過讀取含有class信息的配置文件,動(dòng)態(tài)的生成類對象。多個(gè)class的依賴關(guān)系也寫在配置文件中,Spring讀取依賴關(guān)系、進(jìn)行對象的構(gòu)造,這就是依賴注入。

捎帶提一句,要感謝夏昕為Spring的普及所做的貢獻(xiàn)。正是2004年夏昕的 Spring Guide加速了Spring在眾多的程序員中的普及進(jìn)程。

配置文件的例子如下:

<bean id="user" class="test.User">          
<property name="name"><value>張三</value></property>
<property name="age"><value>20</value></property>
</bean>
<bean id="department" class="test.Department">
<property name="user"><ref bean="user"/></property>
</bean>

這個(gè)配置文件中,Department類引用了User類(Spring會(huì)自動(dòng)根據(jù)引用構(gòu)建依賴關(guān)系,也可以手工指定依賴關(guān)系)。Spring讀這些文件,然后通過反射就可以構(gòu)造出user對象與Department對象。然后再在此基礎(chǔ)上發(fā)展演化,變成了今天的Spring. 它的基本原理簡單,但為什么這樣的framework總是被老外開發(fā)出來呢?不是中國程序員笨,可能要從歷史和大環(huán)境找原因吧。

不說閑話,回歸正題。這個(gè)項(xiàng)目采用了struts+spring+ibatis做架構(gòu),我們就來看一下它的使用方式。

5.2 使用方式及其原理

這里用一個(gè)簡單過程來說明。比如用戶輸入用戶名和密碼進(jìn)行Logon,系統(tǒng)校驗(yàn)用戶名密碼是不是正確。(這是個(gè)演示例子,所以后面的代碼一切從簡)。

用戶點(diǎn)擊登陸,請求被struts送到了Logonaction,如下:

protected ActionForward doExecute(ActionMapping mapping, ActionForm form,
HttpServletRequest request,
HttpServletResponse response) throws Exception{
String name = request.getParameter("name");
String password = request.getParameter("password");
ServletContext sc = request.getSession().getServletContext();
ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(ServletContext sc);
UserHandle userHandle = (UserHandle)ctx.getBean("userhandle"); //獲取userhanle實(shí)例
boolean isSuccess = userHandle.check(name,password);
if(isSuccess){
return mapping.findForward("success");
}else{
return mapping.findForward("failed");
}
}

userHandle實(shí)例是通過容器(ApplicationContext)獲取的,不需要自己寫創(chuàng)建對象的代碼了。容器實(shí)際上是通過讀取配置文件加載UserHandle類的,而這個(gè)過程對于程序員是透明的。

然后我們再分析下面這一句:

boolean isSuccess = userHandle.check(name,password);

userHanle類的通常實(shí)現(xiàn)方法可能是這樣,

class UserHandle{
public boolean check(String name,String password){
UserDAO userDao = DBOperator.getUserDAO(); //你自己的DB操作的封裝類
return userDao.check(name,password);
}
}

然后就可以想象UserDAO里就是數(shù)據(jù)庫連接,發(fā)送SQL的那一套。這樣做,程序員就不得不寫一個(gè) DBOperator類,用來創(chuàng)建DAO. 而Spring就是用來解放這樣的創(chuàng)建工作的,這樣 DBOperator這樣的類就沒必要程序員勞神去寫了,只需要這樣做:

class UserHandle{
private UserDAO userDao;
public boolean check(String name,String password){
return userDao.check(name,password);
}
public void setUserDao(UserDAO userDao){
this.userDao = userDao;
}
public UserDAO getUserDao(){
return this.userDao;
}
}

Spring 會(huì)根據(jù)配置文件中的類的依賴關(guān)系,自動(dòng)的把UserDAO對象注入到 UserHandle對象里,然后程序員去使用userDao就可以了。

既然是根據(jù)配置文件來注入的,那么配置文件是什么樣子呢?如下:

<beans>
<!--4-->
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>efmweb</value></property>
</bean>
<!--3-->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean" >
<property name="configLocation"><value>WEB-INF/sql-map-config.xml</value></property>
<property name="dataSource"><ref local="dataSource"/></property>
</bean>
<!--2-->
<bean id="userdao" class="UserDAO">
<property name="sqlMapClient"><ref bean="sqlMapClient"/></property>
</bean>
<!--1-->
<bean id="userhandle" class="UserHandle">
<property name="userDao"><ref bean="userdao" /></property>
</bean>
</beans>

從這個(gè)配置文件里我們可以看到它們的引用關(guān)系是:1–>2–>3–>4,即 UserHandle引用UserDAO, UserDAO引用 SqlMapClientFactoryBean, SqlMapClientFactoryBean引用 JndiObjectFactoryBean,JndiObjectFactoryBean負(fù)責(zé)根據(jù)jndiName獲得 datasource.

通過這樣層層引用的配置,spring就能自動(dòng)生成對象,不但免了程序員手動(dòng)寫對象生成的代碼,還很容易的把ibatis串連起來。

前面我們已經(jīng)根據(jù)代碼和配置文件知道了UserHandle是怎么引用和注入 UserDAO的方式,現(xiàn)在我們再看UserDAO是怎么引用 SqlMapClientFactoryBean 這個(gè)第三方類的.

5.2.1 UserDAO的實(shí)現(xiàn)以及如何使用第三方SqlMapClient.

根據(jù)UserHandle引用UserDAO的方式,我們也應(yīng)該猜到UserDAO引用 SqlMapClientFactoryBean時(shí)應(yīng)該大致是這樣:

class UserDAO{
private SqlMapClientFactoryBean sqlMapClient;
public void setSqlMapClient(SqlMapClientFactoryBean sqlMapClient){
this.sqlMapClient = sqlMapClient;
}
public SqlMapClientFactoryBean getSqlMapClient(){
return this.sqlMapClient;
}
...;//你自己定義的其它業(yè)務(wù)相關(guān)userDao邏輯;
}

但實(shí)際上,第三方類已經(jīng)把set get這樣的方法封裝好了,你只需要繼承第三方類就可以了,如這樣:

class UserDAO extends SqlMapClientDaoSupport{
...;//你自己定義的其它業(yè)務(wù)相關(guān)userDao邏輯;
}

這樣就不用手工寫那些set get方法了。原理也不難理解,那些set 和 get方法無非被挪進(jìn)了SqlMapClientDaoSupport這個(gè)Spring提供的第三方類里面而已,我們可以看它的源碼片段:

public abstract class SqlMapClientDaoSupport extends DaoSupport {

private SqlMapClientTemplate sqlMapClientTemplate = new SqlMapClientTemplate();
private boolean externalTemplate = false;

/**
* Set the JDBC DataSource to be used by this DAO.
* Not required: The SqlMapClient might carry a shared DataSource.
* @see #setSqlMapClient
*/
public final void setDataSource(DataSource dataSource) {
if (!this.externalTemplate) {
this.sqlMapClientTemplate.setDataSource(dataSource);
}
}

/**
* Return the JDBC DataSource used by this DAO.
*/
public final DataSource getDataSource() {
return this.sqlMapClientTemplate.getDataSource();
}

/**
* Set the iBATIS Database Layer SqlMapClient to work with.
* Either this or a "sqlMapClientTemplate" is required.
* @see #setSqlMapClientTemplate
*/
public final void setSqlMapClient(SqlMapClient sqlMapClient) {
if (!this.externalTemplate) {
this.sqlMapClientTemplate.setSqlMapClient(sqlMapClient);
}
}

/**
* Return the iBATIS Database Layer SqlMapClient that this template works with.
*/
public final SqlMapClient getSqlMapClient() {
return this.sqlMapClientTemplate.getSqlMapClient();
}

/**
* Return the SqlMapClientTemplate for this DAO,
* pre-initialized with the SqlMapClient or set explicitly.
*/
public final SqlMapClientTemplate getSqlMapClientTemplate() {
return this.sqlMapClientTemplate;
}

}

5.2.2 為什么SqlMapClientFactoryBean與SqlMapClient類型不同也可被注入?

果然看到了getSqlMapClient()和setSqlMapClient()方法。我們?nèi)匀桓鶕?jù) UserHandle和UserDAO的注入方式,可以知道UserHandle的屬性u(píng)serDao其實(shí)就是UserDAO類的實(shí)例注入。同樣,配置文件中UserDAO的屬性sqlMapClient 也應(yīng)該是SqlMapClientFactoryBean類的實(shí)例注入才對。但 SqlMapClientFactoryBean并不是SqlMapClient類型,這怎么能注入呢?

這是因?yàn)镾pring的機(jī)制的緣故。簡單的說,如果一個(gè)bean實(shí)現(xiàn)了 FactoryBean接口,那么Spring就不會(huì)把該bean本身實(shí)例化并返回,而是返回該bean的getObject()返回的對象。這是Sprign的游戲規(guī)則。我們來看一眼 SqlMapClientFactoryBean的源碼片段:

public class SqlMapClientFactoryBean implements  FactoryBean,InitializingBean {
private SqlMapClient sqlMapClient;
protected SqlMapClient buildSqlMapClient(Resource configLocation,Properties properties) throws IOException {
InputStream is = configLocation.getInputStream();
if (properties != null) {
if (buildSqlMapClientWithInputStreamAndPropertiesMethodAvailable) {
return SqlMapClientBuilder.buildSqlMapClient(is,properties);
} else {
return SqlMapClientBuilder.buildSqlMapClient(new InputStreamReader(is), properties);
}
} else {
if (buildSqlMapClientWithInputStreamMethodAvailable) {
return SqlMapClientBuilder.buildSqlMapClient(is);
} else {
return SqlMapClientBuilder.buildSqlMapClient(new InputStreamReader(is));
}
}
}
//這里就是返回的、并會(huì)被注入到其它類里的對象
public Object getObject() {
return this.sqlMapClient;
}
}

Spring這種機(jī)制的官方說明:

public interface FactoryBean

Interface to be implemented by objects used within a BeanFactory which are themselves factories. If a bean implements
this interface, it is used as a factory for an object to expose, not directly as a bean instance that will be exposed
itself.

NB: A bean that implements this interface cannot be used as a normal bean. A FactoryBean is defined in a bean style, but
the object exposed for bean references (getObject() is always the object that it creates.

FactoryBeans can support singletons and prototypes, and can either create objects lazily on demand or eagerly on
startup. The SmartFactoryBean interface allows for exposing more fine-grained behavioral metadata.

This interface is heavily used within the framework itself, for example for the AOP ProxyFactoryBean or the
JndiObjectFactoryBean. It can be used for application components as well; however, this is not common outside of
infrastructure code.

NOTE: FactoryBean objects participate in the containing BeanFactory's synchronization of bean creation. There is usually
no need for internal synchronization other than for purposes of lazy initialization within the FactoryBean itself (or
the like).

Since:
08.03.2003
Author:
Rod Johnson, Juergen Hoeller
See Also:
BeanFactory, ProxyFactoryBean, JndiObjectFactoryBean

到此為止,我們已經(jīng)知道UserDAO與SqlMapClient的關(guān)系,接下來看如何使用IBatis

5.2.3 如何使用IBatis

Ibatis是半自動(dòng)化數(shù)據(jù)對象封裝方案。我們看userDao怎么使用Ibatis

class UserDAO{
private SqlMapClientFactoryBean sqlMapClient;
public void setSqlMapClient(SqlMapClientFactoryBean sqlMapClient){
this.sqlMapClient = sqlMapClient;
}
public SqlMapClientFactoryBean getSqlMapClient(){
return this.sqlMapClient;
}
//這里是使用Ibatis的地方
public User getUser(String userid) {
return super.getSqlMapClientTemplate().queryForList("userid",
userid);
}
}

結(jié)合者段代碼以及前面的配置文件的第三段,相信你已經(jīng)大體明白了Ibatis的使用。這里就做一簡單描述。先重新看一下配置文件的第三段:

<!--3-->
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean" >
<property name="configLocation"><value>WEB-INF/sql-map-config.xml</value></property>
<property name="dataSource"><ref local="dataSource"/></property>
</bean>

注意這里有一個(gè)sql-map-config.xml文件。這個(gè)文件的內(nèi)容如下:

<sqlMapConfig>
<sqlMap resource="user.xml"/>
</sqlMapConfig>

再看user.xml文件里是什么?

<sqlMap>
<select id="userid" resultclass="UserDAO" parameterClass="java.lang.String">
select * from user where userid=#userid#
</select>
</sqlMap>

現(xiàn)在,結(jié)合UserDAO類,和三個(gè)xml文件,應(yīng)該容易知道它的原理: 邏輯層調(diào)用userDAO,userDAO通過Spring的粘合會(huì)去調(diào)用 Ibatis,Ibatis讀取sql-map-config.xml文件,然后順藤摸瓜,再讀取user.xml文件,從中找到id="userid"的map,傳遞userid 給#userid#,得到完整的sql,執(zhí)行sql,返回結(jié)果。詳細(xì)的Ibatis的使用請參考相關(guān)手冊。

6 Spring DAO

Spring DAO是Spring對JDBC的封裝。主要利用Template模式實(shí)現(xiàn)。先看看它的用法:

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.update("Update user set name='Jeff' where id=10");

Template模式配合回調(diào)的使用是JdbcTemplate的主要實(shí)現(xiàn)方式,下面是它的源碼片段:

public int update(final String sql) throws DataAccessException {
Assert.notNull(sql, "SQL must not be null");
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL update [" + sql + "]");
}

class UpdateStatementCallback implements StatementCallback,
SqlProvider {
public Object doInStatement(Statement stmt) //<------- 這個(gè)函數(shù)將被下面的方法回調(diào)
throws SQLException {
int rows = stmt.executeUpdate(sql);
if (logger.isDebugEnabled()) {
logger.debug("SQL update affected " + rows
+ " rows");
}
return new Integer(rows);
}

public String getSql() {
return sql;
}
}
return ((Integer) execute(new UpdateStatementCallback()))
.intValue();
}

public Object execute(StatementCallback action) throws DataAccessException {
Assert.notNull(action, "Callback object must not be null");

Connection con = DataSourceUtils.getConnection(getDataSource());
Statement stmt = null;
try {
Connection conToUse = con;
if (this .nativeJdbcExtractor != null
&& this .nativeJdbcExtractor
.isNativeConnectionNecessaryForNativeStatements()) {
conToUse = this .nativeJdbcExtractor
.getNativeConnection(con);
}
stmt = conToUse.createStatement();
applyStatementSettings(stmt);
Statement stmtToUse = stmt;
if (this .nativeJdbcExtractor != null) {
stmtToUse = this .nativeJdbcExtractor
.getNativeStatement(stmt);
}
Object result = action.doInStatement(stmtToUse); //<------- 這里是回調(diào)的地方
handleWarnings(stmt.getWarnings());
return result;
} catch (SQLException ex) {
// Release Connection early, to avoid potential connection pool deadlock
// in the case when the exception translator hasn't been initialized yet.
JdbcUtils.closeStatement(stmt);
stmt = null;
DataSourceUtils.releaseConnection(con, getDataSource());
con = null;
throw getExceptionTranslator().translate(
"StatementCallback", getSql(action), ex);
} finally {
JdbcUtils.closeStatement(stmt);
DataSourceUtils.releaseConnection(con, getDataSource());
}
}

不用回調(diào),如果用順調(diào),會(huì)是怎樣的? 以下是模擬代碼:

public int update(final String sql) throws DataAccessException {
return execute(sql);
}

public int execute(String sql) throws DataAccessException {
Connection con = DataSourceUtils.getConnection();
Statement stmt = null;
stmt = con.createStatement();
int rows = stmt.executeUpdate(sql);
return rows;
//數(shù)據(jù)庫連接關(guān)閉等代碼略
}

這樣做簡單,但兩個(gè)函數(shù)耦合強(qiáng),execute方法的返回類型與參數(shù)都規(guī)定的太死,限制了execute方法的使用范圍。比如,如下的代碼就不容易去使用execute方法:

public User getUser(int id){
//無法使用int execute(String sql) 函數(shù),因?yàn)樗环祷匾粋€(gè)int,不是想要的。
}

但如果是接口回調(diào)方式,則靈活得多

public User getUser(int id) throws DataAccessException {
class UpdateStatementCallback implements StatementCallback {
public Object doInStatement(Statement stmt) //<------- 這個(gè)函數(shù)將被下面的方法回調(diào)
throws SQLException {
String sql = "select * from User where id ="+id;
ResultSet rs = stmt.executeUpdate(sql);
//下面略的部分是把rs取回的數(shù)據(jù)封裝進(jìn)User對象里。
//...略;
User user = getUser(rs);
return user;
}
}
return execute(new UpdateStatementCallback());
}

可以看出,下面的這個(gè)方法可滿足不同的需要

public Object execute(StatementCallback action) throws DataAccessException

而下面這種方法相對死板(但也簡單了不少)

public int execute(String sql) throws DataAccessException

模板模式請參考:http://hi.baidu.com/dapplehou/blog/item/0830c3ce9ccd4a0f93457e6d.html

相信很容易明白Spring是如何把那些關(guān)閉連接的瑣事用模板模式封裝起來的。

7 多庫/多數(shù)據(jù)源

無論由于性能原因?qū)?shù)據(jù)庫拆分,還是本身就是分布式數(shù)據(jù)庫,都涉及到對多個(gè)數(shù)據(jù)庫的操作。這要求程序能動(dòng)態(tài)定位所需數(shù)據(jù)庫,實(shí)際上就是在多個(gè)數(shù)據(jù)源中動(dòng)態(tài)定位一個(gè)。

可采用把定位庫的策略封裝進(jìn)一個(gè)中間層來屏蔽程序?qū)τ诙鄮斓拿舾小R话悴捎肞roxy模式封裝這種DB路由(即:定位)邏輯。Spring又顯身手,它對多庫也提供了方案:

public class DynamicDataSource extends AbstractRoutingDataSource {//1. 繼承AbstractRoutingDataSource
static Logger log = Logger.getLogger("DynamicDataSource");

protected Object determineCurrentLookupKey() {
...................; //2. 你的路由邏輯
return dataSourceId; //3. 返回你要使用的datasourceId
}
}

相應(yīng)的配置文件如下:

<bean id="dataSource0" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>db1</value></property>
</bean>
<bean id="dataSource1" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>db1</value></property>
</bean>
<bean id="dataSource2" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName"><value>db1</value></property>
</bean>
<bean id="dataSource" class="xxx.xxx.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.Integer">
<entry key="0" value-ref="dataSource0"/>
<entry key="1" value-ref="dataSource1"/>
<entry key="2" value-ref="dataSource2"/>
</map>
</property>
<property name="defaultTargetDataSource" ref="dataSource0"/>
</bean>
<bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
<property name="configLocation" value="classpath:com/bitfone/smartdm/dao/sqlmap/sql-map-config.xml"/>
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="UserInfoDAO" class="com.bitfone.smartdm.dao.impl.UserInfoDAO">
<property name="sqlMapClient" ref="sqlMapClient"/>
</bean>

當(dāng)然,也可以不集成 AbstractRoutingDataSource . 而是實(shí)現(xiàn)DataSource接口,代碼片段如下:

public class DynamicDataSource implements DataSource{
private static String dsDefault;
private Map dataSources;

public void setDsDefault(String dsDefault)
{
this.dsDefault = dsDefault;
}

public Connection getConnection() throws SQLException
{
DataSource targetDS = getDataSource();
return targetDS.getConnection();
}


public DataSource getDataSource()
{
..........
..........//你的路由邏輯
String dbName = ?;
DataSource ds = (DataSource)dataSources.get(dbName);
return ds;
}

......
}

實(shí)際上,AbstractRoutingDataSource 就是DataSource接口的一個(gè)實(shí)現(xiàn),繼承 AbstractRoutingDataSource,比自己實(shí)現(xiàn)DataSource要少很多工作量,因?yàn)?AbstractRoutingDataSource都幫你做了。AbstractRoutingDataSource內(nèi)部的數(shù)據(jù)庫路由原理和上面的代碼一樣。

8 結(jié)束

時(shí)間有限,先寫到這里吧。關(guān)于DAO還有太多的東西可以寫,還有深的內(nèi)容可以挖掘,這些內(nèi)容完全可以寫本大厚書。這里主要是從一個(gè)Framework的角度來分析了一下大致的用法和實(shí)現(xiàn)思路。

Author: Dapple Hou

Date: 2010-08-28 13:41:09

HTML generated by org-mode 6.33x in emacs 23

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請遵守用戶 評(píng)論公約

    類似文章 更多