顯示具有 Database 標籤的文章。 顯示所有文章
顯示具有 Database 標籤的文章。 顯示所有文章

2012年7月5日 星期四

C# 資料庫存取

首先關於資料庫的開啟可考慮兩種方式
1.用using{...}區塊
using(SqlConnection con = new SqlConnection())
{
    con.ConnectionString = "....";
    con.Open();
    ....
}

這種方法會在脫離區塊後立刻釋放Connection物件的資源
所以不用使用資料庫的關閉方法(如Close()、Dispose())
另外就是正規的寫法

2.

using System.Data.SqlClient;
....
SqlConnection con = new SqlConnection("xxx");       //xxx = connection string
SqlCommand cmd = new SqlCommand("xxx", con); //xxx = SQL command 
SqlDataReader dr;
...
con.Open();
dr = cmd.ExecuteReader();    //使用DataReader物件讀取資料庫內容
...
con.Close();  


SqlCommand物件的幾個常用方法跟屬性
   ExecuteNonQuery() -
   可以執行INSERT、UPDATE、DELETE等SQL command,成功時回傳受影響的紀錄筆數

   ExecuteScalar() -
   適用於結果集只回傳一個資料列的第一欄,例如SQL command 的COUNT()、MAX()等

   ExecuteReader() -
   執行Command物件中所指定的SQL的SELECT敘述,並建立一個DataReader來瀏覽資料

   CommandText -
   設定或取得要執行的命令


SqlCommand使用範例:

string sqlStr = string.Empty;
sqlStr = "INSERT INTO TableA(姓名, 聯絡方式, 薪水)" + "VALUES(@name, @tel, @pay)"
SqlCommand cmd = new SqlCommand( sqlStr , con);
cmd.CommandType = CommandType.StoreProcedure;     //預先編譯SQL處理函式

cmd.Parameters.Add(new SqlParameter("@name", SqlDbType.NVarChar));
cmd.Parameters.Add(new SqlParameter("@tel", SqlDbType.NVarChar));
cmd.Parameters.Add(new SqlParameter("@pay", SqlDbType.NVarChar));
cmd.Parameters["@name"].Value = "vc56";
cmd.Parameters["@tel"].Value = "0000000";
cmd.Parameters["@pay"].Value = "$20000";

cmd.ExecuteNonQuery();


DataReader有幾個方法跟屬性較為重要,以下列出:
   FieldCount屬性 -
   將已查詢欄位的總數傳回
 
   Read() -
   將指標移到下一筆,並判斷是否指到EOF
   如果到資料結尾則回傳false

   Item[i] -
   取得第i欄的資料內容

   Item["欄位名稱"]集合 -
   取得指定欄位名稱所指的資料內容

   GetValue(i) -
   取得第i欄位的資料內容,傳回值為object資料型別

   IsDBNull(i) -
   判斷第i個欄位使否為資料庫Null值

使用範例:
while(dr.Read())
{
  for(int i = 0; i < dr.FieldCount; i++)
  {
      textBox1.Text += dr[i].ToString() + "\t";
  }
}

以執行效率來說,使用索引會比使用欄位名稱還快
此外,取出時可使用方法來省略手動轉型的動作,如下:

while(dr.Read())
{
  textBox1.Text += dr.GetString(0) + "\t";
  textBox1.Text += dr.GetInt32(1).ToString() + "\t";
  ...
}


DataSet則是ADO.NET的特色
他會先將資料庫的內容存至記憶體中,並得以執行離線操作
操作完畢後再將內容回寫至資料庫中
適用於多用戶端資料存取,當然占用的記憶體多是不可避免的事
DataSet中可以包含一個以上的DataTable物件
由DataAdapter使用Command物件執行SQL command
再將取得的資料寫至DataSet,如此就能用DataTable存取資料表,範例:

using System.Data.SqlClient;
using System.Data;

.....
using(SqlConnection con = new SqlConnection())
{
    con.ConnectionString = "....";
    DataSet ds = new DataSet();
    SqlDataAdapter dsContent = new SqlDataAdapter("xxx", con);  //xxx = sql command
    dsContent.Fill(ds, "Content");    //使用Fill()時,DataAdapter會自動連線到資料庫
    /*
    Adapter也可以改成SqlDataAdapter dsContent = new SqlDataAdapter();
                                     dsContent.SelectCommand = cmd;
    這樣就可以使用SqlCommand物件來保持彈性


    另外有幾種特性語法示範
   int n = ds.Tables.Count;                          //取得DataSet中DataTable的總數
   String tName = ds.Table[i].TableName;  //取得第i個DataTable的表格名稱
   */
   DataTable dt = ds.Tables["Content"];     //對應SqlDataAdapter  Fill ()時填入的資料表名
   for(int i = 0; i < dt.Rows.Count; i++)
   {
        for(int j = 0; j < dt.Columns.Count; j++)
        {
              textBox1.Text += dt.Rows[i][j].ToString() + "\t";
        }
        textBox1.Text += Enviroment.NewLine;
   }
}
....


DataSet另外尚有使資料表之間產生關連的操作

ds.Realations.Add(  "關聯名稱",
      ds.Tables["dt1"].Columns["dt1要關聯的欄位名稱"],
      ds.Tables["dt2"].Columns["dt2要關聯的欄位名稱"],
)

日後可與DataView物件結合使用
dataGridView1.DataSource = ds;
dataGridView1.DataMember = "dt1";

dataGridView2.DataSource = ds;
dataGridView2.DataMember = "dt1.關聯名稱";

2012年7月3日 星期二

[轉貼] C# 淺析SqlConnection的dispose和close方法差異

原文

引用微軟ADO.Team的經理的話說,sqlconnection的close和dispose實際是做的同一件事,
唯一的區別是Dispose方法清空了connectionString,即設置為了null.

SqlConnection con = new SqlConnection("Data Source=localhost;Initial Catalog=northwind;User ID=sa;Password=steveg");
        con.Open();
        con.Close();
        con.Open();
        con.Dispose();
        con.Open();

    上例運行發現,close掉的connection可以重新open
    dispose的不行,因為connectionstring清空了,
    會拋出InvalidOperationException提示The ConnectionString property has not been initialized,
    但請注意此時sqlconnection對象還在。

    如果dispose後給connectionString重新賦值,則不會報錯。 
    由此得出的結論是不管是dispose還是close都不會銷毀對象,即不會釋放內存,
    它們會把sqlconnection對象丟到連接池中,那此對象什麼時候銷毀呢?
    我覺得應該是connection timeout設置的時間內,
    如果程序中沒有向連接池發出請求說要connection對象,sqlconnection對象便會銷毀,
    這也是連接池存在的意義。

    剛開始以為dispose會釋放資源清空內存,
    如果這樣的話,連接池不是每次都是要創建新對象,那何來重用connection呢?
    在網上看到很多人說close比dispose好,
    我想真正的原因是dispose後的sqlconnection對象要重新初始化連接字符串而已,
    並不是像某些人說的dispose會釋放對象。

    所以在try..catch和using的選擇上大膽的使用using吧,
    真正的效率差異我想可能只有百萬分之一秒吧
    (連接池重用該連接對象初始化連接字符串的時間),
    而且enterprise library中封裝的data access層全是用的using,
    從代碼的美觀和效率上綜合考慮,using好 

    補充:
    using不會捕捉其代碼快中的異常,
    只會最後執行dispose方法,相當於finally{dispose},
    本文主要是想說明dispose和close的差異,因為using是絕對dispose的,
    可是如果人為的寫try..finally有的人會選擇close有的人會選擇dispose,
    實際上在這2者的選擇上是有差異的。

    2010年9月26日 星期日

    JDBC 簡介與基礎使用

    JDBC(Java Database Connectivity)是Java規範資料庫存取的API
    資料庫廠商依據Java提供的介面進行實作,開發者則依照JDBC的標準進行操作
    如此設計的因素是每家廠商提供用來操作的 API 並不相同
    開發者透過JDBC的介面操作的好處是,若有更換資料庫的要求,可以避免大量修改程式碼
    只需要替換實作廠商的資料庫驅動
    也就是寫一個Java程式就能應付所有的資料庫
    當然實作時,有時為了使用資料庫的特定功能,仍有修改程式碼的必要

    廠商實作JDBC驅動程式的方式有四種類型
    1.JDBC-ODBC Bridge Driver
       ODBC(Open DataBase Connectivity)是微軟所主導的資料庫連結標準
       所以也很常在 Microsoft 的系統上使用
       這類的驅動程式將JDBC的呼叫轉換成對ODBC的呼叫
       但因為兩者並非一對一的對應,所以在功能及存取速度上都有所限制

    2.Native API Driver
       載入由廠商提供的 C\C++撰寫成的原生函式庫
       直接將JDBC的呼叫轉成資料庫中的相關 API 呼叫
       因為直接呼叫 API 的關係,操作速度比第一種快(但比不上後兩種)
       但因為驅動程式作用的對象只限制在原生資料庫
       沒有做到JDBC的跨資料庫的目標

    3.JDBC-Net Driver
       這類型的驅動程式提供了用戶端網路API,JDBC驅動透過Socket呼叫伺服器上的中介元件
       由該元件將JDBC的呼叫轉換成 API 呼叫
       客戶端的驅動程式與中介元件間的協定是固定的,所以不需要載入廠商的函式庫
       也因此在更換資料庫時,只需修改中介元件即可
       此種方式也是相當有彈性的架構

    4.Native Protocol Driver
       這類的驅動程式使用Socket,直接在用戶端和資料庫間通訊
       也因為這是最直接的實作方式,通常具有最快的存取速度
       但針對不同的資料庫需使用不同的驅動程式
       為不需要JDBC-Net Driver那般彈性時的選擇,也是最常見的驅動程式類型

    接下來的操作範例以第四種為主,並以MySQL為操作範例
    MySQL提供的JDBC驅動程式是 mysql-connector-java ,可在其官網中找到
    首先來個範例:
    SQLController.java=======

    package DB;

    import java.sql.*;

    public class SQLController {
      private Connection con = null;      //Database objects
      private Statement stat = null;        //SQL code to execute
      private ResultSet res = null;         //result
      private PreparedStatement pst = null;

      public SQLController(){

      }
      public SQLController(String SQLType, String SQLDriver, String daatabase, String account, String password){

    /*  範例裡傳入的參數
    SQLType = "mysql"
    SQLDriver = "com.mysql.jdbc.Driver"
    其餘的則按資料庫的設定傳入
    */ 
        
          String conInfo = "jdbc:" + SQLType + "://localhost:3306/" + daatabase + "?useUnicode=true&characterEncoding=UTF-8";
         
          try {
              //register driver
              Class.forName(SQLDriver);
              con = DriverManager.getConnection(conInfo, account, password); 
          }
          catch(ClassNotFoundException e){
                System.out.println("DriverClassNotFound :"+e.toString());
          }
           //prevent SQL code error
          catch(SQLException x) {
                System.out.println("Exception :"+x.toString());
          }
      }

      //clear objects in order to close database
      private void Close()
      {
        try{       
          if(res!=null){  res.close();}
          if(stat!=null){ stat.close();}
          if(pst!=null){ pst.close(); }
          if(con!=null){ con.close(); }
        }
        catch(SQLException e){
          System.out.println("Close Exception :" + e.toString());
        }
      }

    }

    網路上可以找到寫成JavaBean格式的寫法,搭配EL也能正常地使用資料庫
    範例將資料庫連線寫成Java物件,寫法依個人喜好而定

    連結資料庫前必須先載入JDBC驅動程式
    透過Class.forName(),範例程式動態載入 com.mysql.jdbc.Driver 類別至 DriverManager
    類別會自動向 DriverManager 做註冊
    生成連線時,DriverManager就會使用該驅動建立Connection實例

    JDBC URL定義連線資料庫的協定、子協定、資料來源識別
    型態是  協定:子協定:識別資料來源
    使用MySQL的JDBC URL就會是  jdbc:mysql://localhost:3306/資料庫名稱?參數=值&...
    識別資料裡的 localhost:3306 連結 MySQL的連結阜
    使用中文時需附加參數 useUnicode=true&characterEncoding=UTF8,指定用UTF8編碼
    建立Connection實例時我們必須提供JDBC URL
    DriverManager.getConnection() method有兩種引入參數的版本
    單一 參數的版本必須將資料庫的帳密資料附加在JDBC URL裡,再引入URL
    另一個版本就是本例使用的 DriverManager.getConnection(conInfo, account, password);
    將帳號、密碼當作呼叫時的參數傳入

    SQLException是資料庫處理過程發生異常時會被丟出的例外
    資料庫的使用過程中都必須做這個例外處理的準備

    Connection是連接資料庫的代表物件
    執行SQL還需要取得 java.sql.Statement 物件
    下面的範例示範實際執行SQL碼的撰寫方式,並搭配DCBP與JNDI來進行:
    DatabaseBean.java======

    package tw.vencs;

    import java.sql.*;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.sql.DataSource;

    public class DatabaseBean {
        private DataSource dataSource = null;

        public DatabaseBean(){

        }  
        public void setConSource(DataSource dataSource){
            this.dataSource = dataSource;
        }
        public Connection getConnection() throws SQLException{
            return dataSource.getConnection();
        }
    }

    class SignInBean extends DatabaseBean{
        public boolean isSignIn(String account, String password){
            Connection con = null;
            Statement sta = null;
            ResultSet res = null;
            boolean checkOK = false;
          
            try{
              con = getConnection();
              sta = con.createStatement();
              res = sta.executeQuery("SELECT * FROM `member` WHERE account = '"+ account
                                       +"' AND password = '"+ password +"' ");
            
              //如果有找到與輸入的帳密相同的資料,允許會員登入
              while(res.next()){
                  checkOK = true;
              }
            }catch(SQLException ex){
                Logger.getLogger(SignInBean.class.getName()).log(Level.SEVERE, null, ex);
                throw new RuntimeException();
            }finally{
              try{  
                if(res != null){ res.close();}
                if(sta != null){ sta.close();}
                if(con != null){ con.close();}
              }catch(SQLException ex){
                Logger.getLogger(SignInBean.class.getName()).log(Level.SEVERE, null, ex);
                throw new RuntimeException();
              }
            }
            return checkOK;
        }
    }

    SignInConfirmer.java======

    package tw.vencs;

    import java.io.IOException;
    import javax.servlet.http.*;
    import javax.sql.DataSource;
    import com.colid.DatabaseBean;

    public class SignInConfirmer extends HttpServlet{
      public void doPost(HttpServletRequest request, HttpServletResponse response)throws IOException{
             String account = request.getParameter("account");
             String password = request.getParameter("password");
             String cookieMaker = request.getParameter("cookieMaker");
             //get DBCP connection resource from InitialListener
             DataSource dataSource = (DataSource)getServletContext().getAttribute("conData");

             SignInBean confirmer = new SignInBean();
             confirmer.setConSource(dataSource);
            
             if(confirmer.isSignIn(account, password)){
                 HttpSession session = request.getSession();
                 session.setAttribute("account", account);
                
                 if(cookieMaker.equals("yes")){
                    int life = 30*24*60*60;  //set Life of Cookie to 30 days
                    Cookie cookie = new Cookie("account", account);
                    cookie.setMaxAge(life);        
                    response.addCookie(cookie);
                 }
                
                 //encode URL if user's COOKIE function is closed
                 response.sendRedirect(response.encodeURL("index.jsp"));
             }
             else{
                 getServletContext().setAttribute("warning", true);
                 response.sendRedirect(response.encodeURL("login.jsp"));
             }

      }
    }

    範例是一隻處理登入狀態(會話管理)的 Servlet 程式
    DBCP 與 JNDI 的部分可以去找那篇的筆記來看

    執行SQL碼的 Statement 物件從連線物件中取得,程式碼如: con.createStatement()
    Statement 物件常用的方法有 executeUpdate()、executeQuery()和execute()
    executeUpdate()主要用來執行  CREATE、INSERT、 DROP等改變資料庫內容的SQL碼
    執行完畢後回傳  int 數值,表示資料變動的筆數
    executeQuery() 則如字面所示,用來得到 SELECT等SQL碼查詢到的結果
    執行完畢後回傳 java.sql.ResultSet 物件

    ResultSet 的next() method 會回傳 boolean值來表示是否有下一筆資料
    如果確實有資料的話可以使用 getXXX()的方式取得該筆資料,XXX為資料型別
    查詢結果會從1開始作為欄位的索引值
    例如: int id = res.getINT(1)
    execute() 這個method則用在無法事先得知要執行的SQL碼的場合
    假使回傳的結果是 true,則可以用getResultSet() 取得 ResultSet 物件
    回傳 false 則可以用 getUpdateCount() 得知更新資料筆數

    最後提到上面的範例沒有展示到的 PreparedStatement
    假使有動態資料的情況下
    像上述範例以 + 運算子結合字串組成SQL碼會比較麻煩
    字串每使用一次 + ,其實也是重新製作一個String物件
    如此的字串使用也造成效能上的負擔
    PreparedStatement 的使用與 Statement 很相似,如改寫範例後為:
    PreparedStatement pre = con.prepareStatement("SELECT * FROM `member` WHERE
                                                                                           account =? AND password =?");
    pre.setString(1, account);           //setString() 輸入的參數可以避免 Injection Attack
    pre.setString(2, password);
    pre.executeUpdate();
    pre.clearParameters();

    將SQL碼中要動態輸入的部分以 ? 運算子代替,然後設置所要輸入的變數
    con.prepareStatement() 會建立一個預先編譯的SQL實例
    它也同樣可以使用executeUpdate()、executeQuery() method
    當查詢完畢之後,clearParameters() 清除所輸入的參數
    留下來的實例卻可以填上別的參數繼續使用,而不用重新再建立SQL實例
    會被頻繁使用的SQL碼實例適合用這種方式建立
    setString() 輸入的參數會被視為純粹的String物件,最後在SQL碼內的形式是包覆在引號內
    若是需要填入SQL碼內的是數字,則可以用 setInt() 以免出現SQL碼錯誤的情形
    PreparedStatement 會是效能與安全性考量的好選擇

    2010年9月14日 星期二

    Tomcat 6.0 環境配置 DBCP 與 JNDI 使用

    取得資料庫連線的過程必須經過網路連線、協定交換等多個步驟
    過程中的耗費在單機使用者上也許感覺不太出來
    但放大到使用者眾多的網路應用程式,開開關關的連線就是不可小看的浪費

    改善連線效能的方法之一就是建立儲存了連線實例的連接池
    盡量使用已經開啟的連線,重複使用以減低資源上的浪費
    Tomcat的DBCP連接池機制提供了連接池建立的功能
    JNDI 處理 Java EE 平台上資源的分散式運算的協調與資源上查詢
    JNDI 更詳細的介紹可以在 SUN 的文件裡看到
    接著搭配兩者來示範建立連接池

    Java EE將資料庫處理的相關行為規範在 javax.sql.DataSource 介面
    取得Connecion實例等的處理會由實作介面的物件來處理,我們只需取得DataSource實例即可
    範例以建立一個能被封裝散佈的WAR檔的Web Application為例:
    context.xml=========

    <?xml version="1.0" encoding="UTF-8"?>
    <!-- 應用程式目錄是Demo -->
    <Context antiJARLocking="true" path="/Demo">
      <Resource name="jdbc/sql" auth="Container" type="javax.sql.DataSource"
                maxActive="60" maxIdle="20" maxWait="10000"
                username="***" password="***" driverClassName="com.mysql.jdbc.Driver"
                url="jdbc:mysql://localhost:3306/***?useUnicode=true&amp;characterEncoding=UTF8" />
    </Context>

    web.xml===========

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app ...>
      ....... 
      <resource-ref>
          <res-ref-name>jdbc/sql</res-ref-name>
          <res-type>javax.sql.DataSource</res-type>
          <res-auth>Container</res-auth>
          <res-sharing-scope>Shareable</res-sharing-scope>
      </resource-ref>
      ....

    </web-app>

    DatabaseBean.java====

    package tw.vencs;

    import java.sql.*;
    import java.util.logging.*;
    import javax.naming.*;
    import javax.sql.DataSource;

    public class DatabaseBean {
        private DataSource dataSource;
       
        public DatabaseBean(){
            try{
                Context initContext = new InitialContext();
                //java:comp/env 表示應用程式環境項目
                Context envContext = (Context)initContext.lookup("java:/comp/env");
                //查找 jdbc/sql 對應的 DataSource 物件
                dataSource = (DataSource)envContext.lookup("jdbc/sql");
            }catch(NamingException ex){
                Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
                throw new RuntimeException();
            }
        }
       
        public boolean isConnectedOK(){
            boolean ok = false;
            Connection conn = null;
            try{
                // 透過 DataSource 物件取得連線
                conn = dataSource.getConnection();
                if(!conn.isClosed()){
                    ok = true;
                }
            }catch(SQLException ex){
                Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
            }finally{
                if(conn != null){
                    try{
                        conn.close();
                    }catch(SQLException ex){
                        Logger.getLogger(DatabaseBean.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }
            return ok;
        }
    }

    conn.jsp========

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <jsp:useBean id="db" class="tw.vencs.DatabaseBean" />   
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>db Test</title>
    </head>
    <body>

    <c:choose>
      <c:when test="${db.connectedOK }">連線成功</c:when>
      <c:otherwise>失敗</c:otherwise>
    </c:choose>

    </body>
    </html>


    context.xml需要放置在Web應用程式根目錄的META-INF目錄裡
    <Context>裡的 path 填上相對於容器網路根目錄的應用程式目錄位置
    <Resource>標籤裡有許多與連線設定相關的屬性
    name                       存取辨識用的 JNDI 名稱
    auth                         認證方式,一般為Container
    type                         數據集的型別,使用標準的javax.sql.DataSource
    maxActive               連接池中最多的Connection實例數量,0為不限制
    maxIdle                   最大空閒連接數,也就是一個連接池裡最少要有的Connection實例數量
    maxWait                  等待可用的Connection實例,最大的等待時間,-1為不限制
    username                資料庫使用者名稱
    password                 資料庫密碼
    driverClassName     JDBC Driver 類別名稱
    url                           JDBC URL,注意因資訊寫在xml裡,"&"需改成"&amp;"才符合XML規範

    web.xml裡的設定提供 JNDI 查找時需要的環境資訊
    減少物件建立所需要設置的參數的麻煩

    DatabaseBean 物件裡不會看見資料庫連結的實體位置、連接阜等資訊
    那些資訊由context.xml設定,只須給資料庫管理人員管理

    程式部署後,Tomcat根據放在 META-INF 裡的context.xml設定,尋找指定的JDBC Driver
    所以驅動程式也必須放在程式的Build path裡(如Tomcat的lib資料夾)
    conn.jsp只提供了連線的訊息,資料庫的操作還須依照JDBC的規範來

    2010年7月10日 星期六

    PHP跨資料庫應用淺談

    寫一下目前查資料之後的結果
    當然實際的驗證及比較要先等一段時間後,有空再去把這些都學完

    目前較常拿來做比較的跨資料庫抽象層應用大致上有MDB2、ADOdb、PDO三種

    MDB2的優點在於pear的套件取得上較為容易,開發較不會有客戶端缺少開發環境的問題
    當然套件的相依性會是比較麻煩的議題
    資料庫處理速度不錯,算是各方面都有中上表現的選擇
    詳情可以看MDB2筆記

    ADOdb以微軟的ADO為基底所製作,很多習慣使用ASP的使用者會比較喜歡ADOdb
    它有蠻多強大的功能,卻也因為過於龐大讓資料庫處理速度慢的缺點浮現
    官方網站有提供Lite版本,大小大約是原本的1/6
    不過因為缺少許多強大的ADOdb功能,如果原先有使用那些功能的話還要改變程式的寫法
    官方網站:http://adodb.sourceforge.net/
    熱心人士翻譯的手冊:http://www.php5.idv.tw/documents/ADODB/

    PDO是PHP5以後新增加的功能,也會是以後PHP6預設的資料庫處理方式
    資料庫處理的速度會是目前PHP選擇中最快的
    因為PHP5.3後物件導向方面的變化不小,學習及使用上要更加注意

    2010年7月8日 星期四

    MDB2使用筆記(2)

    上篇介紹了如何建立資料庫的連線
    接著來說說MDB2的query部分,總共有query()和exec()兩種
    成功會回傳結果,失敗則都會回 傳MDB2_Error訊息
    差別在於query()是執行sql指令並取得值
    exec()則是單純的執行指令,適用於 INSERT、UPDATE或DELETE

    query()範例:
    // Proceed with a query
    $res =  $mdb2->query('SELECT * FROM clients');

    // Always check that result is not an error
    if (PEAR::isError($res)) {
        die($res->getMessage());
    }

    exec() 範例:
    $sql  = "INSERT INTO clients (name, address) VALUES ($name, $address)";

    $affected = $mdb2->exec($sql);

    // Always check that result is not an error
    if (PEAR::isError($affected)) {
        die($affected->getMessage());
    }

    另 外在查詢時一樣可以設定查詢的範圍及起點
    $sql = "SELECT * FROM clients";
    $mdb2->setLimit(20, 10);                 //回傳20筆資料,從第10筆資料後開始算起
    $affected =& $mdb2->exec($sql);

    $sql = "DELETE FROM clients";
    if ($mdb2->supports('limit_queries') === 'emulated') {
        echo 'offset will likely be ignored'
    }
    // only delete 10 rows
    $mdb2->setLimit(10);
    $affected =& $mdb2->exec($sql);

    查詢時可以使用quote()提高資料存取的安全性, 例如:
    $query = 'INSERT INTO sometable (textfield1, boolfield2, datefield3) VALUES ('
        .$mdb2->quote($val1, "text", true).', '
        .$mdb2->quote($val2, "boolean", false).', '
        .$mdb2->quote($val3, "date", true).')';

    quote() 函式只有第一個參數-值是需要的,後面的三個參數則是視需求使用
    第二個參數是值的類別,可以排除設定類別外的值
    允許使用的有text, boolean, integer, decimal, float, time stamp, date, time, clob, blob
    第三個參數是是否使用quote(),即用引號包住值,false的話則只是單純的將值輸出
    第四個參數則是使否要脫離萬用字元
    善用quote()可以增加安全性

    針對query出來的結果也可以調整表現的方式,例如:
    $mdb2->setFetchMode(MDB2_FETCHMODE_ASSOC);
    setFetchMode() 有三種屬性
    MDB2_FETCHMODE_ORDERED(例:$result[0])
    MDB2_FETCHMODE_ASSOC(例:$result['name'])
    MDB2_FETCHMODE_OBJECT(例:$result->name)

    取得結果之後,接著就是要用函式來取出值
    fetchOne(), fetchRow(), fetchCol() and fetchAll()這四個函式看起來就很容易懂
    分別是取一個,取一行,取一列,取所有
    此外 numRows(), numCols(), rowCount()等函式也可以獲取結果的其他資訊

    除了以上處理過程外,也有函式可以幫助簡化撰寫,直接取得結果
    就是queryOne(), queryRow(), queryCol() and queryAll()這四個對應的函式

    以範例作比較,使用query():
    $res = $mdb2->query("select ID,name,birthday from member");
    while ($data = $res->fetchRow()) {
      echo $data['ID'].','.$data['name'].','.$data['birthday'];
    }

    queryOne():
    $res = $mdb2->queryOne("select count(*) from member");
    if (!is_null($res)) echo $res;

    queryRow():
    $res = $mdb2->queryRow("select name,birthday from member where ID = 1");
    if (!is_null($res)) echo $res['name'].','.$dbquery['birthday'];

    依此類推,依需求選擇使用的函式

    MDB2尚有預編譯及批量處理Prepare & Execute、moudle等好用功能沒提到
    等之後有用到再去查技術手冊了

    MDB2使用筆記(1)

    來自官方的說明:

    PEAR MDB2 is a merge of the PEAR DB and Metabase php database abstraction layers.

    It provides a common API for all supported RDBMS. The main difference to most  other DB abstraction packages is that MDB2 goes much further to ensure  portability.


    MDB2整合了過去的 PEAR:db函式的新類別庫,為目前PEAR官方所支持
    其有許多選擇性的功能,可以增加對各種資料庫系統間資料的可攜性
    很適合作為跨資料庫的選擇

    MDB2的安裝可以使用套件管理員來進行,詳情可以參考PEAR安裝教學那篇
    或者是到PEAR套件官網下載,下載時要連同所連結的資料庫的驅動一起下載
    以下簡介使用方式,詳細解說可以到官方說明手冊

    首先來個修改自官網的範例:
     <?php
    require_once 'MDB2.php';

    $dsn = array(
        'phptype'  => 'mysqli',             //輸入所要連結的資料庫類別
        'username' => 'user',
        'password' => 'pw',
        'hostspec' => 'localhost',
        'database' => 'DBname',
        'charset' => 'utf8',
    );

    $options = array(
        'debug'       => 2,                                                  //numeric debug level
        'portability' => MDB2_PORTABILITY_ALL,
    );

    $mdb2 =& MDB2::connect($dsn, $options);
    if (PEAR::isError($mdb2)) {
        die($mdb2->getMessage(). ', ' . $mdb2->getDebugInfo() );
    }

    $mdb2->disconnect();
    ?>

    首先要使用PEAR的套件都必須先將使用的檔案include
    dsn是Data Source Name,用來記錄資料庫資訊
    另外,也可以寫成  $dsn = 'mysqli://user:pw@localhost/DBname';
    options當然就是額外的附屬要求
    需要一提的是portability選項
    除非設置成了MDB2_PORTABILITY_NONE,否則query出來的結果都是小寫
    例如結果不可能會有  echo $array['A'];
    官方的預設是小寫,目的是要達到各種資料庫的相容性,當然這也是MDB2的原則

    資料庫的連結方式有三種
    &MDB2::connect 建立MDB2物件並連線資料庫
    &MDB2::factory 建立MDB2物件,但等到要進行資料庫操作時才連線
    &MDB2::singleton 同factory,但它保證只有一個MDB2物件連線到資料庫
    想用哪個就用哪個,一般使用下應是factory效率較高
    最後用disconnect()來結束連線

    連線與結束連線可以以global variable的方式寫成function來增加使用的方便性
    可以參考這篇文章

    官網那邊尚有使用SSL來建立連線的範例,如下:
    <?php
    require_once 'MDB2.php';

    $dsn = array(
        'phptype'  => 'mysqli',
        'username' => 'someuser',
        'password' => 'apasswd',
        'hostspec' => 'localhost',
        'database' => 'thedb',
        'key'      => 'client-key.pem',
        'cert'     => 'client-cert.pem',
        'ca'       => 'cacert.pem',
        'capath'   => '/path/to/ca/dir',
        'cipher'   => 'AES',
    );

    $options = array(
        'ssl' => true,
    );

    // gets an existing instance with the same DSN
    // otherwise create a new instance using MDB2::factory()
    $mdb2 =& MDB2::singleton($dsn, $options);
    if (PEAR::isError($mdb2)) {
        die($mdb2->getMessage());
    }
    ?>