目錄
MySQL C++ Driver的實現基于JDBC4.0規范
安裝MySQL Connector/C++
運行時依賴
C++ IDE
為示例程序創建數據庫與數據表
使用Connector/C++測試數據庫連接
使用prepared Statements
使用事務
訪問Result Set Metadata
訪問Database Metadata
通過PreparedStatment對象訪問參數元數據
捕獲異常
調試/跟蹤 MySQL Connector/C++
更多信息
MySQL C++ Driver的實現基于JDBC4.0規范
MySQL Connector/C++是由Sun Microsystems開發的MySQL連接器。它提供了基于OO的編程接口與數據庫驅動來操作MySQL服務器。
與許多其他現存的C++接口實現不同,Connector/C++遵循了JDBC規范。也就是說,Connector/C++ Driver的API主要是基于Java語言的JDBC接口。JDBC是java語言與各種數據庫連接的標準工業接口。Connector/C++實現了大部分JDBC4.0規范。如果C++程序的開發者很熟悉JDBC編程,將很快的入門。
MySQL Connector/C++實現了下面這些類:
Driver
Connection
Statement
PreparedStatement
ResultSet
Savepoint
DatabaseMetaData
ResultSetMetaData
ParameterMetaData
Connector/C++可用于連接MySQL5.1及其以后版本。
在MySQL Connector/C++發布之前,C++程序員可以使用MySQL C API或者MySQL++訪問MySQL。前者是非標準、過程化的C API,后者是對MySQL C API的C++封裝。
安裝MySQL Connector/C++
此處略。(譯者注:用戶可以到MySQL的官網[http://dev.mysql.com/downloads/connector/cpp/1.0.html]去下載MySQL Connector/C++的安裝程序,或者只下載dll,或者下載源代碼自己編譯。筆者在Window平臺上使用MySQL,下載了mysql-connector-c++-noinstall-1.0.5-win32這個版本用于調試。)
運行時依賴
MySQL Connector/C++ Driver依賴MySQL的客戶端庫,在MySQL安裝目錄下的lib\opt\libmysql.dll。如果是通過安裝程序來安裝MySQL Connector/C++,libmysql會一并安裝,如果從官網只下載了dll或源碼,在使用時,程序必須鏈接到libmysql.dll。
C++ IDE
此處略。(譯者注:原文作者使用NetBean作為C++的IED。筆者使用VS2008)
為示例程序創建數據庫與數據表
(譯者注:此節略掉許多不太重要的內容。)在MySQL中創建test數據庫,使用下面語句創建數據表:City:
Create Table: CREATE TABLE `City` ( `CityName` varchar(30) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=ascii
然后向City表中添加一些數據。最后表的內容為:
mysql>SELECT * FROM City;
+--------------------+
| CityName |
+--------------------+
| Hyderabad, India |
| San Francisco, USA
|
| Sydney, Australia |
+--------------------+
3 rows in set (0.17 sec)
使用Connector/C++測試數據庫連接
下面的代碼演示如何使用Connector/C++連接到MySQL服務器:
連接到test數據庫;
執行一個查詢獲取City表中的數據,顯示在控制臺上;
使用Prepared Statements向City表插入數據;
使用savepoints演示事務;
獲取結果集和數據庫的元信息;
例子代碼僅僅用于演示,不建議讀者在實際開發中使用這種樣式的代碼。(譯者注:例子代碼很長,如果看不太明白,沒關系,等閱讀完全文之后再回過頭來看)
#include
#include
#include
#include
#include "mysql_driver.h"
#include "mysql_connection.h"
#include "cppconn/driver.h"
#include "cppconn/statement.h"
#include "cppconn/prepared_statement.h"
#include "cppconn/metadata.h"
#include "cppconn/exception.h"
#define DBHOST "tcp://127.0.0.1:3306"
#define USER "root"
#define PASSWORD "000000"
#define DATABASE "test"
#define NUMOFFSET 100
#define COLNAME 200
using namespace std;
using namespace sql;
#pragma comment(lib, "mysqlcppconn.lib")
void Demo();
int main(int argc, char *argv[])
{
Demo();
return 0;
}
/* 獲取數據庫信息 */
static void GetDBMetaData(Connection *dbcon)
{
if (dbcon->isClosed())
{
throw runtime_error("DatabaseMetaData FAILURE - database connection closed");
}
cout << "\nDatabase Metadata" << endl;
cout << "-----------------" << endl;
cout << boolalpha;
/* The following commented statement won't work with Connector/C++ 1.0.5 and later */
//auto_ptr < DatabaseMetaData > dbcon_meta (dbcon->getMetaData());
DatabaseMetaData *dbcon_meta = dbcon->getMetaData();
cout << "Database Product Name: " << dbcon_meta->getDatabaseProductName() << endl;
cout << "Database Product Version: " << dbcon_meta->getDatabaseProductVersion() << endl;
cout << "Database User Name: " << dbcon_meta->getUserName() << endl << endl;
cout << "Driver name: " << dbcon_meta->getDriverName() << endl;
cout << "Driver version: " << dbcon_meta->getDriverVersion() << endl << endl;
cout << "Database in Read-Only Mode?: " << dbcon_meta->isReadOnly() << endl;
cout << "Supports Transactions?: " << dbcon_meta->supportsTransactions() << endl;
cout << "Supports DML Transactions only?: " << dbcon_meta->supportsDataManipulationTransactionsOnly() << endl;
cout << "Supports Batch Updates?: " << dbcon_meta->supportsBatchUpdates() << endl;
cout << "Supports Outer Joins?: " << dbcon_meta->supportsOuterJoins() << endl;
cout << "Supports Multiple Transactions?: " << dbcon_meta->supportsMultipleTransactions() << endl;
cout << "Supports Named Parameters?: " << dbcon_meta->supportsNamedParameters() << endl;
cout << "Supports Statement Pooling?: " << dbcon_meta->supportsStatementPooling() << endl;
cout << "Supports Stored Procedures?: " << dbcon_meta->supportsStoredProcedures() << endl;
cout << "Supports Union?: " << dbcon_meta->supportsUnion() << endl << endl;
cout << "Maximum Connections: " << dbcon_meta->getMaxConnections() << endl;
cout << "Maximum Columns per Table: " << dbcon_meta->getMaxColumnsInTable() << endl;
cout << "Maximum Columns per Index: " << dbcon_meta->getMaxColumnsInIndex() << endl;
cout << "Maximum Row Size per Table: " << dbcon_meta->getMaxRowSize() << " bytes" << endl;
cout << "\nDatabase schemas: " << endl;
auto_ptr < ResultSet > rs ( dbcon_meta->getSchemas());
cout << "\nTotal number of schemas = " << rs->rowsCount() << endl;
cout << endl;
int row = 1;
while (rs->next()) {
cout << "\t" << row << ". " << rs->getString("TABLE_SCHEM") << endl;
++row;
} // while
cout << endl << endl;
}
/* 獲取結果集信息 */
static void GetResultDataMetaBata(ResultSet *rs)
{
if (rs -> rowsCount() == 0)
{
throw runtime_error("ResultSetMetaData FAILURE - no records in the result set");
}
cout << "ResultSet Metadata" << endl;
cout << "------------------" << endl;
/* The following commented statement won't work with Connector/C++ 1.0.5 and later */
//auto_ptr < ResultSetMetaData > res_meta ( rs -> getMetaData() );
ResultSetMetaData *res_meta = rs -> getMetaData();
int numcols = res_meta -> getColumnCount();
cout << "\nNumber of columns in the result set = " << numcols << endl << endl;
cout.width(20);
cout << "Column Name/Label";
cout.width(20);
cout << "Column Type";
cout.width(20);
cout << "Column Size" << endl;
for (int i = 0; i < numcols; ++i)
{
cout.width(20);
cout << res_meta -> getColumnLabel (i+1);
cout.width(20);
cout << res_meta -> getColumnTypeName (i+1);
cout.width(20);
cout << res_meta -> getColumnDisplaySize (i+1) << endl << endl;
}
cout << "\nColumn \"" << res_meta -> getColumnLabel(1);
cout << "\" belongs to the Table: \"" << res_meta -> getTableName(1);
cout << "\" which belongs to the Schema: \"" << res_meta -> getSchemaName(1) << "\"" << endl << endl;
}
/* 打印結果集中的數據 */
static void RetrieveDataAndPrint(ResultSet *rs, int type, int colidx, string colname)
{
/* retrieve the row count in the result set */
cout << "\nRetrieved " << rs->rowsCount() << " row(s)." << endl;
cout << "\nCityName" << endl;
cout << "--------" << endl;
/* fetch the data : retrieve all the rows in the result set */
while (rs->next())
{
if (type == NUMOFFSET)
{
cout << rs -> getString(colidx) << endl;
} else if (type == COLNAME)
{
cout << rs -> getString(colname) << endl;
} // if-else
} // while
cout << endl;
}
void Demo()
{
Driver *driver;
Connection *con;
Statement *stmt;
ResultSet *res;
PreparedStatement *prep_stmt;
Savepoint *savept;
int updatecount = 0;
/* initiate url, user, password and database variables */
string url(DBHOST);
const string user(USER);
const string password(PASSWORD);
const string database(DATABASE);
try
{
driver = get_driver_instance();
/* create a database connection using the Driver */
con = driver -> connect(url, user, password);
/* alternate syntax using auto_ptr to create the db connection */
//auto_ptr con (driver -> connect(url, user, password));
/* turn off the autocommit */
con -> setAutoCommit(0);
cout << "\nDatabase connection\'s autocommit mode = " << con -> getAutoCommit() << endl;
/* select appropriate database schema */
con -> setSchema(database);
/* retrieve and display the database metadata */
GetDBMetaData(con);
/* create a statement object */
stmt = con -> createStatement();
cout << "Executing the Query: \"SELECT * FROM City\" .." << endl;
/* run a query which returns exactly one result set */
res = stmt -> executeQuery ("SELECT * FROM City");
cout << "Retrieving the result set .." << endl;
/* retrieve the data from the result set and display on stdout */
RetrieveDataAndPrint (res, NUMOFFSET, 1, string("CityName"));
/* retrieve and display the result set metadata */
GetResultDataMetaBata (res);
cout << "Demonstrating Prepared Statements .. " << endl << endl;
/* insert couple of rows of data into City table using Prepared Statements */
prep_stmt = con -> prepareStatement ("INSERT INTO City (CityName) VALUES (?)");
cout << "\tInserting \"London, UK\" into the table, City .." << endl;
prep_stmt -> setString (1, "London, UK");
updatecount = prep_stmt -> executeUpdate();
cout << "\tCreating a save point \"SAVEPT1\" .." << endl;
savept = con -> setSavepoint ("SAVEPT1");
cout << "\tInserting \"Paris, France\" into the table, City .." << endl;
prep_stmt -> setString (1, "Paris, France");
updatecount = prep_stmt -> executeUpdate();
cout << "\tRolling back until the last save point \"SAVEPT1\" .." << endl;
con -> rollback (savept);
con -> releaseSavepoint (savept);
cout << "\tCommitting outstanding updates to the database .." << endl;
con -> commit();
cout << "\nQuerying the City table again .." << endl;
/* re-use result set object */
res = NULL;
res = stmt -> executeQuery ("SELECT * FROM City");
/* retrieve the data from the result set and display on stdout */
RetrieveDataAndPrint(res, COLNAME, 1, string ("CityName"));
cout << "Cleaning up the resources .." << endl;
/* Clean up */
delete res;
delete stmt;
delete prep_stmt;
con -> close();
delete con;
} catch (SQLException &e) {
cout << "ERROR: " << e.what();
cout << " (MySQL error code: " << e.getErrorCode();
cout << ", SQLState: " << e.getSQLState() << ")" << endl;
if (e.getErrorCode() == 1047) {
/*
Error: 1047 SQLSTATE: 08S01 (ER_UNKNOWN_COM_ERROR)
Message: Unknown command
*/
cout << "\nYour server does not seem to support Prepared Statements at all. ";
cout << "Perhaps MYSQL < 4.1?" << endl;
}
return;
} catch (std::runtime_error &e) {
cout << "ERROR: " << e.what() << endl;
return;
}
return;
}
建立數據庫連接
sql::Connection代表到數據庫的連接,可以通過sql::Driver來創建。sql::mysql::get_mysql_driver_instance()方法用于獲取sql::Driver,通過調用sql::Driver::connect方法來創建sql::Connection對象。(譯者注:筆者使用的Connector/C++版本與作者使用的版本不一樣,接口方面也有點細微的差別。這里根據筆者使用的最新版本mysql-connector-c++-noinstall-1.0.5-win32來說明。)
下面是get_mysql_driver_instance與connect這兩個方法的簽名:
/* mysql_driver.h */
MySQL_Driver *sql::mysql::get_mysql_driver_instance()
/* mysql_driver.h */
sql::Connection * connect(const std::string& hostName, const std::string& userName, const std::string& password);
sql::Connection * connect(std::map<:string sql::connectpropertyval> & options);
Driver類重載了connect方法,一個接收數據庫地址的url、用戶名和密碼的字符串,后一個接收一個map,map中以key/value的形式包含數據庫地址、用戶名與密碼。使用TCP/IP連接到MySql服務器的url字符串的格式如下:"tcp://[hostname[:port]][/schemaname]"。例如:tcp://127.0.0.1:5555/some_scehma。hostname和端口號是可選的,如果省略,默認是127.0.0.1與3306。如果hostname為"localhost",會被自動轉換為"127.0.0.1"。schemaname也是可選的,如果連接字符串中沒有設置schema,需要在程序中通過Connection::setSchema方法手動設置。
在unix系統上,可以通過UNIX domain socket連接運行在本地的MySQL服務,連接字符串格式為:"unix://path/to/unix_socket_file",例如:unix:///tmp/mysql.sock.
在windows系統上,可以以命名管道的方式連接到運行在本地的MySQL數據庫,連接字符串格式為:"pipe://path/to/the/pipe"。MySQL服務必須啟動允許命名管道連接,可以在啟動MySQL服務器的時候,通過--enable-named-pipe命令行選項來啟動該功能。如果沒有通過--socket=name選項設置命名管道的名稱,系統默認使用MySQL。在windows上,管道的名稱是區別大小寫的。
下面的代碼片斷嘗試連接到本地的MySQL服務器,通過3306端口,用戶名為root,密碼是000000,schema為test.
sql::mysql::MySQL_Driver *driver = 0;
sql::Connection *conn = 0;
try
{
driver = sql::mysql::get_mysql_driver_instance();
conn = driver->connect("tcp://localhost:3306/test", "root", "000000");
cout << "連接成功" << endl;
}
catch (...)
{
cout << "連接失敗" << endl;
}
if (conn != 0)
{
delete conn;
}
也可以通過connection的第二個重載方法連接MySQL。ConnectPropertyVal是union類型,在connection.h中定義。
sql::mysql::MySQL_Driver *driver = 0;
sql::Connection *conn = 0;
std::map<:string connectpropertyval> connProperties;
ConnectPropertyVal tmp;
tmp.str.val = "tcp://127.0.0.1:3306/test";
connProperties[std::string("hostName")] = tmp;
tmp.str.val = "root";
connProperties[std::string("userName")] = tmp;
tmp.str.val = "000000";
connProperties[std::string("password")] = tmp;
try
{
driver = sql::mysql::get_mysql_driver_instance();
conn = driver -> connect(connProperties);
cout << "連接成功" << endl;
}
catch(...)
{
cout << "連接失敗" << endl;
}
if (conn != 0)
{
delete conn;
}
上面的連接字符串可以將協議與路徑分開寫(譯者注:C++會把兩個連在一起的字符串合并成一個字符串),如:mp.str.val = "unix://" "/tmp/mysql.sock"
當建立與服務器之間的連接后,通過Connection::setSessionVariable方法可以設置像sql_mode這樣的選項。
C++細節注意點
像Connection這樣的對象,必須在用完之后,顯式的delete,例如:
sql::Connection *conn = driver -> connect("tcp://127.0.0.1:3306", "root", "000000");
// do something
delete conn
使用使用auto_ptr來維護連接對象的清理, 如:
use namespace std;
use namespace sql;
auto_ptr < Connection > con ( driver -> connect("tcp://127.0.0.1:3306", "root", "000000") );
獲取Statement對象
Statement對象用于向MySQL服務器發送SQL語句。該對象可以通過調用Connection::createStatement方法獲得。Statement向MySQL發送一個靜態的SQL語句,然后從MySQL獲取操作的結果,我們無法向它提供sql參數。如果要向它傳遞參數,可以使用PreparedStatemenet類。如果相同的SQL語句(只SQL參數不同)要被執行多次,建議使用PreparedStatement類。
Connection::createStatement的簽名如下(關于Connection類所提供的方法列表,可以查看connection.h頭文件):
/* connection.h */
Statement* Connection::createStatement();
下面的的代碼段通過調用Connection對象的createStatemenet來獲取一個Statement對象:
Connection *conn; // Connection對象的引用
Statement *stat;
Statement stat = conn -> createStatement();
執行SQL語句
在執行SQL語句之前應該通過Connection對象的setSchema方法設置相應的Schema(如果沒有在數據庫地址URL中指定schema)。
Statement::executeQuery用于執行一個Select語句,它返回ResultSet對象。Statement::executeUpdate方法主要用于執行INSERT, UPDATE, DELETE語句(executeUpdate可以執行所有的SQL語句,如DDL語句,像創建數據表。),該方法返回受影響記錄的條數。
如果你不清楚要執行的是像select這樣的查詢語句還是像update/insert/delete這樣的操作語句,可以使用execute方法。對于查詢語句,execute()返回True,然后通過getResultSet方法獲取查詢的結果;對于操作語句,它返回False,通過getUpdateCount方法獲取受影響記錄的數量。
在一些特殊的情況下,單條SQL語句(如執行存儲過程),可能會返回多個結果集 和/或 受影響的記錄數量。如果你不想忽略這些結果,通過getResultSet或getUpdateCount方法第一個結果后,再通過getMoreResults()來獲取其他的結果集。
下面是這些方法的簽名,可以在statement.h頭文件中查閱Statement的完整方法列表。
/* connection.h */
void Connection::setSchema(const std::string& catalog);
/* statement.h */
ResultSet* Statement::executeQuery (const std::string& sql);
int Statement::executeUpdate (const std::string& sql);
bool Statement::execute (const std::string& sql);
ResultSet* Statement::getResultSet();
uint64_t Statement::getUpdateCount();
這些方法出錯時都會拋出SQLException異常,所以在你的代碼中應該使用try...catch語句塊來捕獲這些異常。
現在回顧上面那個完全的例子,你會發現獲取City表的所有記錄是如此的簡單:
Statement *stmt;
ResultSet *res;
res = stmt -> executeQuery ("SELECT * FROM City");
executeQuery方法返回ResultSet對象,它包含了查詢的結果。在以下情況下,executeQuery會拋出SQLException異常:數據庫在執行查詢時出錯;在一個關閉的Statement對象上調用executeQuery;給出的SQL語句返回的不是一個簡單的結果集;
上面的代碼可以用Statement::execute()重寫:
bool retvalue = stmt -> execute ("SELECT * FROM City");
if (retvalue)
{
res = stmt -> getResultSet();
}
else
{
...
}
execute()返回True表示操作的結果是一個ResultSet對象,否則結果是受影響記錄的數量或沒有結果。當返回True時,通過getResultSet方法獲取結果集,在返回False的情況下調用getResultSet方法,將返回NULL。
當數據庫在執行時發生錯誤或者在一個已關閉的Statement對象上執行execute與getResultSet方法,都會拋出SQLException異常。
如果要往數據庫里添加一條新的記錄,可以像下面的例子一樣簡單的調用executeUpdate方法:
int updateCount = stmt -> executeUpdate ("INSERT INTO City (CityName) VALUES ('Napier, New Zealand')");
如果executeUpdate執行的是像INSERT, UPDATE或DELETE這樣的數據操作語句(DML),它返回受影響的記錄的數量;如果執行的是數據定義語句(DDL),它返回0。在數據庫操作失敗,或者在一個已經關閉的Statement上調用該方法,或者給出的SQL語句是一個查詢語句(會返回結果集),該方法會拋出SQLException異常。
下面的代碼使用execute和getUpdateCount方法來生寫上面的例子:
int updateCount = 0;
bool retstatus = stat->execute("INSERT INTO City (CityName) VALUES ('Napier, New Zealand')");
if (!retstatus)
{
updateCount = stat->getUpdateCount();
}
else
{
...
}
從ResultData中獲取數據
上面的段落介紹了執行SQL查詢的方法:executeQuery和execute,用于獲取ResultSet對象。我們可以通過ResultSet訪問查詢的結果。每一個ResultSet都包含一個游標(cursor),它指向數據集中的當前記錄行。ResultSet中排列的記錄是有序的(譯者注:只能按順序一條一條獲取,不能跳躍式獲取)。(但)在同一行中,列值的訪問卻是隨意的:可以通過列的位置或者名稱。通過列的名稱訪問列值讓代碼更清晰,而通過位置訪問列值則更高效。
列的名稱通過SQL語句的AS子名設定,如果SQL語句中沒有使用AS子名,列的名稱默認為數據表中對應的列名。例如對于"SELECT CityName AS CN FROM City",CN就是結果集中列的名稱。
在ResultSet中的數據,可以通過getXX系列方法來獲取,例如:getString(), getInt(),"XX"取決于數據的類型。next()與previous()使游標移到結果集中的下一條或上一條記錄。
Statement執行SQL語句返回ResultSet對象后,ResultSet就變成一個獨立的對象,與原先的Statement再也沒有聯系,即使Statement對象關閉,重新執行其他sql語句,或者獲取多個結果集中的下一個。ResultSet將一直有效,除非顯式或隱式地將其關閉。
在撰寫本文時,對于Statement對象,MySQL Connector/C++總是返回緩存結果,這些結果在客戶端緩存。不管結果集數據量大小,MySQLConnector/C++ Driver總是獲取所有的數據。希望以后的版本中,Statement對象能夠返回緩存和非緩存的結果集。
下面是數據獲取方法的簽名,可以在resultset.h頭文件中查看所有ResultSet類支持的方法。
/* resultset.h */
size_t ResultSet::rowsCount() const;
void ResultSet::close();
bool ResultSet::next();
bool ResultSet::previous();
bool ResultSet::last();
bool ResultSet::first();
void ResultSet::afterLast();
void ResultSet::beforeFirst();
bool ResultSet::isAfterLast() const;
bool ResultSet::isBeforeFirst()const;
bool ResultSet::isClosed() const;
bool ResultSet::isNull(uint32_t columnIndex) const;
bool ResultSet::isNull(const std::string& columnLabel) const;
bool ResultSet::wasNull() const;
std::string ResultSet::getString(uint32_t columnIndex) const;
std::string ResultSet::getString(const std::string& columnLabel) const;
int32_t ResultSet::getInt(uint32_t columnIndex) const;
int32_t ResultSet::getInt(const std::string& columnLabel) const;
在下面的簡單示例中,查詢語句"SELECT * FROM City"返回的ResultSet中只包含一列:CityName,數據類型為String,對應MySQL中的VARCHAR類型。這個例子通過next方法循環從結果集中獲取CityName值,并顯示在控制臺上:
while (res -> next())
{
cout << rs -> getString("CityName") << endl;
}
也可以通過位置來獲取列值(位置從1開始而非從0開始),下面的代碼產生相同的結果:
while (res -> next())
{
cout << rs -> getString(1) << endl;
}
如果數據庫中該字段的值為NULL,getString將返回一個空的字符串。Result::isNull用于判斷指定列在數據庫中的值是否為NULL。Result::wasNULL()用于判斷最近讀取的列的值是否為空。
下面的例子演示了通過cursor(游標)倒序讀取結果集中的數據:
/* Move the cursor to the end of the ResultSet object, just after the last row */
res -> afterLast();
if (!res -> isAfterLast())
{
throw runtime_error("Error: Cursor position should be at the end of the result set after the last row.");
}
/* fetch the data : retrieve all the rows in the result set */
while (res -> previous())
{
cout << rs->getString("CityName") << endl;
}
getString方法在以下情況下會拋出SQLException異常:指定列名或位置不存在;數據庫在執行操作時失敗;在一個關閉的cursor上執行調用該方法。
未完待續!