C++入门系列博客九 连接数据库


作者: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 <WinSock.h>

// 引入mysql头文件(比较好的做法是把文件夹拷到工程目录,也可以在vc目录里面设置)
#include "include/mysql.h"

#include <Windows.h>

// 包含附加依赖项,也可以在工程--属性里面设置
#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 <iostream>
#include <stdio.h>
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 <iostream>
#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 结束语##

数据库在软件开发中扮演着无可替代的角色,熟悉并掌握数据库的用法,是一项非常重要的技能。


数据库
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,622评论 18 399
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,065评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,651评论 18 139
  • 时间如水缓缓流走 就像握不住的沙 太阳依然会升起 仍旧会发光发热 明天我不知道会发生什么 后天也一样 每一天都是新...
    芦苇根尖尖儿阅读 173评论 0 1
  • 守得花开花谢 日复一日,年复一年 在河的另一畔一直有一个人等你 你却始终不曾归来
    青年高地阅读 131评论 0 0