Dict.CN 在線詞典, 英語(yǔ)學(xué)習(xí), 在線翻譯

          都市淘沙者

          荔枝FM Everyone can be host

          統(tǒng)計(jì)

          留言簿(23)

          積分與排名

          優(yōu)秀學(xué)習(xí)網(wǎng)站

          友情連接

          閱讀排行榜

          評(píng)論排行榜

          如何簡(jiǎn)化JDBC代碼 (轉(zhuǎn))

          原文:http://blog.csdn.net/kerry365/archive/2005/09/29/491918.aspx
          問(wèn)題的提出

          在一個(gè)應(yīng)用程序中,處理JDBC的操作是一個(gè)重復(fù)率較高的工作。當(dāng)你在一個(gè)JDBC數(shù)據(jù)源上執(zhí)行SQL查詢時(shí),你通常需要執(zhí)行下面幾個(gè)步驟:

          1.生成SQL語(yǔ)句

          2.獲得連接

          3.獲得一個(gè)PreparedStatement對(duì)象

          4.在PreparedStatement對(duì)象中設(shè)定需要送入到數(shù)據(jù)庫(kù)的值

          5.執(zhí)行SQL語(yǔ)句

          6.處理查詢結(jié)果

          除此之外,你還需要處理SQLException異常。如果上面列出的這些步驟分散在程序的各個(gè)部分的話,程序中需要多個(gè)try/catch塊來(lái)處理異常。

          如果我們仔細(xì)看看上面列出的步驟,就會(huì)發(fā)現(xiàn)在執(zhí)行不同的SQL語(yǔ)句時(shí),上面這些步驟中涉及到的程序代碼變化不會(huì)很大:我們使用同樣的方法獲得數(shù)據(jù)庫(kù)連接和PreperedStatement對(duì)象;使用setXXX方法來(lái)設(shè)定PreperedStatement對(duì)象中的值;處理SQL查詢結(jié)果的過(guò)程也是基本不變的。在這篇文章中,我們通過(guò)定義三個(gè)JDBC模型,去除了上述六個(gè)步驟中的三個(gè)步驟,這樣使得整個(gè)過(guò)程更加簡(jiǎn)單,而且具有更好的通用性。

          查詢模型

          我們定義了一個(gè)名叫SQLProcessor的類,在該類中定義了一個(gè)executeQuery()方法來(lái)執(zhí)行SQL語(yǔ)句,我們?cè)趯?shí)現(xiàn)這個(gè)方法的時(shí)候盡量保持代碼的簡(jiǎn)潔性,并且傳遞盡可能少的參數(shù)給該方法。下面是該方法的定義:

          public Object[] executeQuery(String sql, Object[] pStmntValues,                             ResultProcessor processor);



          我們知道在執(zhí)行SQL語(yǔ)句的JDBC過(guò)程中,變化的因素有三個(gè):SQL語(yǔ)句,PreparedStatement對(duì)象和如何解釋和處理查詢結(jié)果。在上面的方法定義中,sql中保存的就是SQL語(yǔ)句;pStmntValues對(duì)象數(shù)組保存的是需要放入preparedStatement對(duì)象中的值;processor參數(shù)是一個(gè)能夠處理查詢結(jié)果的對(duì)象,于是我們把JDBC程序涉及到的對(duì)象分成了三個(gè)部分。下面讓我們來(lái)看一下executeQuery()和與它相關(guān)的一些方法的實(shí)現(xiàn):

          public class SQLProcessor {  public Object[] executeQuery(String sql, Object[] pStmntValues,                               ResultProcessor processor) {    //獲得連接    Connection conn = ConnectionManager.getConnection();    //將SQL語(yǔ)句的執(zhí)行重定向到handlQuery()方法    Object[] results = handleQuery(sql, pStmntValues, processor, conn);    //關(guān)閉連接    closeConn(conn);    //返回結(jié)果    return results;  }  protected Object[] handleQuery(String sql, Object[] pStmntValues,                     ResultProcessor processor, Connection conn) {    //獲得一個(gè)preparedStatement對(duì)象    PreparedStatement stmnt = null;    try {      //獲得preparedStatement      stmnt = conn.prepareStatement(sql);      //向preparedStatement中送入值      if(pStmntValues != null) {        PreparedStatementFactory.buildStatement(stmnt, pStmntValues);      }      //執(zhí)行SQL語(yǔ)句      ResultSet rs = stmnt.executeQuery();      //獲得查詢結(jié)果      Object[] results = processor.process(rs);      //關(guān)閉preparedStatement對(duì)象      closeStmnt(stmnt);      //返回結(jié)果      return results;      //處理異常      } catch(SQLException e) {        String message = "無(wú)法執(zhí)行查詢語(yǔ)句 " + sql;        //關(guān)閉所有資源        closeConn(conn);        closeStmnt(stmnt);        //拋出DatabaseQueryException        throw new DatabaseQueryException(message);      }    }  }...}



          程序中有兩個(gè)方法需要說(shuō)明:PreparedStatementFactory.buildStatement()和processor.process()。buildStatement()方法把在pStmntValues對(duì)象數(shù)組中的所有對(duì)象送到prepareStatement對(duì)象中的相應(yīng)位置。例如:

          ...//取出對(duì)象數(shù)組中的每個(gè)對(duì)象的值,//在preparedStatement對(duì)象中的相應(yīng)位置設(shè)定對(duì)應(yīng)的值for(int i = 0; i < values.length; i++) {  //如果對(duì)象的值為空, 設(shè)定SQL空值  if(value instanceof NullSQLType) {    stmnt.setNull(i + 1, ((NullSQLType) value).getFieldType());  } else {    stmnt.setObject(i + 1, value);  }}

          因?yàn)閟tmnt.setOject(int index, Object value)方法不能接受一個(gè)空對(duì)象作為參數(shù)。為了使程序能夠處理空值,我們使用了自己設(shè)計(jì)的NullSQLType類。當(dāng)一個(gè)NullSQLType對(duì)象被初始化的時(shí)候,將會(huì)保存數(shù)據(jù)庫(kù)表中相應(yīng)列的SQL類型。在上面的例子中我們可以看到,NULLSQLType對(duì)象的屬性中保存了一個(gè)SQL NULL實(shí)際對(duì)應(yīng)的SQL類型。我們用NULLSQLType對(duì)象的getFieldType()方法來(lái)向preparedStatement對(duì)象填入空值。

          下面讓我們來(lái)看一看processor.process()方法。Processor類實(shí)現(xiàn)了ResultProcessor接口,該接口是用來(lái)處理SQL查詢結(jié)果的,它只有一個(gè)方法process(),該方法返回了處理SQL查詢結(jié)果后生成的對(duì)象數(shù)組。

          public interface ResultProcessor {  public Object[] process(ResultSet rs) throws SQLException;}


          process()的典型實(shí)現(xiàn)方法是遍歷查詢后返回的ResultSet對(duì)象,將保存在ResultSet對(duì)象中的值轉(zhuǎn)化為相應(yīng)的對(duì)象放入對(duì)象數(shù)組。下面我們通過(guò)一個(gè)例子來(lái)說(shuō)明如何使用這些類和接口。例如當(dāng)我們需要從數(shù)據(jù)庫(kù)的一張用戶信息表中取出用戶信息,表名稱為User:

          列名 數(shù)據(jù)類型
          ID NUMBER
          UserName VARCHAR2
          Email VARCHAR2



          我們需要在程序中定義一個(gè)類User來(lái)映射上面的表:

          public User(int id, String userName, String email)


          如果我們使用常規(guī)方法來(lái)讀取User表中的數(shù)據(jù),我們需要一個(gè)方法來(lái)從數(shù)據(jù)庫(kù)表中讀取數(shù)據(jù),然后將數(shù)據(jù)送入U(xiǎn)ser對(duì)象中。而且一旦查詢語(yǔ)句發(fā)生變化,我們需要修改大量的代碼。讓我們看一看采用本文描述的解決方案情況會(huì)如何。

          首先構(gòu)造一個(gè)SQL語(yǔ)句。

          private static final String SQL_GET_USER = "SELECT * FROM USERS WHERE ID = ?";


          然后創(chuàng)建一個(gè)ResultProcessor接口的實(shí)例類,通過(guò)它我們可以從查詢結(jié)果中獲得一個(gè)User對(duì)象。

          public class UserResultProcessor implements ResultProcessor {  // 列名稱定義(省略)  ...  public Object[] process(ResultSet rs) throws SQLException {    // 使用List對(duì)象來(lái)保存所有返回的User對(duì)象    List users = new ArrayList();    User user = null;    // 如果查詢結(jié)果有效,處理查詢結(jié)果    while(rs.next()) {      user = new User(rs.getInt(COLUMN_ID), rs.getString(COLUMN_USERNAME),                      rs.getString(COLUMN_EMAIL));      users.add(user);    }    return users.toArray(new User[users.size()]);


          最后,將執(zhí)行SQL查詢和返回User對(duì)象的指令放入getUser()方法中。

          public User getUser(int userId) {  // 生成一個(gè)SQLProcessor對(duì)象并執(zhí)行查詢  SQLProcessor processor = new SQLProcessor();  Object[] users = processor.executeQuery(SQL_GET_USER_BY_ID,                                          new Object[] {new Integer(userId)},                                          new UserResultProcessor());  // 返回查詢到的第一個(gè)User對(duì)象  return (User) users[0];}

          這就是我們需要做的全部工作:只需要實(shí)現(xiàn)一個(gè)processor類和一個(gè)getUser()方法。與傳統(tǒng)的JDBC程序相比,在本文描述的模型中,我們不需要處理數(shù)據(jù)庫(kù)連接操作,生成prepareStatement對(duì)象和異常處理部分的代碼。如果需要在同一張表中根據(jù)用戶名查用戶ID,我們只需要在代碼中申明新的查詢語(yǔ)句,然后重用UserResultProcessor類中的大部分代碼。

          更新模型

          如果SQL語(yǔ)句中涉及到更新,情況又會(huì)怎樣呢?我們可以用類似于設(shè)計(jì)查詢模型的方法來(lái)設(shè)計(jì)更新模型,我們需要向SQLProcessor類中增加一些新的方法。這些方法同executeQuery()和handleQuery()方法有相似之處,只是我們需要改變一下處理ResultSet對(duì)象的代碼,并且把更新的行數(shù)作為方法的返回值。

          public void executeUpdate(String sql, Object[] pStmntValues,                          UpdateProcessor processor) {  // 獲得數(shù)據(jù)庫(kù)連接  Connection conn = ConnectionManager.getConnection();  // 執(zhí)行SQL語(yǔ)句  handleUpdate(sql, pStmntValues, processor, conn);  // 關(guān)閉連接  closeConn(conn);}protected void handleUpdate(String sql, Object[] pStmntValues,                            UpdateProcessor processor, Connection conn) {  PreparedStatement stmnt = null;  try {  stmnt = conn.prepareStatement(sql);  // 向prepareStatement對(duì)象中送入值  if(pStmntValues != null) {    PreparedStatementFactory.buildStatement(stmnt, pStmntValues);  }  // 執(zhí)行更新語(yǔ)句  int rows = stmnt.executeUpdate();  // 統(tǒng)計(jì)有多少行數(shù)據(jù)被更新  processor.process(rows);  closeStmnt(stmnt);  // 異常處理  } catch(SQLException e) {    String message = "無(wú)法執(zhí)行查詢語(yǔ)句 " + sql;    closeConn(conn);    closeStmnt(stmnt);    throw new DatabaseUpdateException(message);  }}



          上面的兩個(gè)方法和處理查詢的方法不同之處在于他們?nèi)绾翁幚矸祷刂?。由于更新語(yǔ)句只需要返回被更新了的行數(shù),所以我們不需要處理SQL操作返回的結(jié)果。實(shí)際上有些情況下連被更新了的行數(shù)都不需要返回,我們這樣做的原因是在某些情況下需要確認(rèn)更新操作已經(jīng)完成。

          我們?cè)O(shè)計(jì)了UpdateProcessor接口來(lái)處理Update操作返回的更新行數(shù)。

          public interface UpdateProcessor {  public void process(int rows);}



          例如在程序中需要保證更新操作,更新表中的至少一條記錄。在UpdateProcessor接口的實(shí)現(xiàn)類中就可以加入對(duì)修改行數(shù)的檢測(cè),當(dāng)沒(méi)有記錄被更新時(shí),processor()方法可以拋出自定義的異常;也可以將更新的行數(shù)記錄到日志文件中;或者激發(fā)一個(gè)自定義的更新事件。總而言之,你可以在其中做任何事。

          下面是一個(gè)使用更新模型的例子:

          首先生成SQL語(yǔ)句

          private static final String SQL_UPDATE_USER = "UPDATE USERS SET USERNAME = ?, EMAIL = ? WHERE ID = ?";



          實(shí)現(xiàn)UpdateProcessor接口。在Processor()方法中,檢查Update操作是否更新了數(shù)據(jù)。如果沒(méi)有,拋出IllegalStateException異常。

          public class MandatoryUpdateProcessor implements UpdateProcessor {  public void process(int rows) {    if(rows < 1) {      String message = "更新操作沒(méi)有更新數(shù)據(jù)庫(kù)表中的數(shù)據(jù)。";      throw new IllegalStateException(message);    }  }}



          最后在updateUser()方法中執(zhí)行Update操作并處理結(jié)果。

          public static void updateUser(User user) {  SQLProcessor sqlProcessor = new SQLProcessor();  sqlProcessor.executeUpdate(SQL_UPDATE_USER,                             new Object[] {user.getUserName(),                                           user.getEmail(),                                           new Integer(user.getId())},                             new MandatoryUpdateProcessor());}

           

          事務(wù)模型

          在數(shù)據(jù)庫(kù)中,事務(wù)和獨(dú)立的SQL語(yǔ)句的區(qū)別在于事務(wù)在生命期內(nèi)使用一個(gè)數(shù)據(jù)庫(kù)連接,并且AutoCommit屬性必須被設(shè)為False。因此我們需要指定事務(wù)何時(shí)開始,何時(shí)結(jié)束,并且在事務(wù)結(jié)束時(shí)提交事務(wù)。我們可以重用SQLProcessor中的大部分代碼來(lái)處理事務(wù)。也許在最開始讀者會(huì)問(wèn)為什么要把執(zhí)行更新和處理更新的工作放在 executeUpdate()和handleUpdate()兩個(gè)函數(shù)中完成--實(shí)際上它們是可以被合并到同一個(gè)函數(shù)中的。這樣做的原因是把處理數(shù)據(jù)庫(kù)連接的代碼和處理SQL操作的代碼分離開來(lái)。對(duì)于需要在多個(gè)SQL操作間共用數(shù)據(jù)庫(kù)連接的事務(wù)模型來(lái)說(shuō),這種方案便于編碼。

          在事務(wù)中,我們需要保存事務(wù)的狀態(tài),特別是數(shù)據(jù)庫(kù)連接的狀態(tài)。前面的SQLProcessor中沒(méi)有保存狀態(tài)的屬性,為了保證對(duì)SQLProcessor類的重用,我們?cè)O(shè)計(jì)了一個(gè)包裝類,該類包裝了SQLProcessor類,并且可以維護(hù)事務(wù)在生命周期內(nèi)的狀態(tài)。

          public class SQLTransaction {  private SQLProcessor sqlProcessor;  private Connection conn;  // 缺省構(gòu)造方法,該方法初始化數(shù)據(jù)庫(kù)連接,并將AutoCommit設(shè)定為False  ...  public void executeUpdate(String sql, Object[] pStmntValues,                            UpdateProcessor processor) {    // 獲得結(jié)果。如果更新操作失敗,回滾到事務(wù)起點(diǎn)并拋出異常    try {       sqlProcessor.handleUpdate(sql, pStmntValues, processor, conn);    } catch(DatabaseUpdateException e) {       rollbackTransaction();       throw e;    }   }  public void commitTransaction() {    // 事務(wù)結(jié)束,提交更新并回收資源    try {      conn.commit();      sqlProcessor.closeConn(conn);    // 如果發(fā)生異常,回滾到事務(wù)起點(diǎn)并回收資源    } catch(Exception e) {      rollbackTransaction();      throw new DatabaseUpdateException("無(wú)法提交當(dāng)前事務(wù)");    }  }  private void rollbackTransaction() {    // 回滾到事務(wù)起點(diǎn)并回收資源    try {      conn.rollback();      conn.setAutoCommit(true);      sqlProcessor.closeConn(conn);    // 如果在回滾過(guò)程中發(fā)生異常,忽略該異常    } catch(SQLException e) {      sqlProcessor.closeConn(conn);    }  }}



          SQLTransaction中出現(xiàn)了一些新方法,這些方法主要是用來(lái)處理數(shù)據(jù)庫(kù)連接和進(jìn)行事務(wù)管理的。當(dāng)一個(gè)事務(wù)開始時(shí),SQLTransaction對(duì)象獲得一個(gè)新的數(shù)據(jù)庫(kù)連接,并將連接的AutoCommit設(shè)定為False,隨后的所有SQL語(yǔ)句都是用同一個(gè)連接。

          只有當(dāng)commitTransaction()被調(diào)用時(shí),事務(wù)才會(huì)被提交。如果執(zhí)行SQL語(yǔ)句的過(guò)程中發(fā)生了異常,程序會(huì)自動(dòng)發(fā)出一個(gè)回滾申請(qǐng),以恢復(fù)程序?qū)?shù)據(jù)庫(kù)所作的改變。對(duì)于開發(fā)人員來(lái)說(shuō),不需要擔(dān)心在出現(xiàn)異常后處理回滾或關(guān)閉連接的工作。下面是一個(gè)使用事務(wù)模型的例子。

          public static void updateUsers(User[] users) { // 開始事務(wù)  SQLTransaction trans = sqlProcessor.startTransaction();  // 更新數(shù)據(jù)  User user = null;  for(int i = 0; i < users.length; i++) {    user = users[i];    trans.executeUpdate(SQL_UPDATE_USER,                       new Object[] {user.getUserName(),                                     user.getFirstName(),                                     user.getLastName(),                                     user.getEmail(),                                     new Integer(user.getId())},                       new MandatoryUpdateProcessor());  }  // 提交事務(wù)  trans.commitTransaction();}



          在例子中我們只使用了更新語(yǔ)句(在大多數(shù)情況下事務(wù)都是由更新操作構(gòu)成的),查詢語(yǔ)句的實(shí)現(xiàn)方法和更新語(yǔ)句類似。

          問(wèn)題

          在實(shí)際使用上面提到的這些模型時(shí),我遇到了一些問(wèn)題,下面是這些問(wèn)題的小結(jié),希望對(duì)大家有所幫助。

          自定義數(shù)據(jù)庫(kù)連接

          在事務(wù)處理的時(shí)候,有可能發(fā)生在多個(gè)事務(wù)并存的情況下,它們使用的數(shù)據(jù)庫(kù)連接不同的情況。ConnectionManager需要知道它應(yīng)該從數(shù)據(jù)庫(kù)連接池中取出哪一個(gè)連接。你可以簡(jiǎn)單修改一下模型來(lái)滿足上面的要求。例如在executeQuery()和executeUpdate()方法中,你可以把數(shù)據(jù)庫(kù)連接作為參數(shù),然后將它們傳送給ConnectionManager對(duì)象。請(qǐng)記住所有的連接管理都應(yīng)該放在executeXXX()方法中。另外一種解決方案,也是一種更面向?qū)ο蠡慕鉀Q方案,是將一個(gè)連接工廠作為參數(shù)傳遞給SQLProcessor的構(gòu)造函數(shù)。對(duì)于不同的連接工廠類型,我們需要不同的SQLProcessor對(duì)象。

          ResultProcessor類的返回值:對(duì)象數(shù)組還是List?

          為什么ResultProcessor接口中的process()方法返回的是對(duì)象數(shù)組呢?怎么不使用List類呢?這是由于在很多實(shí)際的應(yīng)用中,SQL查詢?cè)诖蠖鄶?shù)情況下值返回一行數(shù)據(jù),在這種情況下,使用List對(duì)象會(huì)有些多余了。但是如果你確信SQL查詢將返回多行結(jié)果,你可以使用List對(duì)象。

          數(shù)據(jù)庫(kù)操作異常

          我們可以用多個(gè)自定義的數(shù)據(jù)庫(kù)操作異常類來(lái)替代運(yùn)行時(shí)發(fā)生的SQLException異常。最好在這些自定義的異常類時(shí)繼承RuntimeException類,這樣可以將這些異常進(jìn)行集中處理。也許你會(huì)認(rèn)為因該將異常處理放在發(fā)生異常的地方。但是我們?cè)O(shè)計(jì)這個(gè)的模型的目的之一是在JDBC應(yīng)用程序開發(fā)中去掉或弱化異常處理的部分,只有使用RuntimeException我們才可能達(dá)到這個(gè)目的。

          posted on 2007-10-15 22:20 都市淘沙者 閱讀(165) 評(píng)論(0)  編輯  收藏 所屬分類: Java Basic/Lucene/開源資料

          主站蜘蛛池模板: 和平区| 宣武区| 榆树市| 平原县| 阿合奇县| 都兰县| 婺源县| 礼泉县| 抚州市| 太和县| 铜陵市| 海淀区| 岳西县| 宁河县| 敖汉旗| 绥德县| 石棉县| 仁寿县| 开原市| 巴中市| 阿勒泰市| 万载县| 新营市| 乐陵市| 定州市| 鹤山市| 肃北| 瑞金市| 班戈县| 邵阳市| 两当县| 阿拉善盟| 垫江县| 闵行区| 寿宁县| 嘉禾县| 巴楚县| 定襄县| 和顺县| 阿尔山市| 辉南县|