[Java] 自己實現.Net的DataTable、DataRow、DataColumn

  • 18319
  • 0
  • Java
  • 2011-11-27

[Java] 實現ADO.net DataTable、DataRow、DataColumn

.Net的DataTable真好用,傳欄位名稱或欄位索引就可以找到資料

因為 JDBC的ResultSet物件相當.Net 的DataReader,靠著游標移動抓資料很麻煩,用完後還要自己關閉Connection

所以花了一晚沒睡= =”

乾脆自己實現山寨版的DataTable、DataRow、DataColumn。

經過這幾天專案上的實際運用,看來沒問題的樣子,就PO出來分享,順便說明一下底層運作。

 

因為個人使用DataTable的目的,只是單純地保存資料,讀取資料,就像二維陣列一樣,只是多個可以傳欄位名稱抓資料功能(這樣才不用數欄位數到下班 Orz),所以程式碼的功能實現趨向簡單

沒有Select()、isLike() 等等。 (因為個人覺得交由SQL語法做會比較準確)

 

先從DataRow類別的設計開始吧,請先看以下的DataRow有什麼特性

image

說明:一個DataRow物件,有一個欄位(不會重覆)對應一個值

所以這特性可以用Map字典集合的物件實現

 

DataRow.java

package System.Data;

import java.util.LinkedHashMap;
 
  //讓DataRow成為一個Map字典物件,String為欄位(不會重覆)當做Key,Object當做要存放的值
  public class DataRow extends LinkedHashMap<String,Object>{

    
      /**
      * DataRow被建立時,必須指定所屬的DataTable
      * @param DataRow所屬的DataTable 
      */
     public DataRow(DataTable table) {
        this.table = table;
       }


     /**
      * 此資料列所屬的DataTable,唯讀
      */
     private DataTable table; 
     
     
    /**
      * 取得DataRow所屬的DataTable
      * @return DataTable
      */
    public DataTable getTable()
    {
      return this.table;
    }

 
  
   
}

而DataRowCollection就是DataRow的集合

DataRowCollection.java

package System.Data;

import java.util.ArrayList;


public class DataRowCollection extends ArrayList<DataRow>{
    
    /**
     * DataRowCollection所屬的DataTable,唯讀
     */
    private DataTable Table;

    /**
     * DataRowCollection被建立時,一定要指定所屬的DataTable
     * @param table 
     */
    public DataRowCollection(DataTable table)
    {
     this.Table = table;
    
    }
    
    /**
     * 取得所屬的DataTable
     * @return DataTable
     */
    public DataTable getTable()
    {
     return this.Table;
    }
    
    
    
}
請回顧一下這張圖

每個DataRow都有自己的索引(從0算起),rowIndex這部份由於DataRowCollection繼承ArrayList<DataRow>,所以就交由ArrayList來管理rowIndex

DataRowCollection類別設計完畢。

 

其實DataTable的資料集就是DataRowCollection

DataTable.java

package System.Data;


public class DataTable {
     
     /**
     * 保存DataRow的集合,在DataTable初始化時,便會建立
     */
     public DataRowCollection Rows; 
    
     /**
      * DataTable的名稱,沒什麼用到
      */
     public String TableName; 

     /**
      * 初始化DataTable,並建立DataRowCollection
      */
     public DataTable() {
        
         this.Rows = new DataRowCollection(this);
       
     }
     
     /**
     * 除了初始化DataTable, 可以指定DataTable的名字(沒什麼意義)
     * @param dataTableName DataTable的名字
     */
     public DataTable(String tableName) {
         this();
         this.TableName = tableName;
     }



    /**
      * 由此DataTable物件來建立一個DataRow物件
      * @return DataRow
      */
     public DataRow NewRow()  {
        
        DataRow row = new DataRow(this);
        
        return row;
   }

    
     

      
 }



完成3成左右,接著再回頭看DataRow,該如何設定資料、取得資料

DataRow.java 追加setXXX、getXXX方法

package System.Data;

import java.util.LinkedHashMap;
 
  public class DataRow extends LinkedHashMap<String,Object>{

    
     /**
      * 此資料列所屬的DataTable,唯讀
      */
     private DataTable table; 
     
   
     /**
      * DataRow被建立時,必須指定所屬的DataTable
      * @param DataRow所屬的DataTable 
      */
     public DataRow(DataTable table) {
        this.table = table;
     }

     
    /**
      * 取得DataRow所屬的DataTable
      * @return DataTable
      */
    public DataTable getTable()
    {
      return this.table;
    }

    /**
     * 設定該列該行的值
     * @param columnindex 行索引(從0算起)
     * @param value 要設定的值
     */
     public void setValue(int columnindex,Object value) {
         //?????
    }
   
    /**
      * 設定該列該行的值
      * @param columnName 行名稱
      * @param value 要設定的值
      */
     public void setValue(String columnName,Object value) {
         this.put(columnName.toLowerCase(), value);
     }


     /**
      * 設定該列該行的值
      * @param column DataColumn物件
      * @param value 要設定的值
      */
     private void setValue(DataColumn column,Object value) {
        //??????
     }
  
    /**
      * 取得該列該行的值
      * @param columnIndex 行索引(從0算起)
      * @return Object
      */
    public Object getValue(int columnIndex) {
       //??????????
    }
 
    /**
     * 取得該列該行的值
     * @param columnName 行名稱
     * @return Object
     */
    public Object getValue(String columnName) {
        return this.get(columnName.toLowerCase());
    }
   
   /**
     * 取得該列該行的值
     * @param column DataColumn物件
     * @return Object
     */
    public Object getValue(DataColumn column) {
      //??????????????????
   }
   
  }


可以看到黃色標示部份,因為有明確指定columnName(Key),所以可以直接找到對應的Value

置於其它Method,則要用到DataColumnCollection來幫助DataRow得到Key值找Value了

image

請先見DataColumn.java

package System.Data;

public class DataColumn {
    
     /**
     * DataColumn所屬的DataTable
     */
     private DataTable table; 
     /**
      * DataColumn的欄位名稱
      */
     public String ColumnName; // 欄名,當做DataRow的key
     
 
    /**
      * DataColumn被建立時,一定要指定欄名
      * @param columnName 欄名
      */
     public DataColumn(String columnName) {
       this.ColumnName = columnName.toLowerCase();
     }
    
    /**
     * 給DataColumnCollection加入DataColumn時設定所屬的DataTable的方法,同一個package才用到
     * @param table 
     */
     void setTable(DataTable table)
    {
      this.table = table;
    }
    
    /**
     * 取得DataColumn所屬的DataTable,唯讀
     * @return DataTable
     */
    public DataTable getTable()
    {
       return this.table;
    }
  
     /**
     * DataColumn物件的toString(),會回傳自己的欄名
     * @return 
     */
     @Override
     public String toString(){
         return this.ColumnName;
     }
  }


重點就是DataColumn要有欄名

接著DataColumnCollection只不過是DataColumn的集合

DataColumnCollection.java

package System.Data;

import java.util.ArrayList;


public class DataColumnCollection extends ArrayList<DataColumn>{

    /**
     * DataColumnCollection所屬的DataTable,唯讀
     */
    private DataTable Table;
    
    /**
     * DataColumnCollection被建立時,一定要指定所屬的DataTable
     * @param table 
     */
    public DataColumnCollection(DataTable table)
    {
      this.Table = table;
    }
    
    /**
     * 取得DataColumnCollection所屬的DataTable
     * @return DataTable
     */
    public DataTable getTable()
    {
      return this.Table;
    }
    
    /**
     * 加入一個DataColumn物件,程式碼會設定該DataColumn的DataTable和呼叫Add()方法的DataColumnCollection同一個DataTable
     * @param column 
     */
    public void Add(DataColumn column)
    {  
       column.setTable(this.Table);
       this.add(column);
    }
    
    /**
     * 給欄位名稱
     * <br/>加入一個DataColumn物件,程式碼會設定該DataColumn的DataTable和呼叫Add()方法的DataColumnCollection同一個DataTable
     * @param columnName
     * @return 
     */
    public DataColumn Add(String columnName)
    {  
       DataColumn column = new DataColumn(columnName.toLowerCase());
       column.setTable(this.Table);
       this.add(column);
       return column;
    }
     
    /**
     * 依據欄名,取得DataColumn
     * @param columnName 欄名
     * @return DataColumn
     */
    public DataColumn get(String columnName)
    {   
        
       DataColumn column = null;
       for(DataColumn dataColumn :this)
       {
           if (dataColumn.ColumnName.toLowerCase().equals(columnName.toLowerCase())) {
               return dataColumn;
           }
       
       }
        return column;
    }
    

}


請留意,因為DataColumnCollection繼承ArrayList<DataColumn>,所以本身已有add(DataColumn obj)的方法

但若交由其他物件使用,為了避免add(DataColumn obj)時,工程師忘了順便幫DataColumn設定所屬的DataTable,所以再另外寫一個Add(DataColumn column)的方法,

裡頭自動設定該column所屬的DataTable

另,ArrayList<DataColumn>,也提供了add(int index,DataColumn column)和get(int index)方法,所以columnIndex部份就交由ArrayList管理就好

只是我們要再另外寫一個get(String columnName)回傳DataColumn的方法,這樣才會跟原始版(.Net)有異曲同工之妙

DataColumn相關的程式碼就這樣結束

再回到DataTable類別,加入DataColumnCollection成員

package System.Data;


public class DataTable {
     
     /**
     * 保存DataRow的集合,在DataTable初始化時,便會建立
     */
     public DataRowCollection Rows; 
     /**
      * 保存DataColumn的集合,在DataTable初始化時,便會建立
      */
     public DataColumnCollection Columns; 
     /**
      * DataTable的名稱,沒什麼用到
      */
     public String TableName; 

     /**
      * 初始化DataTable,並建立DataColumnCollection,DataRowCollection
      */
     public DataTable() {
         this.Columns = new DataColumnCollection(this);
         this.Rows = new DataRowCollection(this);
         
     }
     
     /**
     * 除了初始化DataTable, 可以指定DataTable的名字(沒什麼意義)
     * @param dataTableName DataTable的名字
     */
     public DataTable(String tableName) {
         this();
         this.TableName = tableName;
     }



    /**
      * 由此DataTable物件來建立一個DataRow物件
      * @return DataRow
      */
     public DataRow NewRow()  {
        
        DataRow row = new DataRow(this);
        
        return row;
   }

   
     

      
 }



這樣DataTable類別也設計完畢,如果想把DataTable當作二維陣列讀寫資料的話,待會請看最後的程式碼

在那之前,回到DataRow類別來解決如何透過columnIndex和DataColumn物件來讀寫DataRow

DataRow.java

package System.Data;

import java.util.LinkedHashMap;
 
  public class DataRow extends LinkedHashMap<String,Object>{

    
    /**
      * 在getValue()和setValue()時候,程式碼須透過此成員的欄位名稱來找出Map字典裡的物件
      */
     private DataColumnCollection columns;
     /**
      * 此資料列所屬的DataTable,唯讀
      */
     private DataTable table; 
     
   
     /**
      * DataRow被建立時,必須指定所屬的DataTable
      * @param DataRow所屬的DataTable 
      */
     public DataRow(DataTable table) {
        this.table = table;
        this.columns = table.Columns;
     }

     
    /**
      * 取得DataRow所屬的DataTable
      * @return DataTable
      */
    public DataTable getTable()
    {
      return this.table;
    }

    /**
     * 設定該列該行的值
     * @param columnindex 行索引(從0算起)
     * @param value 要設定的值
     */
     public void setValue(int columnindex,Object value) {
         setValue(this.columns.get(columnindex), value);
    }
   
    /**
      * 設定該列該行的值
      * @param columnName 行名稱
      * @param value 要設定的值
      */
     public void setValue(String columnName,Object value) {
         this.put(columnName.toLowerCase(), value);
     }
 
     /**
      * 設定該列該行的值
      * @param column DataColumn物件
      * @param value 要設定的值
      */
     private void setValue(DataColumn column,Object value) {
         if (column != null) {
             String lowerColumnName = column.ColumnName.toLowerCase();
           if (this.containsKey(lowerColumnName))
                this.remove(lowerColumnName);
             this.put(lowerColumnName, value);
         }
     }
  
    /**
      * 取得該列該行的值
      * @param columnIndex 行索引(從0算起)
      * @return Object
      */
    public Object getValue(int columnIndex) {
        String columnName = this.columns.get(columnIndex).ColumnName.toLowerCase();//取得Key
        return this.get(columnName);
    }
 
    /**
     * 取得該列該行的值
     * @param columnName 行名稱
     * @return Object
     */
    public Object getValue(String columnName) {
        return this.get(columnName.toLowerCase());//利用欄名(Key)來取值
    }
   
   /**
     * 取得該列該行的值
     * @param column DataColumn物件
     * @return Object
     */
    public Object getValue(DataColumn column) {
      return this.get(column.ColumnName.toLowerCase());//利用欄名(Key)來取值
   }
   
  }


DataRow類別設計完畢

最後,要把DataTable當作二維陣列一樣可以讀寫某列某行資料的話

DataTable.java

package System.Data;


public class DataTable {
     
     /**
     * 保存DataRow的集合,在DataTable初始化時,便會建立
     */
     public DataRowCollection Rows; 
     /**
      * 保存DataColumn的集合,在DataTable初始化時,便會建立
      */
     public DataColumnCollection Columns; 
     /**
      * DataTable的名稱,沒什麼用到
      */
     public String TableName; 

     /**
      * 初始化DataTable,並建立DataColumnCollection,DataRowCollection
      */
     public DataTable() {
         this.Columns = new DataColumnCollection(this);
         this.Rows = new DataRowCollection(this);
         
     }
     
     /**
     * 除了初始化DataTable, 可以指定DataTable的名字(沒什麼意義)
     * @param dataTableName DataTable的名字
     */
     public DataTable(String tableName) {
         this();
         this.TableName = tableName;
     }



    /**
      * 由此DataTable物件來建立一個DataRow物件
      * @return DataRow
      */
     public DataRow NewRow()  {
        
        DataRow row = new DataRow(this);//DataRow為呼叫此方法DataTable的成員
        
        return row;
   }

    
     /**
      * 把DataTable當做二維陣列,給列索引和行索引,設定值的方法
      * <br/>(發佈者自行寫的方法)
      * @param rowIndex 列索引(從0算起)
      * @param columnIndex 行索引(從0算起)
      * @param value 要給的值
      */
    public void setValue(int rowIndex, int columnIndex,Object value) {
        this.Rows.get(rowIndex).setValue(columnIndex, value);
    }
 
    /**
      * 把DataTable當做二維陣列,給列索引和行名稱,設定值的方法
      * <br/>(發佈者自行寫的方法)
      * @param rowIndex 列索引(從0算起)
      * @param columnIndex 行名稱
      * @param value 要給的值
      */
     public void setValue(int rowIndex,String columnName,Object value) {
         this.Rows.get(rowIndex).setValue(columnName.toLowerCase(), value);
     }
     
     
     /**
      * 把DataTable當做二維陣列,給列索引和行索引,取得值的方法
      * <br/>(發佈者自行寫的方法)
      * @param rowIndex 列索引(從0算起)
      * @param columnIndex 行索引(從0算起)
      * @return 回傳該位置的值
      */
    public Object getValue(int rowIndex,int columnIndex) {
        return this.Rows.get(rowIndex).getValue(columnIndex);
    }
          
          
     /**
     * 把DataTable當做二維陣列,給列索引和行名稱,取得值的方法
     * <br/>(發佈者自行寫的方法)
     * @param rowIndex 列索引(從0算起)
     * @param columnName 行名稱
     * @return 回傳該位置的值
     */
     public Object getValue(int rowindex,String columnName) {
        return this.Rows.get(rowindex).getValue(columnName.toLowerCase());
    }
     

      
 }



四個黃標的方法,微軟的DataTable物件沒有,只有本人寫的山寨版才有XD

接著把五個類別包成一個JAR檔(我是用JDK1.5編譯)

然後用NetBeans IDE開一個Web專案引用該JAR檔

測試程式碼:

DataTable.jsp

<%@page import="java.util.*"%>
<%@page import="System.Data.*"%>
<%@page contentType="text/html" pageEncoding="UTF-8"%>
<%
   DataTable dt = new DataTable();
   //定義三個欄位
   dt.Columns.Add("CustomerID");
   dt.Columns.Add("CustomerName");
   DataColumn column = new DataColumn("Address");
   dt.Columns.Add(column);
   
    //產生第一列,用欄名設值
    DataRow dr = dt.NewRow();
    dr.setValue("CustomerID", 1);
    dr.setValue("CustomerName", "Shadow");
    dr.setValue("Address", "點部落格");
    dt.Rows.add(dr);//加入至DataRowCollection
 
    //產生第二列,用columnIndex設值
    dr = dt.NewRow();
    dr.setValue(0, 2);
    dr.setValue(1, "Super Man");
    dr.setValue(2, "U.S.A");
    dt.Rows.add(dr);//加入至DataRowCollection
    
    
    //直接用DataTable+欄名 設定值
    DataRow r=new DataRow(dt);
    dt.Rows.add(r);
    dt.setValue(2, "CustomerID", 3);
    dt.setValue(2, "CustomerName", "Java");
    dt.setValue(2, "Address", "unknown");
    
    //直接用DataTable+columnIndex 設定值
    DataRow r2 = new DataRow(dt);
    dt.Rows.add(r2);
    dt.setValue(3, 0, 4);
    dt.setValue(3, 1, "Microsoft");
    dt.setValue(3, 2, "U.S.A");
%>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    </head>
    <body>
        <form name="form1" method="post" action="DataTable.jsp" >

           
            
            <%
             
             out.print("<table border='1'>");
             out.print("<tr><td>CustomerID</td><td>CustomerName</td><td>Address</td></tr>");
             for(DataRow row : dt.Rows){
              out.print("<tr><td>"+row.getValue("CustomerID") +"</td><td>"+row.getValue(1) +"</td><td>"+row.getValue("Address") +"</td></tr>");
             
             }
        //透過DataTable直接抓資料
         out.print("<tr><td>"+dt.getValue(0, "CustomerID") +"</td><td>"+dt.getValue(0, 1) +"</td><td>"+dt.getValue(0, "Address") +"</td></tr>");
             out.print("</table>");
             
            %>
        </form>
    </body>
</html>


執行結果:

2011.6.8 追記

發現MSSQL的欄名,預設定序是不分大小寫,所以程式也改成欄名大小寫一律識為相同欄名。