代码改变世界

MySQL Connector/C++(一)

2013-04-18 21:06  DVwei  阅读(6372)  评论(0编辑  收藏  举报

英文原文:http://dev.mysql.com/tech-resources/articles/mysql-connector-cpp.html#trx

如果我翻译错了或者翻译的不好,欢迎指正~


Developing Database Applications Using MySQL Connector/C++

这个教程会教你搭建安装MySQL Connector/C++ driver的要点和步骤,以一个简单的连接MySQL的例子,从MySQL中获取数据并对其进行插入(数据)操作。因为重点在于从C++程序连接数据库,所以本文档假设MySQL已经运行并且能从客户端访问。

本文是面向那些初次接触MySQL Connector/C++的程序开发者的教程,并不是讲述C++编程和MySQL数据库的。

构建和运行本教程的例子需要下列工具和技术来编译:

Database MySQL Server 5.1.24-rc
C++ Driver MySQL Connector/C++ 1.0.5
MySQL Client Library MySQL Connector/C 6.0
Compiler Sun Studio 12 C++ compiler
Make CMake 2.6.3
Operating System OpenSolaris 2008.11 32-bit
CPU / ISA Intel Centrino / x86
Hardware Toshiba Tecra M2 Laptop

CONTENTS

MySQL C++ Driver 基于JDBC 4.0标准实现

MySQL Connector/C++是最新发布的MySQL连接器,由Sun Microsystems开发。MySQL connector为C++提供面向对象的编程接口(API)和连接MySQL Server的数据库驱动器

与现存的driver不同,Connector/C++是JDBC API在C++中的实现。换句话说,Connector/C++ driver的接口主要是基于Java语言的JDBC API。Java数据库连接(JDBC)是Java连接各种数据库的业界标准。Connector/C++实现了JDBC 4.0的大部分规范。熟悉JDBC编程的C++程序开发者可以提高程序开发的效率。

MySQL Connecotr/C++实现了以下类:

  • Driver
  • Connection
  • Statement
  • PreparedStatement
  • ResultSet
  • Savepoint
  • DatabaseMetaData
  • ResultSetMetaData
  • ParameterMetaData

Connecotr/C++ driver可用于连接MySQL5.1以及后续版本。

在MySQL Connector/C++出现之前,C++程序员需要使用非标准的、过程化的MySQL C API或MySQL++ API连接MySQL,MySQL Connector/C++是MySQL C API的C++封装

安装MySQL Connector/C++

从二进制程序安装

从1.0.4版本开始,Connector/C++可用于Solaris, Linux, Windows, FreeBSD, Mac OS X, HP-UX and AIX平台。MSI安装程序和二进制ZIP文件并不需要安装程序可用于Windows,GNU TAR的压缩文档(tar.gz)可用于其他的平台。你可以从“Connector/C++ download“下载预编译的二进制文件。

在Windows和其他平台下二进制包的安装是非常简单的-简单的解压缩文档到指定位置安装Connector/C++ driver。静态链接和动态链接的Connector/C++ driver可以在安装目录下找到lib目录。如果您打算使用动态链接版本的MySQL连接器/ C + +,要确保运行时链接程序可以找到MySQL客户端库。请查阅你操作系统文档,修改和扩展库的搜索路径。如果你无法修改库的搜索路径,那么复制你的程序、MySQL Connector/C++ driver和MySQL客户端库到相同的目录。这种方法在大多数平台都可行,编译器到其他地方都所必须动态库之前会先搜索原始目录(当前目录)。

从源码安装

如果需要从源码编译安装,请查看“Installing MySQL Connector/C++ from Source“页面的详细介绍。

运行时依赖

略(英语水平有限,这一段翻译的不通畅,就不写了,于阅读本教程没什么影响) 

使用IDE开发C++程序

如果你正在寻找一个集成开发环境(IDE)来开发C/C++程序,可以考虑使用开源免费的NetBeans平台。NetBeans C/C++开发包让程序员可以使用他们指定的编译器和工具来搭配NetBeans IDE,构建Solaris,Linux,Windows和Mac OS X的原生应用。C/C++开发包使编辑器具有语言识别功能,可以识别C/C++语言,并且提供项目模板,一个动态类浏览器,支持Makefile和m(不知道是什么)调试器功能。可以通过模块和插件来扩展C/C++开发包的基础功能。

MySQL Connector/C++: How to Build a Client using NetBeans 6.5 (for Dummies) 教程介绍了如何使用NetBeans IDE来构建基于Connector/C++的客户端程序。除了上面的教程,在NetBeans.org网站上的Installing and Configuring C/C++ Support (安装和配置支持)教程可以帮助你安装和配置C/C++开发包,并且”Getting Started With the NetBeans C/C++ Development Pack“提供了使用NetBeans C/C++开发包参与开发一个C/C++程序的基本步骤

为实例代码创建City表和test数据库

本教程中的简单示例代码尝试获取MySQL中的test数据库中的City表的数据。此表的结构和数据已经使用mysql客户端显示在下面。MySQL服务运行在3306默认端口。

# mysql -u root -p
Enter password: admin
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 5
Server version: 5.1.24-rc-standard Source distribution

Type 'help;' or '\h' for help. Type '\c' to clear the buffer.

mysql> USE test;
Database changed

mysql> DESCRIBE City;
+----------+-------------+------+-----+---------+-------+
| Field    | Type        | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| CityName | varchar(30) | YES  |       | NULL  |       |
+----------+-------------+------+-----+---------+-------+
1 row in set (0.07 sec)

mysql> SHOW CREATE TABLE City\G
*************************** 1. row ***************************
       Table: City
Create Table: CREATE TABLE `City` (
  `CityName` varchar(30) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=ascii
1 row in set (0.00 sec)

mysql> SELECT * FROM City;

+--------------------+
| CityName           |
+--------------------+
| Hyderabad, India   |
| San Francisco, USA |
| Sydney, Australia  |
+--------------------+
3 rows in set (0.17 sec)

本教程的所有例子的运行结果都是使用bash shell显示

使用Connector/C++测试与数据库连接

下面的C++示例代码简单演示如何使用MySQL Connector/C++连接本机的MySQL服务。实例代码使用Connector/C++提供的类似于JDBC的API连接到MySQL中的test数据库,实行一条查询语句,从City表中获取所有行数据,从结果集中提取数据并显示在标准输出上,使用"Prepared Statements"插入几行数据到City表中。

示例代码仅仅供参考。不建议读者使用特定的编码风格。为了简单起见,实例代码假设用户提供了合法的输入,所以下面例子中并没有明确的错误检测代码。

# cat MySQLConnectorC++Client.cpp

/* Standard C++ headers */
#include <iostream>
#include <sstream>
#include <memory>
#include <string>
#include <stdexcept>

/* MySQL Connector/C++ specific headers */
#include <driver.h>
#include <connection.h>
#include <statement.h>
#include <prepared_statement.h>
#include <resultset.h>
#include <metadata.h>
#include <resultset_metadata.h>
#include <exception.h>
#include <warning.h>
    
#define DBHOST "tcp://127.0.0.1:3306"
#define USER "root"
#define PASSWORD "admin"
#define DATABASE "test"

#define NUMOFFSET 100
#define COLNAME 200

using namespace std;
using namespace sql;

static void retrieve_data_and_print (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;

} // retrieve_data_and_print()

static void retrieve_dbmetadata_and_print (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;

} // retrieve_dbmetadata_and_print()

static void retrieve_rsmetadata_and_print (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;

} // retrieve_rsmetadata_and_print()


int main(int argc, const char *argv[]) {

    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(argc >= 2 ? argv[1] : DBHOST);
    const string user(argc >= 3 ? argv[2] : USER);
    const string password(argc >= 4 ? argv[3] : PASSWORD);
    const string database(argc >= 5 ? argv[4] : 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 */
        retrieve_dbmetadata_and_print (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 */
        retrieve_data_and_print (res, NUMOFFSET, 1, string("CityName"));

        /* retrieve and display the result set metadata */
        retrieve_rsmetadata_and_print (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 */
        retrieve_data_and_print (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: SQLException in " << __FILE__;
        cout << " (" << __func__<< ") on line " << __LINE__ << endl;
        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 EXIT_FAILURE;
    } catch (std::runtime_error &e) {

        cout << "ERROR: runtime_error in " << __FILE__;
        cout << " (" << __func__ << ") on line " << __LINE__ << endl;
        cout << "ERROR: " << e.what() << endl;

        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
} // main()
# CC -V
CC: Sun C++ 5.9 SunOS_i386 Patch 124864-09 2008/12/16

# CC -o mysqlconnectorc++client -g0 -xO4 -features=extensions -I/opt/coolstack/mysql_32bit/include/mysql \
     -I/export/expts/MySQLConnectorC++/include/cppconn -L/opt/coolstack/mysql_32bit/lib/mysql \
     -L/export/expts/MySQLConnectorC++/lib -lmysqlclient_r -lmysqlcppconn MySQLConnectorC++Client.cpp

# export LD_LIBRARY_PATH=/opt/coolstack/mysql_32bit/lib/mysql:/export/expts/ConnectorC++/lib/:$LD_LIBRARY_PATH

# ./mysqlconnectorc++client localhost root admin test

Database connection's autocommit mode = 0

Database Metadata
-----------------
Database Product Name: MySQL
Database Product Version: 5.1.24-rc-standard
Database User Name: root@localhost

Driver name: MySQL Connector/C++
Driver version: 1.0.5

Database in Read-Only Mode?: false
Supports Transactions?: true
Supports DML Transactions only?: false
Supports Batch Updates?: true
Supports Outer Joins?: true
Supports Multiple Transactions?: true
Supports Named Parameters?: false
Supports Statement Pooling?: false
Supports Stored Procedures?: true
Supports Union?: true

Maximum Connections: 151
Maximum Columns per Table: 512
Maximum Columns per Index: 16
Maximum Row Size per Table: 2147483639 bytes

Database schemas: 

Total number of schemas = 4

    1. information_schema
    2. ISVe
    3. mysql
    4. test


Executing the Query: "SELECT * FROM City" ..
Retrieving the result set ..

Retrieved 3 row(s).

CityName
--------
Hyderabad, India
San Francisco, USA
Sydney, Australia

ResultSet Metadata
------------------

Number of columns in the result set = 1

   Column Name/Label         Column Type         Column Size
            CityName             VARCHAR                  30


Column "CityName" belongs to the Table: "City" which belongs to the Schema: "test"

Demonstrating Prepared Statements .. 

    Inserting "London, UK" into the table, City ..
    Creating a save point "SAVEPT1" ..
    Inserting "Paris, France" into the table, City ..
    Rolling back until the last save point "SAVEPT1" ..
    Committing outstanding updates to the database ..

Querying the City table again ..

Retrieved 4 row(s).

CityName
--------
Hyderabad, India
San Francisco, USA
Sydney, Australia
London, UK

Cleaning up the resources ..

 

上述示例代码的一些重要步骤说明如下:

建立一个连接到MySQL Server

通过sql::Driver对象实例的sql::Connection成员与MySQL建立连接,sql::Driver对象通过sql::Driver::get_driver_instance函数的返回值设定,而sql::Driver::connect函数返回一个sql::Connection对象。
 
注:

上一段使用的<namespace>::<classname>::<methodname>符号为完全限定的函数名。例如,在sql::Driver::get_driver_instance()中,sql是名称空间,Driver是类名以及get_driver_instance()是函数名。在C++程序中如此使用Connector/C++,你可以在代码头中使用"using namespace sql;"指令,这样每次使用相应声明的时候就不需要用“sql::”前缀。所以本教程其余部分使用“sql”名称空间中的函数或其他成员时,都省略了“sql”名称空间的前缀,这样会更简单清晰。

下面是Driver::get_driver_instance和Driver::connect函数的签名。你可以通过查看安装程序中的driver.h头文件获取完整的函数列表。

 

/* driver.h */
Driver* Driver::get_driver_instance()

Connection* Driver::connect(const std::string& URL, const std::string& userName, const std::string& password)
Connection* Driver::connect(std::map<std::string, ConnectPropertyVal> properties)

connect在Driver类中是一个重载函数。这里有两个重载版本。在第一个版本中,connect接受一个数据库连接URL以及数据库的用户名和密码。第二个版本中,connector接受一个std::map对象,此对象是包含了数据库连接的URL,用户名和密码的键值对。

你可以在连接URL中指定TCP/IP,“tcp://[hostname[:port]][/schemaname]"的形式连接到MySQL。例如:tcp://127.0.0.1:5555/some_schema. hostname和port是可选的,默认使用127.0.0.1和3306.运行时,"localhost"会自动转换成127.0.0.1。是否指定schema name 也是可选的,如果没设置,则必须使用Connector::setSchema函数来设置schema。

如果你要使用UNIX域的套接口来连接本机的MySQL Server,需在数据库连接URL中指定"unix://path/to/unix_socket_file"。例如:unix://tmp/mysql.sock. 在Windwos中,你可以使用命名管道来连接MySQL Server,需要在数据库连接URL中指定字符串"pipe://path/to/the/pipe"。要启用管道支持,必须在启动MySQL时加上--enable-named-pipe选项。如果你没有使用--socket=name选项指定管道名称,那么会默认创建名为MySQL的命名管道。在Microsoft Windows中,name不区分大小写。

下列代码尝试连接到本地的MySQL Server默认端口:3306,使用root用户名,密码为admin,schema名称为test.

using namespace sql;

Driver *driver;
Connection *con;

try {

    driver = get_driver_instance();
    con = driver -> connect("tcp://127.0.0.1:3306/test", "root", "admin");

} catch (..) {
    ..
}

 

connect也可以像下面这样使用,调用第二个重载版本。ConnectPropertyVal是一个枚举类型,定义在connection.h头文件中。编译此段代码需要包含map头文件。

..
std::map conn_properties;
ConnectPropertyVal tmp;

tmp.str.val = "unix:///tmp/mysql.sock";
conn_properties [std::string("hostName")] = tmp;

tmp.str.val = "root";
conn_properties [std::string("userName")] = tmp;

tmp.str.val = "admin";
conn_properties [std::string("password")] = tmp;

try {

    driver = get_driver_instance();
    con = driver -> connect(conn_properties);

} catch(..) {
    ..
}

 

如果你更喜欢使用指定UNIX套接口协议,可以重写上述代码的数据库连接URL,如下所示:

            tmp.str.val = "unix://" "/tmp/mysql.sock";

一旦建立了连接,你就可以使用Connection::setSessionVariable函数来设置变量,如sql_mode。

C++ Specific Note

"sql::Connection *con = driver -> connect("tcp://127.0.0.1:3306", "root", "admin");”语句可以使用auto_ptr来重写:

std::auto_ptr < sql::Connection > con ( driver -> connect("tcp://127.0.0.1:3306", "root", "admin") );

				= OR =

use namespace std;
use namespace sql;

auto_ptr < Connection > con ( driver -> connect("tcp://127.0.0.1:3306", "root", "admin") );

C++标准模板类auto_ptr帮助开发者管理动态内存分配,防止在意想不到的情况下发生内存泄漏,比如在某些情况下,正常的清理(内存)代码被跳过了。auto_ptr对象与指针具有相同的语义,但是auto_ptr对象超出作用域时将会自动释放掉动态分配的内存。也就是说,使用auto_ptr时,你不必显式使用delete操作符来释放内存,delete con;

要使用auto_ptr类,你需要包含<memory>这个头文件,并且auto_ptr<type>是定义在std名称空间内的,所以也要通过std名称空间来访问。type可以是指向任意类型/对象的指针。

至于采用auto_ptr智能指针机制,还是传统机制来动态管理内存,由读者自己决定。

获取一个Statement对象

当调用Connection::createStatement函数时,返回一个Statement对象,它可以用来向数据库服务器发送SQL语句。一般情况下,Statement对象执行的是不带参数的SQL语句。换句话说,一个Statement对象用于执行静态SQL语句,并返回其执行结果。如果需要执行多次不同输入的SQL语句,可以考虑使用Prepared Statements对象。

Connection::createStatement的函数签名(原型)如下所示,完整的Connection的接口函数列表,请阅看你的Connector/C++安装文件中的connection.h头文件

/* connection.h */
Statement* Connection::createStatement();

下面代码片段调用Connection对象中的createStatement函数,获得一个Statement类型的对象。

Connection *con;
Statement *stmt;

Statement stmt = con -> createStatement();

这个例子中,con是Connection类型的引用(指针)

执行SQL语句

在执行SQL语句前,你需要先连接数据库,并且选择相应的数据库架构。通过调用Connection对象中的setSchema函数来选择需要连接的架构,setSchema函数的参数是架构的名称。

以SQL语句作为(executeQuery的)参数,调用Statement::executeQuery函数执行查询语句。executeQuery()返回一个ResultSet对象。Statement::executeUpdate函数可用于执行特定的SQL语句,如INSERT,UPDATE,DELETE。或者是不返回任何结果的SQL语句,如SQL DDL语句。与excuteQuery()不同,excuteUpdate函数不返回ResultSet对象。相反,它返回INSERT,UPDATE,DELETE操作后受影响的行数。

如果你事先并不知道SQL语句是SELECT还是INSERT,UPDATE或DELETE,你可以使用execute函数。当SQL语句是SELECT操作时,execute()返回true,当SQL语句是INSERT,UPDATE,DELETE操作时,execute()返回false。如果语句是SELECT查询操作,你可以调用Statement实例中的getResultSet函数获取查询结果集。如果语句是INSERT,UPDATE,DELETE操作,你可以调用getUpdateCount()获取受影响的行数。

在少数情况下,一条SQL语句可能返回多个结果和/或更新行数。一般情况下,你可以忽略这一点。除非你执行一个存储过程,你知道可能会返回多个结果;或者动态执行未知的SQL语句。使用getResultSet或getUpdateCount函数可以获取结果,getMoreResults()可以检查是否还有其他结果集合。

相应的函数签名如下所示。完整的Connection和Statement的接口函数列表,请查看connection.h头文件。

/* 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(SQL异常),所以在代码中必须确保catch(捕捉)到这些异常。为了简单起见,在实例代码中并没有使用try...catch块(指retrieve_data_and_print函数中)。如果你重新查看完整的实例代码,你会发现其目的只是获取test数据库中City表的所有行数据。于是,使用executeQuery()的示例代码如下所示:

Statement *stmt;
ResultSet *res;

res = stmt -> executeQuery ("SELECT * FROM City");

executeQuery函数返回一个ResultSet对象,它包含了给定查询语句所生成的结果数据。当发生数据库访问错误,或调用已关闭的Statement对象,或给定的SQL语句产生的数据超过单一ResultSet对象,executeQuery将抛出SQLException。

另外,上述代码段可以用Statement::execute()重写,如下所示:

bool retvalue = stmt -> execute ("SELECT * FROM City");

if (retvalue) {
    res = stmt -> getResultSet();
} else {
    ...
}

如果查询结果是一个ResultSet对象(SELECT操作),execute函数返回true,如果查询结果是更新的行数(UPDATE,INSERT,DELETE)或者无结果,execute函数返回false。如果execute()返回true,使用getResultSet函数可以获取结果集。如果查询结果是更新的行数或者没有结果,getResultSet返回NULL。

execute和getResultSet这两个函数在发生数据库访问错误或在已经关闭的Statement上调用时,都会抛出SQLException.如果你需要插入新记录到数据库,你可以使用executeUpdate函数,如下所示:

int updateCount = stmt -> executeUpdate ("INSERT INTO City (CityName) VALUES ('Napier, New Zealand')");

executeUpdate执行给定的SQL数据操作语言(DML)语句,例如INSERT,UPDATE,DELETE;或不返回任何数据的SQL语句,例如DDL语句。执行INSERT,UPDATE,DELETE操作,executeUpdate()返回受影响的行数,如果SQL语句不返回任何数据,executeUpdate()返回0值。

当发生数据库访问错误或在已关闭的Statement对象上调用executeUpdate(),或者是SQL语句产生一个结果集对象,executeUpdate都会抛出SQLException.

另一方面,使用execute和getUpdateCount函数可以重写上述代码段,如下所示:

int updateCount = 0;

bool retstatus = stmt -> execute ("INSERT INTO City (CityName) VALUES ('Napier, New Zealand')");

if (!retstatus) {
    updateCount = stmt -> getUpdateCount();
} else {
    ...
}

如果SQL语句的执行的返回结果是受(更新)影响的行数,execute函数返回false,此时可以使用getUpdateCount函数获得受影响行数值。如果SQL语句执行结果返回一个ResultSet对象或者更多结果,getUpdateCount()返回-1.

在已关闭的Statement对象上调用execute和getUpdateCount或出现数据库访问错误的情况下都会抛出SQLException.

从结果集中获取数据

前面的段落解析到,executeQuery和execute两个函数执行SQL查询语句,前者返回一个ResultSet实例。你可以使用ResultSet对象访问SQL查询语句从数据库返回的数据。没一个ResultSet维护一个游标(指针),它指向其当前数据行。利用游标可以顺序读取数据集中的行。在一行中,列的值可以以任意顺序访问。你可以根据列的位置(offset)或它们的列名/标签来访问,后者具有一定的容错性,尤其是在表的架构发生改变后。使用列标签或列名会使代码更加清晰。另一方面,通过列的索引或位置访问,可以提高性能。

列标签就是SQL“AS”子句中指定的标签名。如果SQL“AS”子句没有指定标签,那么标签名就是列名。例如在“SELECT CityName AS CN FROM City”中,CN就是列标签名。

存储在ResultSet中的数据可以通过getXXX函数来获取,例如getString() 或 getInt(),根据数据类型的不同使用相应的函数获取数据。使用使用ResulSet的next和previous函数移动游标指向下一行数据。

ResultSet会一直保持打开,即使生成他的Statement对象关闭了,重新执行可以获取多个结果集队列中的下一个结果。一旦Statement生成了结果集,该ResultSet会一直有效,直到显示或隐式的关闭它(才会失效),不论产生它的Statement对象状态如何,都不会影响到已经产生的ResultSet。

截至撰写这篇文章时,MySQL Connector/C++的Statement对象返回一个结果缓冲区。缓冲区缓存在客户端上。不论结果集多大,driver都会获取所有的数据。connector未来的版本Statement对象有望返回缓冲和非缓冲结果集。

相关的函数签名如下所示。ResultSet支持的接口完整列表请查看resultset.h头文件。

/* 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”返回的结果集只有一列CityName数据,数据类型为String,在MySQL中为VARCHAR类型

下列代码片段遍历ResultSet对象res,通过准确的列名获取每一行数据,并显示在标准输出上。

while (res -> next()) {
    cout << rs -> getString("CityName") << endl;
}

因为也可以通过列的索引访问,所以下列代码片段也能得到相同的效果:

while (res -> next()) {
        cout << rs -> getString(1) << endl;
}

getString()的整形参数引用查询语句中指定的列,列号从1开始。(译者注:比如“SELECT COLUMN1 COLUMN2 COLUMN3 FROM TABLE”返回的结果中,第一列就是COLUMN1,第二列为COLUMN2,第三列为COLUMN3,通过getString(1)、getString(2)、getString(3)获取对应数据)

这两个版本的getString()都返回列值。如果列值为空(NULL),则返回值为空字符串(string对象)。你可以通过ResultSet::isNull函数,以列索引或列名/列标签作为参数,检查相应的列值是否为SQL NULL。通过ResultSet::wasNull()函数可以确定读取的最后一列的值是否为SQL NULL,此函数没有参数。

下列实例演示如何反向遍历结果集中的数据:

/* 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;
}

 

如果列名/列标签无效,或出现数据库访问错误,或在已关闭的ResultSet对象上调用getString(),getString()都会抛出SQLException.

 

(未完待续)