首先關於資料庫的開啟可考慮兩種方式
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月5日 星期四
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();
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者的選擇上是有差異的。
引用微軟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();
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){
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 會是效能與安全性考量的好選擇
資料庫廠商依據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"?>
<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&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裡,"&"需改成"&"才符合XML規範
web.xml裡的設定提供 JNDI 查找時需要的環境資訊
減少物件建立所需要設置的參數的麻煩
DatabaseBean 物件裡不會看見資料庫連結的實體位置、連接阜等資訊
那些資訊由context.xml設定,只須給資料庫管理人員管理
程式部署後,Tomcat根據放在 META-INF 裡的context.xml設定,尋找指定的JDBC Driver
所以驅動程式也必須放在程式的Build path裡(如Tomcat的lib資料夾)
conn.jsp只提供了連線的訊息,資料庫的操作還須依照JDBC的規範來
過程中的耗費在單機使用者上也許感覺不太出來
但放大到使用者眾多的網路應用程式,開開關關的連線就是不可小看的浪費
改善連線效能的方法之一就是建立儲存了連線實例的連接池
盡量使用已經開啟的連線,重複使用以減低資源上的浪費
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&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裡,"&"需改成"&"才符合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後物件導向方面的變化不小,學習及使用上要更加注意
當然實際的驗證及比較要先等一段時間後,有空再去把這些都學完
目前較常拿來做比較的跨資料庫抽象層應用大致上有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的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());
}
?>
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());
}
?>
訂閱:
文章 (Atom)