[Java] 實現ADO.net DataTable、DataRow、DataColumn
.Net的DataTable真好用,傳欄位名稱或欄位索引就可以找到資料
因為 JDBC的ResultSet物件相當.Net 的DataReader,靠著游標移動抓資料很麻煩,用完後還要自己關閉Connection
所以花了一晚沒睡= =”
乾脆自己實現山寨版的DataTable、DataRow、DataColumn。
經過這幾天專案上的實際運用,看來沒問題的樣子,就PO出來分享,順便說明一下底層運作。
因為個人使用DataTable的目的,只是單純地保存資料,讀取資料,就像二維陣列一樣,只是多個可以傳欄位名稱抓資料功能(這樣才不用數欄位數到下班 Orz),所以程式碼的功能實現趨向簡單
沒有Select()、isLike() 等等。 (因為個人覺得交由SQL語法做會比較準確)
先從DataRow類別的設計開始吧,請先看以下的DataRow有什麼特性
說明:一個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了
請先見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
然後用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的欄名,預設定序是不分大小寫,所以程式也改成欄名大小寫一律識為相同欄名。