MySQL Connector/C++入門教程(上)
阿新 • • 發佈:2019-02-05
翻譯: DarkBull(www.darkbull.net)
譯者注:該教程是一篇介紹如何使用C++操作MySQL的入門教程,內容簡單易用。我對原文中的一些例子進行了修改,並新添加了部分例子,主要目標是更簡單明瞭的向讀者介紹如何操作MySQL資料庫。本人也是MySQL的初學者,錯誤也在所難免,歡迎拍磚!
這篇教程將一步一步引導您如何去構建和安裝MySql Connection/C++ Driver,同時提供幾個簡單的例子來演示如何連線MySQL資料庫,如何向MySQL新增、獲取資料。本教程關注如何在C++應用程式中操作MySQL,所以首先應該確定MySQL資料庫服務已經開啟並且在當前機器能夠訪問到。
本教程面向的讀者是MySQL Connector/C++的初學者,如果您對C++語言或者MySQL資料庫不是很瞭解,請參考其他的教程。 教程使用了下面所列的一些工具和技術,來構建、編譯、執行例子程式(譯者注:這是原文作者使用的環境。筆者使用的環境是:WinXP,MySQL5.1,VS2008, ):
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++實現了下面這些類:
在MySQL Connector/C++釋出之前,C++程式設計師可以使用MySQL C API或者MySQL++訪問MySQL。前者是非標準、過程化的C API,後者是對MySQL C API的C++封裝。
mysql> SELECT * FROM City; +--------------------+ | CityName | +--------------------+ | Hyderabad, India | | San Francisco, USA | | Sydney, Australia | +--------------------+ 3 rows in set (0.17 sec)
下面的程式碼片斷嘗試連線到本地的MySQL伺服器,通過3306埠,使用者名稱為root,密碼是000000,schema為test.
Connection::createStatement的簽名如下(關於Connection類所提供的方法列表,可以檢視connection.h標頭檔案):
在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類支援的方法。
本教程面向的讀者是MySQL Connector/C++的初學者,如果您對C++語言或者MySQL資料庫不是很瞭解,請參考其他的教程。 教程使用了下面所列的一些工具和技術,來構建、編譯、執行例子程式(譯者注:這是原文作者使用的環境。筆者使用的環境是:WinXP,MySQL5.1,VS2008, ):
- DatabaseMySQL Server 5.1.24-rc
- C++ DriverMySQL Connector/C++ 1.0.5
- MySQL Client LibraryMySQL Connector/C 6.0
- CompilerSun Studio 12 C++ compiler
- MakeCMake 2.6.3
- Operating SystemOpenSolaris 2008.11 32-bit
- CPU / ISAIntel Centrino / x86
- HardwareToshiba Tecra M2 Laptop
目錄
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規範
- Driver
- Connection
- Statement
- PreparedStatement
- ResultSet
- Savepoint
- DatabaseMetaData
- ResultSetMetaData
- ParameterMetaData
在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:- CreateTable: CREATETABLE `City` ( `CityName` varchar(30) DEFAULTNULL ) ENGINE=InnoDB DEFAULT CHARSET=ascii
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 <iostream>
- #include <map>
- #include <string>
- #include <memory>
- #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
- usingnamespace std;
- usingnamespace sql;
- #pragma comment(lib, "mysqlcppconn.lib")
- void Demo();
- int main(int argc, char *argv[])
- {
- Demo();
- return 0;
- }
- /* 獲取資料庫資訊 */
- staticvoid 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;
- }
- /* 獲取結果集資訊 */
- staticvoid 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;
- }
- /* 列印結果集中的資料 */
- staticvoid 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;
- } elseif (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<std::string, sql::ConnectPropertyVal> & options);
下面的程式碼片斷嘗試連線到本地的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;
- }
- sql::mysql::MySQL_Driver *driver = 0;
- sql::Connection *conn = 0;
- std::map<std::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++細節注意點
像Connection這樣的物件,必須在用完之後,顯式的delete,例如:- sql::Connection *conn = driver -> connect("tcp://127.0.0.1:3306", "root", "000000");
- // do something
- delete conn
- 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 */
- ment* Connection::createStatement();
- 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();
- Statement *stmt;
- ResultSet *res;
- res = stmt -> executeQuery ("SELECT * FROM City");
- bool retvalue = stmt -> execute ("SELECT * FROM City");
- if (retvalue)
- {
- res = stmt -> getResultSet();
- }
- else
- {
- ...
- }
- int updateCount = stmt -> executeUpdate ("INSERT INTO City (CityName) VALUES ('Napier, New Zealand')");
- 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;
- while (res -> next())
- cout << rs -> getString("CityName") << endl;
也可以通過位置來獲取列值(位置從1開始而非從0開始),下面的程式碼產生相同的結果:
- while (res -> next())
- cout << rs -> getString(1) << endl;
- /* 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上執行呼叫該方法。