作者:AceTan,转载请标明出处!
0x00 为什么要连接数据库##
之前我们使用过C++读写文件,如果我们想永久存储一些数据,可以把它写入到文件中。文件存储可以以目录方式管理,简洁明了。但是,如果文件多了起来,便非常难以管理。还有一个问题是,文件的方式浪费了很多硬盘空间。这时候,我们就需要数据库来解决这些问题了。
数据库(Database)是按照数据结构来组织、存储和管理数据的建立在计算机存储设备上的仓库。
数据库的历史很悠久 ,从最简单的存储有各种数据的表格到能够进行海量数据存储的大型数据库系统都在各个方面得到了广泛的应用。搞IT这一块,基本上绕不开数据库的。它的作用和应用的广泛程度,远超你我的想象。
主流的数据库大约有以下几种:Oracle、MySQL、SQL Server、MongoDB、PostgreSQL、SQLite等。关于各数据库的优缺点,这里不想讨论,数据库的知识请参阅其他书籍。
我们以开源免费的MySQL为例,来一窥C++如何连接数据库。希望能起到抛砖引玉的作用。
0x01 准备工作##
首先,我们得安装数据库,最好顺带再安装一下数据库的一些管理工具。MySQL数据库的安装,随手百度一下就能百度到,这里不做详细介绍。需要注意一下的是安装过程中的字符集问题,最好选UTF8,不要使用默认的拉丁字符集。端口保持默认值就好。
推荐使用的mysql数据库的管理工具是:navicat for mysql。 使用其他的工具亦可。
现在假定你已经顺利完成了上述工作,数据库服务能正常运行。现在我们使用navicat连接mysql。创建一个名为test的数据库,在test数据库下,创建一个名为user的表。添加如下字段:
id : 用户唯一标识。设为主键,且设为自增长。
name: 用户名。
password : 密码。
再随便添加几个记录,完成后的表格大致上如下:
id | name | password |
---|---|---|
1 | DanDan | 12345678 |
2 | AceTan | password |
0x02 Code##
我的开发环境是:VS2015 + MySQL5.5 + win7 64位。 其中数据库也是64位的。
然后需要做以下两件事:
引入相关的头文件:引入mysql的头文件。这里有两种做法,一个是在VC目录下设置。具体做法:右击项目 → 属性 → VC++目录 → 包含目录,找到mysql安装目录下的include文件夹。第二种做法是:拷贝mysql安装目录下的include文件夹到工作目录中,然后直接#include就可以了。推荐使用第二种方法。
连接相关的库文件:引入mysql的库文件,这里也有两种做法,和上面的引入头文件的方法相同,这次引入的是lib这个文件夹,同样推荐使用第二种方法。
数据库的一些常用操作###
数据库有几个常用的基本操作,增删查改,我们简单复习一下。
/*增*/
INSERT INTO table_name(field1, field2, field3) VALUES(value1, value2, value3),(value11, value22, value33);
/*删*/
DELETE FROM table_name [WHERE condition];
/*查*/
SELECT * FROM table_name [WHERE condition];
/*改*/
UPDATE table_name SET columnName=NewValue [WHERE condition];
其他一些数据库操作,如用户管理、修改表结构等,这里不做介绍了。
Talk is cheap, show you the code.
MySQLManager.h 文件:
/*
文件名: MySQLManager.h
内 容: MySQL连接数据库管理类
创建日期: 2016年10月18日
创建人: AceTan
*/
#pragma once
// 网络通信头文件
#include
// 引入mysql头文件(比较好的做法是把文件夹拷到工程目录,也可以在vc目录里面设置)
#include "include/mysql.h"
#include
// 包含附加依赖项,也可以在工程--属性里面设置
#pragma comment(lib, "wsock32.lib")
#pragma comment(lib, "libmysql.lib")
// 连接数据库的一些必要信息
struct ConnectionInfo
{
const char* host; // 主机地址
const char* user; // 用户名
const char* password; // 密码
const char* database; // 数据库名
unsigned int port; // 端口号
const char* unix_socket; // unix连接标识
unsigned long clientflag; // 客户端连接标志
// 构造函数,设置一些默认值
ConnectionInfo() :
host("127.0.0.1"),
port(3306),
unix_socket(NULL),
clientflag(0)
{
}
};
class MySQLManager
{
public:
// 连接数据库
bool Init(ConnectionInfo& info);
// 释放连接
bool FreeConnect();
// 增加数据
// bool InsertData(const char* sql);
// 删除数据
// bool DeleteData(const char* sql);
// 更新数据
// bool UpdateData(const char* sql);
// 执行sql语句, 包括增加、删除、更新数据
bool ExecuteSql(const char* sql);
// 查询数据
MYSQL_RES* QueryData(const char* sql);
// 打印结果集
void PrintQueryRes();
private:
MYSQL m_mysql; // mysql连接
MYSQL_RES* m_res; // 查询结果集
};
MySQLManager.cpp 文件:
#include "MySQLManager.h"
#include
#include
using namespace std;
// 连接数据库
bool MySQLManager::Init(ConnectionInfo& info)
{
// 初始化mysql,连接mysql,数据库
mysql_init(&m_mysql);
// 连接失败
if (!(mysql_real_connect(&m_mysql, info.host, info.user, info.password, info.database, info.port, info.unix_socket, info.clientflag)))
{
return false;
}
return true;
}
// 释放连接
bool MySQLManager::FreeConnect()
{
//释放资源
mysql_free_result(m_res);
mysql_close(&m_mysql);
return false;
}
// 执行sql语句, 包括增加、删除、更新数据
bool MySQLManager::ExecuteSql(const char * sql)
{
if (mysql_query(&m_mysql, sql))
{
// 打错误log,这里直接显示到控制台
cerr << "执行sql语句失败,错误信息为: " << mysql_error(&m_mysql) << endl;
return false;
}
else
{
cout << "执行sql语句成功!" << endl;
}
return true;
}
// 查询数据
MYSQL_RES* MySQLManager::QueryData(const char* sql)
{
if (mysql_query(&m_mysql, sql))
{
// 打错误log,这里直接显示到控制台
cerr << "查询语句执行失败,错误信息为: " << mysql_error(&m_mysql) << endl;
return nullptr;
}
else
{
cout << "查询语句执行成功!" << endl;
}
// 存储查询结果
m_res = mysql_store_result(&m_mysql);
return m_res;
}
// 遍历结果集
void MySQLManager::PrintQueryRes()
{
if (nullptr == m_res || NULL == m_res)
{
return;
}
// 获取行数
// unsigned int rows = mysql_affected_rows(m_mysql);
// 字段列数组
MYSQL_FIELD* field = nullptr;
//存字段名二维数组
char fieldName[64][32];
// 获取字段名
for (int i = 0; field = mysql_fetch_field(m_res); ++i)
{
strcpy_s(fieldName[i], field->name);
}
// 获取列数
int columns = mysql_num_fields(m_res);
for (int i = 0; i < columns; ++i)
{
// 使用C语言的printf格式化更方便一点
printf("%10s\t", fieldName[i]);
}
cout << endl;
MYSQL_ROW row;
while (row = mysql_fetch_row(m_res))
{
for (int i = 0; i < columns; ++i)
{
printf("%10s\t", row[i]);
}
cout << endl;
}
}
main.cpp文件
#include
#include "MySQLManager.h"
using namespace std;
int main()
{
MySQLManager mysql;
ConnectionInfo info;
// 填充ConnectionInfo这个结构体,项目中一般从配置文件这读取
info.user = "root";
info.password = "your_password";
info.host = "localhost";
info.port = 3306;
info.database = "test";
info.unix_socket = NULL;
info.clientflag = 0;
// mysql连接
if (!mysql.Init(info))
{
return -1;
}
// 增加数据测试
const char* sql1 = "insert into user values (NULL, 'Ada', 'password')";
mysql.ExecuteSql(sql1);
// 删除数据测试
const char* sql2 = "delete from user where name = 'AceTan'";
mysql.ExecuteSql(sql2);
// 修改数据测试
const char* sql3 = "update user set password='update_password' where name = 'Ada'";
mysql.ExecuteSql(sql3);
// 查询数据测试
const char* sql4 = "select * from user";
mysql.QueryData(sql4);
mysql.PrintQueryRes();
// 释放mysql资源
mysql.FreeConnect();
return 0;
}
执行后的结果:
0x03 一些值得注意的点##
一般把include文件 夹、lib文件夹拷贝到当前目录,使用相对路径设置。
注意生成的平台是否一致:是64位还是32位的。如果不一致,要在项目配置中调成一致。
.lib文件 .dll 放在执行目录下,不然找不到文件。
linux操作系统下,代码要做适量修改。链接前检查数据库安装是否正确、包含头文件、连接相关的库文件等操作类似。
0x04 唠叨几句ORM框架##
所谓ORM,就是Object Relational Mapping的缩写,即对象关系映射。它最主要的作用就是运行时能参照映射文件的信息,把对象持久化到数据库中。这在实际应用中非常常见的需求。常见于Java,例如著名的Hibernate、MyBatis等。
我们上面介绍了数据库的简单连接,然后我们之后的很多工作就是如何增删查改一些对象,前期的练习是非常必须的,可在实际项目中就没必要重复造轮子了。很多大牛已经搞出了一套轻量级的ORM,我们可以参考并应用。C++中常见的ORM大致上有如下这几个:LiteSQL、ODB、OxOrm等,关于它们的优缺点,可以搜下百度,这里不展开讨论了。
0x05 结束语##
数据库在软件开发中扮演着无可替代的角色,熟悉并掌握数据库的用法,是一项非常重要的技能。