当前位置:首页 > PHP教程 > PHP总结归纳

MySQLConnector/C++入门教程(上)

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

翻译: darkbull(www.darkbull.net)

示例代码:mysqldemo.7z

译者注:该教程是一篇介绍如何使用c++操作mysql的入门教程,内容简单易用。我对原文中的一些例子进行了修改,并新添加了部分例子,主要目标是更简单明了的向读者介绍如何操作mysql数据库。本人也是mysql的初学者,错误也在所难免,欢迎拍砖!

这篇教程将一步一步引导您如何去构建和安装mysql connection/c++ driver,同时提供几个简单的例子来演示如何连接mysql数据库,如何向mysql添加、获取数据。本教程关注如何在c++应用程序中操作mysql,所以首先应该确定mysql数据库服务已经开启并且在当前机器能够访问到。

本教程面向的读者是mysql connector/c++的初学者,如果您对c++语言或者mysql数据库不是很了解,请参考其他的教程。

教程使用了下面所列的一些工具和技术,来构建、编译、运行例子程序(译者注:这是原文作者使用的环境。笔者使用的环境是:winxp,mysql5.1,vs2008, ):

  • 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

目录

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安装目录下的liboptlibmysql.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 & 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 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上执行调用该方法。

未完待续!


【说明】本文章由站长整理发布,文章内容不代表本站观点,如文中有侵权行为,请与本站客服联系(QQ:)!