MySQL C++ Driver的实现基于JDBC规范
MySQL Connector/C++是由Sun Microsystems开发的MySQL连接器。它提供了基于OO的编程接口与数据库驱动来操作MySQL服务器。
与许多其他现存的C++接口实现不同,Connector/C++遵循了JDBC规范。也就是说,Connector/C++ Driver的API主要是基于Java语言的JDBC接口。JDBC是java语言与各种数据库连接的标准工业接口。
Connector/C++实现了大部分JDBC规范。如果C++程序的开发者很熟悉JDBC编程,将很快的入门。
MySQL Connector/C++需要安装配置boost库,boost库安装编译在这里不进行阐述
库下载:
mysql-connector-c++-8.0.18-win32库
工程配置:
- 新建一个空项目ManageSystem:
- 将下载好的mysql-connector-c++-8.0.18-win32和boost_1_72_0库放到对应的目录下,方便引用:
- 将F:\Project\ManageSystem2\Third\mysql-connector-c++-8.0.18-win32\lib添加到项目的库目录中(根据具体路径而定)
- 将F:\Project\ManageSystem2\Third\mysql-connector-c++-8.0.18-win32\include添加到VC++的包含目录中(根据具体路径而定),
- 将F:\boost\boost_1_55_0添加到VC++的包含目录中(根据具体路径而定)
配置同上
- 添加mysqlcppconn.lib至链接器--->输入--->附加依赖项中
同样,如果使用的mysql是64位的,还需要将项目的解决方案平台由win32改成x64
- 将F:\Project\ManageSystem2\Third\mysql-connector-c++-8.0.18-win32\lib(根据具体路径而定)下的mysqlcppconn.dll复制到项目中去,和.cpp,.h文件位于同一路径下
- 将F:\MySQL\MySQL Server 5.6\lib(根据具体路径而定)下的libmysql.dll复制到项目中去,和.cpp,.h文件位于同一路径下(配置完成)
或者放到项目的运行目录F:\Project\ManageSystem2\proj.win32\Debug.win32下面都可以。
程序实现:
- 程序引入头文件
#include "jdbc/mysql_connection.h"
#include "jdbc/mysql_driver.h"
#include "jdbc/cppconn/statement.h"
using namespace sql;
using namespace std;
#define DBHOST "tcp://localhost:3306"
#define USER "root"
#define PASSWORD "123456"
- 连接数据库,实现查询操作
//初始化驱动
//Driver *m_driver;
//m_driver = get_driver_instance();
sql::mysql::MySQL_Driver *driver = NULL;
sql::Connection *conn = NULL;
sql::Statement *state;
sql::ResultSet *result;
driver = sql::mysql::get_mysql_driver_instance();
if (driver == NULL)
{
CCLOG("driver is null...");
}
conn = driver->connect(DBHOST, USER, PASSWORD);
if (conn == NULL)
{
CCLOG("conn is null...");
}
else
{
CCLOG("connect suceess...");
}
CCLOG("-------------------------begin query------------------------------------------");
state = conn->createStatement();
state->execute("use db_babykylin");
result = state->executeQuery("select * from t_users");
//// 输出查询
while (result->next() != NULL)
{
//CCLOG("userid:%d", result->getInt("userid"));
//CCLOG("account:%s", (result->getString("account")).c_str());
CCLOG("userid:%s, account:%s, name:%s, sex:%s, headimg:%s, lv:%s, coins:%s, gems:%s, roomid:%s", (result->getString("userid")).c_str(), \
(result->getString("account")).c_str(), (result->getString("name")).c_str(), (result->getString("sex")).c_str(), (result->getString("headimg")).c_str(), \
(result->getString("lv")).c_str(), (result->getString("coins")).c_str(), (result->getString("gems")).c_str(), (result->getString("roomid")).c_str() );
}
CCLOG("--------------------------end query-----------------------------------------");
delete conn;
driver = NULL;
//m_driver = NULL;
conn = NULL;
数据库表结构:
查询结果:
执行update操作的存储过程:
/*
如果你事先并不知道SQL语句是SELECT还是INSERT,UPDATE或DELETE,你可以使用execute函数。
当SQL语句是SELECT操作时,execute()返回true,当SQL语句是INSERT,UPDATE,DELETE操作时,execute()返回false。
如果语句是SELECT查询操作,你可以调用Statement实例中的getResultSet函数获取查询结果集。
如果语句是INSERT,UPDATE,DELETE操作,你可以调用getUpdateCount()获取受影响的行数。
*/
//--------执行存储过程-update---------
int userid = 9;
int coins = 100;
int gems = 20;
char proc[1024] = { 0 };
sprintf(proc, "call update_user_info(%d,%d,%d)", userid, coins, gems);
CCLOG("call proc:%s", proc);
bool resExec = state->execute(proc);
int uCount = 0;
if (!resExec)
{
uCount = state->getUpdateCount();
}
CCLOG("call proc-update res:%d", uCount);
//--------存储过程---------
CREATE DEFINER=`root`@`localhost` PROCEDURE `update_user_info`(in_userid int,in_coins int,in_gems int)
BEGIN
update t_users set coins=coins+in_coins,gems=gems+in_gems where userid=in_userid;
END
执行结果:
执行insert操作的存储过程:
/*
如果你事先并不知道SQL语句是SELECT还是INSERT,UPDATE或DELETE,你可以使用execute函数。
当SQL语句是SELECT操作时,execute()返回true,当SQL语句是INSERT,UPDATE,DELETE操作时,execute()返回false。
如果语句是SELECT查询操作,你可以调用Statement实例中的getResultSet函数获取查询结果集。
如果语句是INSERT,UPDATE,DELETE操作,你可以调用getUpdateCount()获取受影响的行数。
*/
//--------执行存储过程-insert---------
string name = "user5";
string pwd = "123456";
sprintf(proc, "call insert_manager('%s','%s')", name.c_str(), pwd.c_str());
CCLOG("call proc:%s", proc);
resExec = state->execute(proc);
uCount = 0;
if (!resExec)
{
uCount = state->getUpdateCount();
}
CCLOG("call proc-insert res:%d", uCount);
//--------存储过程---------
CREATE DEFINER=`root`@`localhost` PROCEDURE `insert_manager`(IN `in_name` varchar(64),IN `in_pwd` varchar(32))
BEGIN
#Routine body goes here...
INSERT INTO t_manager(name, pwd) VALUES(in_name, in_pwd);
END
执行结果:
数据库里面实现了插入。
执行delete操作的存储过程:
/*
如果你事先并不知道SQL语句是SELECT还是INSERT,UPDATE或DELETE,你可以使用execute函数。
当SQL语句是SELECT操作时,execute()返回true,当SQL语句是INSERT,UPDATE,DELETE操作时,execute()返回false。
如果语句是SELECT查询操作,你可以调用Statement实例中的getResultSet函数获取查询结果集。
如果语句是INSERT,UPDATE,DELETE操作,你可以调用getUpdateCount()获取受影响的行数。
*/
//--------执行存储过程-delete---------
name = "user7";
sprintf(proc, "call delete_manager('%s')", name.c_str());
CCLOG("call proc:%s", proc);
resExec = state->execute(proc);
uCount = 0;
if (!resExec)
{
uCount = state->getUpdateCount();
}
CCLOG("call proc-delete res:%d", uCount);
//--------存储过程---------
CREATE DEFINER=`root`@`localhost` PROCEDURE `delete_manager`(IN `in_name` varchar(64))
BEGIN
#Routine body goes here...
DELETE FROM t_manager WHERE name=in_name;
END
因为没有这行记录,操作应该返回时0;
执行结果:
下面将name = "user6";改成这个,应该删除id为7的行;res应该返回非0值.
执行结果:
数据库里面已经删掉。
完整代码:
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
#include "jdbc/mysql_connection.h"
#include "jdbc/mysql_driver.h"
#include "jdbc/cppconn/statement.h"
using namespace sql;
using namespace std;
#define DBHOST "tcp://localhost:3306"
#define USER "root"
#define PASSWORD "123456"
class HelloWorld : public cocos2d::Scene
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
// a selector callback
void menuCloseCallback(cocos2d::Ref* pSender);
// implement the "static create()" method manually
CREATE_FUNC(HelloWorld);
};
#endif // __HELLOWORLD_SCENE_H__
#include "HelloWorldScene.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
Scene* HelloWorld::createScene()
{
return HelloWorld::create();
}
// Print useful error message instead of segfaulting when files are not there.
static void problemLoading(const char* filename)
{
printf("Error while loading: %s\n", filename);
printf("Depending on how you compiled you might have to add 'Resources/' in front of filenames in HelloWorldScene.cpp\n");
}
// on "init" you need to initialize your instance
bool HelloWorld::init()
{
//////////////////////////////
// 1. super init first
if ( !Scene::init() )
{
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
//初始化驱动
//Driver *m_driver;
//m_driver = get_driver_instance();
sql::mysql::MySQL_Driver *driver = NULL;
sql::Connection *conn = NULL;
sql::Statement *state;
sql::ResultSet *result;
driver = sql::mysql::get_mysql_driver_instance();
if (driver == NULL)
{
CCLOG("driver is null...");
}
conn = driver->connect(DBHOST, USER, PASSWORD);
if (conn == NULL)
{
CCLOG("conn is null...");
}
else
{
CCLOG("connect suceess...");
}
CCLOG("-------------------------begin query------------------------------------------");
state = conn->createStatement();
state->execute("use db_babykylin");
result = state->executeQuery("select * from t_users");
//// 输出查询
while (result->next() != NULL)
{
//CCLOG("userid:%d", result->getInt("userid"));
//CCLOG("account:%d", (result->getString("account")).c_str());
CCLOG("userid:%s, account:%s, name:%s, sex:%s, headimg:%s, lv:%s, coins:%s, gems:%s, roomid:%s", (result->getString("userid")).c_str(), \
(result->getString("account")).c_str(), (result->getString("name")).c_str(), (result->getString("sex")).c_str(), (result->getString("headimg")).c_str(), \
(result->getString("lv")).c_str(), (result->getString("coins")).c_str(), (result->getString("gems")).c_str(), (result->getString("roomid")).c_str() );
}
CCLOG("--------------------------end query-----------------------------------------");
//--------执行存储过程-update---------
int userid = 9;
int coins = 100;
int gems = 20;
char proc[1024] = { 0 };
sprintf(proc, "call update_user_info(%d,%d,%d)", userid, coins, gems);
CCLOG("call proc:%s", proc);
bool resExec = state->execute(proc);
int uCount = 0;
if (!resExec)
{
uCount = state->getUpdateCount();
}
CCLOG("call proc-update res:%d", uCount);
//--------执行存储过程-insert---------
string name = "user6";
string pwd = "123456";
sprintf(proc, "call insert_manager('%s','%s')", name.c_str(), pwd.c_str());
CCLOG("call proc:%s", proc);
resExec = state->execute(proc);
uCount = 0;
if (!resExec)
{
uCount = state->getUpdateCount();
}
CCLOG("call proc-insert res:%d", uCount);
//--------执行存储过程-delete---------
name = "user6";
sprintf(proc, "call delete_manager('%s')", name.c_str());
CCLOG("call proc:%s", proc);
resExec = state->execute(proc);
uCount = 0;
if (!resExec)
{
uCount = state->getUpdateCount();
}
CCLOG("call proc-delete res:%d", uCount);
delete state;
delete result;
delete conn;
driver = NULL;
//m_driver = NULL;
conn = NULL;
return true;
}
void HelloWorld::menuCloseCallback(Ref* pSender)
{
//Close the cocos2d-x game scene and quit the application
Director::getInstance()->end();
#if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
exit(0);
#endif
/*To navigate back to native iOS screen(if present) without quitting the application ,do not use Director::getInstance()->end() and exit(0) as given above,instead trigger a custom event created in RootViewController.mm as below*/
//EventCustom customEndEvent("game_scene_close_event");
//_eventDispatcher->dispatchEvent(&customEndEvent);
}
参考文档:
- 官方手册: http://dev.mysql.com/doc/refman/5.6/en/connector-cpp.html
- MySQL Connector/C++文档翻译: http://www.cnblogs.com/dvwei/archive/2013/04/18/3029464.html
获取一个Statement对象
当调用Connection::createStatement函数时,返回一个Statement对象,它可以用来向数据库服务器发送SQL语句。一般情况下,Statement对象执行的是不带参数的SQL语句。换句话说,一个Statement对象用于执行静态SQL语句,并返回其执行结果。如果需要执行多次不同输入的SQL语句,可以考虑使用Prepared Statements对象。
执行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.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(捕捉)到这些异常。
Statement *stmt;
ResultSet *res;
res = stmt -> executeQuery ("SELECT * FROM City");
从结果集中获取数据
存储在ResultSet中的数据可以通过getXXX函数来获取,例如getString() 或 getInt(),根据数据类型的不同使用相应的函数获取数据。使用使用ResulSet的next和previous函数移动游标指向下一行数据。
ResultSet会一直保持打开,即使生成他的Statement对象关闭了,重新执行可以获取多个结果集队列中的下一个结果。一旦Statement生成了结果集,该ResultSet会一直有效,直到显示或隐式的关闭它(才会失效),不论产生它的Statement对象状态如何,都不会影响到已经产生的ResultSet。
下列代码片段遍历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.