C中级 MariaDB Connector/C API 编程教程

引言 - 环境搭建

  首先开始环境搭建. 主要在Window 10 + Visual Studio 2015 上构建使用 mariadb connector/c api 进行数据操作开发.

为什么选择在window上搭建开发环境呢? 最核考虑是 更 方便 看源码!!!

  记得以前也写过一个在ubuntu上mariadb api开发教程, 有兴趣也可以参照看一下, 数据库层api是一样的.

   c基础 mariadb处理简单案例 http://www.cnblogs.com/life2refuel/p/5574544.html

本文重点讲解

  1. MariaDB在window 环境上搭建.

  2. MariaDB Connector/C API 的 HelloWorld

  3. Blob数据的insert 和 select

OK, 那开始吧, 先介绍需要下载的资源种子
  1. MaraDB window  : https://mariadb.com/my_portal/download/mariadb-enterprise#windows

     
  2. MariaDB Connector/C Download https://mariadb.com/kb/en/mariadb/mariadb-connector-c/

   

有了这些资源, 开始解压和安装, 先弄mariadb的压缩包, 解压完毕之后是下面这样 . 我放在了E盘下.

再设置一下 Path变量 (window 10 Path变量设置如下图)

环境变量设置好了之后安装 Conector/C 库的安装包 , 安装完毕后在C盘, MaeiaDB文件夹路径下会遇到以下文件目录

 

现在基本软件和驱动都已经安装完毕了. 后面任务是让mariadb 服务启动起来, 打开管理员模式下的cmd窗口, 执行

:: 开启mariadb 服务, 需要管理员权限
mysqld.exe --install mariadb
net start mariadb

 

扩充一点, 对于暂停, 卸载, 删除 命令如下

:: 下面是停止,卸载,删除服务命令
net stop mariadb
mysqld.exe --remove mariadb
sc delete mariadb

 

是不是很简单, 按照上面做了之后, 基本上mariadb 服务就已经启动起来了(前提脸不黑, O(∩_∩)O哈哈~).

开始执行下面, sql脚本, 创建用户和构建测试数据表

mysql -uroot -p

-- 开始使用test数据库, 进行数据测试
use test;
create table tb_user (
    id int unsigned not null auto_increment comment '员工编号',
    name varchar(20) not null comment '员工姓名',
    sex tinyint not null comment '员工性别, 0女士, 1男士, 其它扩展',
    email varchar(30) not null comment '员工邮箱',
    department varchar(50) not null comment '员工所在部门',
    employtime int unsigned not null default 0 comment '入职时间',
    salary int not null default 0 comment '员工工资',
    ext blob comment '后期使用, 扩展数据',
    
    primary key(id)
) engine = innodb default charset = latin1;

-- 为用户创建权限
-- 为 seluser 查询权限
-- 为 noruser 所用权限

-- 开始创建用户, 并刷新
create user 'seluser'@'localhost' identified by '7seluser';
create user 'noruser'@'localhost' identified by '7noruser';
flush privileges;

-- 设置不同用户权限
grant select on test.* to 'seluser'@'localhost';
grant all on test.* to 'noruser'@'localhost';

 

创建了两个用户, seluser和noruser, 分别具有test数据库下面读权限和所有权限. 扯一点, 权限管理其实是软件开发中一个共性, 哪里都需要.

因为权限它是权力在虚拟系统中缩影. 后面说一下 ,为什么用 latin1不用 utf-8. 这也是个''坑'', 推荐看看下面资料.

  编码ascii latin1 utf8 简介 http://blog.sina.com.cn/s/blog_5edf2a9f0100sicm.html

这步完成后, 就能通过mariadb命令进行操作了, 如同下面操作内容. 最终软件环境就搭建完毕了.

 

前言 - 环境测试, 搭建HelloWorld Demo

      目前可以开始着手编程开发了, 主要依赖的圣经是下面官网API Functions 说明.  我们所需要的一切都可以从下面内容中找见.

  MariaDB Connector/C API Functions  https://mariadb.com/kb/en/mariadb/mariadb-connectorc-api-functions/

那行, 打开VS, 创建控制台程序. 开始添加库目录, 头文件目录等.  参照下面流程先在项目中添加 引用目录

再添加静态库目录 

再为此项目指定导入静态库文件

其实VS 项目管理最核心文件就是*.sln 和 *. vcxproj 文件.  例如打开其中一个文件, 看见下面的XML组织管理结构. 很清晰的看出VS 项目是如何管理引用, 资源等公有内容的.

以上完成后, 现在先写一个 HelloWorld的Demo  mariadb_heoo.c

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mysql.h>

#define _STR_MHOST      "localhost"
#define _STR_MUSER      "seluser"
#define _STR_MPASSWD    "7seluser"
#define _STR_MDB        "test"

static inline void _mysql_check(MYSQL * con) {
    fprintf(stderr, "%s\n", mysql_error(con));
    mysql_close(con);
    exit(EXIT_FAILURE);
}

/*
 * 这里测试从mariadb 数据中拉取数据
 * ip        : localhost
 * name       : seluser
 * passwd    : 7seluser
 */
int main(int argc, char * argv[]) {

    // 创建数据连接对象, 需要和 mysql_close成对出现
    MYSQL * con = mysql_init(NULL);
    if (NULL == con) {
        fputs("main mysql_init NULL == con! error !\n", stderr);
        exit(EXIT_FAILURE);
    }

    if (!mysql_real_connect(con, _STR_MHOST, _STR_MUSER, _STR_MPASSWD, _STR_MDB, 0, NULL, 0))
        _mysql_check(con);

    if (mysql_query(con, "show tables;")) 
        _mysql_check(con);

    puts("mariadb is connect and run succesed!");

    /*
     * 这里拉取数据
     */
    MYSQL_RES * res = mysql_store_result(con);
    if (NULL == res)
        _mysql_check(con);

    MYSQL_ROW row;
    unsigned rlen = mysql_num_fields(res);
    printf("mariadb now row length = %u\n", rlen);
    
    // 打印行数据
    while ((row = mysql_fetch_row(res))) {
        for (unsigned i = 0; i < rlen; ++i)
            printf("%s ", row[i]);
        putchar('\n');
    }

    // 释放结果内存
    mysql_free_result(res);
    // 释放mysql客户端链接对象
    mysql_close(con);

    getchar();
    return 0;
}

 

这个演示Demo 主要是拉取 show tables; 返回数据. 上面都是开发中套路, 参照注释, 代码容易明爱. 主要流程包括 初始化, 链接, 请求查询, 解析结果, 关闭.

当我们运行的时候, 还需要添加上动态库 libmariadb.dll

运行最终结果如下, 到这里基础Hello World就大功告成了.

 

正文 - 实战blob数据的insert and select

   很恭喜到了这里, 以上前戏基本完毕了. 这里先把前面一个关于 latin1一个坑补上. 这个坑造成原因是, 传统C/C++ 使用的是ascii码,

对于汉字转utf-8麻烦, 而latin1是对ascii码扩充, 所以汉字也能正常显示. 这也是很多老系统或框架在和DB交互的时候, 使用latin1编码的原因.

此刻开始blob 练习演示. 先简单回顾一下 mariadb中常用的数据类型, 了解blob是啥.

类  型         占用字节数     无符号数的取值范围         有符号数的取值范围
tinyint       1             0-255                   -128-127
int           4             0-(2^32-1)              -(2^32/2)-(2^32/2-1)
bigint        8             0-(2^64-1)              -(2^64/2)-(2^64/2-1)
varchar       1-65535       类型的长度是可变,其取值范围为0-65535。
blob          65k           保存二进制数据

 

对于mariadb 的二进制blob类型 需要使用下面api构建 ,

unsigned long STDCALL mysql_real_escape_string(MYSQL *mysql,
                           char *to,const char *from,
                           unsigned long length);

 内部序列化成其内部保存的''串''. 那我们依赖test.workers表插入数据 . 首先定义对映的一种数据结构如下

#define _INT_WNAME        (63)
#define _INT_WEMAIL        (127)
#define _INT_WDEPAR        (255)

// workers 扩展信息, 当做其另一半吧
struct workers_ext {
    unsigned int id;                    // 唯一标识id
    char name[_INT_WNAME + 1];           // 姓名信息
};

// 对应数据库 test.workers 表内容
struct workers {
    unsigned int id;                    // 唯一标识id
    char name[_INT_WNAME + 1];          // 姓名信息
    char sex;                           // 0 女士, 1男士
    char email[_INT_WEMAIL + 1];        // 邮箱
    char department[_INT_WDEPAR + 1];   // 部门介绍
    int     salary;                     // 基本工资
    struct workers_ext ext;             // 扩展数据
};

这里struct workers_ext 结构就是对映test.workers 中 ext blob字段.  项目的业务例子参照 mariadb_insert.c

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <mysql.h>

#define LEN(arr) (sizeof(arr) / sizeof(*(arr)))

#define _STR_MHOST        "localhost"
#define _STR_MUSER        "noruser"
#define _STR_MPASSWD      "7noruser"
#define _STR_MDB          "test"

static inline void _mysql_check(MYSQL * con) {
    fprintf(stderr, "%s\n", mysql_error(con));
    mysql_close(con);
    exit(EXIT_FAILURE);
}

#define _INT_WNAME         (63)
#define _INT_WEMAIL        (127)
#define _INT_WDEPAR        (255)

// workers 扩展信息, 当做其另一半吧
struct workers_ext {
    unsigned int id;                    // 唯一标识id
    char name[_INT_WNAME + 1];          // 姓名信息
};

// 对应数据库 test.workers 表内容
struct workers {
    unsigned int id;                      // 唯一标识id
    char name[_INT_WNAME + 1];            // 姓名信息
    char sex;                             // 0 女士, 1男士
    char email[_INT_WEMAIL + 1];          // 邮箱
    char department[_INT_WDEPAR + 1];     // 部门介绍
    int     salary;                       // 基本工资
    struct workers_ext ext;               // 扩展数据
};

#define _INT_WINSERTSQL    (6*1024)        // 默认最大6k, 程序决定, 不是线程安全
// 得到最终insert 拼接的字符串
static void _workers_get_insertsql(MYSQL * con, struct workers * worker) {
    char query[_INT_WINSERTSQL + 1];
    assert(con && worker);

    // 保存扩展数据, 2 * size + 1 是api规定的, 返回最终编码长度
    char chunk[2 * sizeof(struct workers_ext) + 1];
    mysql_real_escape_string(con, chunk, (const char *)&worker->ext, sizeof(struct workers_ext));

    int len = snprintf(query, LEN(query),
        "insert into workers(name, sex, email, department, salary, ext) "
        "values('%s', %d, '%s', '%s', %d, '%s');",
        worker->name,
        worker->sex,
        worker->email,
        worker->department,
        worker->salary,
        chunk);
    if (len > _INT_WINSERTSQL) {
        fprintf(stderr, "_workers_get_insertsql snprintf len = %d is too long!\n", len);
        return;
    }

    // 这里可以插入到数据库
    if (mysql_real_query(con, query, len))
        _mysql_check(con);
}

/*
 * 这里测试写入复杂数据到mariadb中, 例如插入blob数据
 */
int mariadb_insert(int argc, char * argv[]) {

    // 创建数据连接对象, 需要和 mysql_close成对出现
    MYSQL * con = mysql_init(NULL);
    if (NULL == con) {
        fputs("main mysql_init NULL == con! error !\n", stderr);
        exit(EXIT_FAILURE);
    }
    // 开始创建TCP常连接对象
    if (!mysql_real_connect(con, _STR_MHOST, _STR_MUSER, _STR_MPASSWD, _STR_MDB, 0, NULL, 0))
        _mysql_check(con);

    // 每次插入就只重置2条数据
    if (mysql_query(con, "truncate table workers;"))
        _mysql_check(con);

    struct workers workers[] = {
        { 0, "09.09 毛无敌诞辰", 1, "666666@666.com", "帝王大厦,长江口", -1, { 2, "09.10 教师节快乐" } },
        { 0, "09.10 教师节快乐", 1, "55555@555.com", "各大地毯,松花江", 555, { 1, "09.09 毛无敌诞辰" } },
    };

    // 开始插入数据
    for (int i = 0; i < LEN(workers); ++i)
        _workers_get_insertsql(con, workers + i);

    puts("mariadb localhost test.workers reset is succesed!");

    // 释放mysql客户端链接对象
    mysql_close(con);

    getchar();
    return 0;
}

 

上面演示中主要执行插入代码见 _workers_get_insertsql 函数, 完成sql语句的拼接, 和query查询操作. 最终的插入结果

一些正常, 通过上面例子学习, 觉得应该对于mariadb 的 connector/c 驱动 api 有点头绪了. 还是很容易理解的, 因为没有转弯的地方, 很直白.

O(∩_∩)O哈哈~

数据构建好了, 自然数据的查询也要有呢.  参照 mariadb_select.c

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <mysql.h>

#define _STR_MHOST        "localhost"
#define _STR_MUSER        "seluser"
#define _STR_MPASSWD      "7seluser"
#define _STR_MDB          "test"

static inline void _mysql_check(MYSQL * con) {
    fprintf(stderr, "%s\n", mysql_error(con));
    mysql_close(con);
    exit(EXIT_FAILURE);
}

#define _INT_WNAME        (63)
#define _INT_WEMAIL        (127)
#define _INT_WDEPAR        (255)

// workers 扩展信息, 当做其另一半吧
struct workers_ext {
    unsigned id;                        // 唯一标识id
    char name[_INT_WNAME + 1];          // 姓名信息
};

// 对应数据库 test.workers 表内容
struct workers {
    unsigned id;                        // 唯一标识id
    char name[_INT_WNAME + 1];          // 姓名信息
    char sex;                           // 0 女士, 1男士
    char email[_INT_WEMAIL + 1];        // 邮箱
    char department[_INT_WDEPAR + 1];   // 部门介绍
    unsigned employtime;                // 入职时间
    int     salary;                     // 基本工资
    struct workers_ext ext;             // 扩展数据
};

/*
 * 这里测试写入复杂数据到mariadb中, 例如插入blob数据
 */
int mariadb_select(int argc, char * argv[]) {

    // 创建数据连接对象, 需要和 mysql_close成对出现
    MYSQL * con = mysql_init(NULL);
    if (NULL == con) {
        fputs("main mysql_init NULL == con! error !\n", stderr);
        exit(EXIT_FAILURE);
    }
    // 开始创建TCP常连接对象
    if (!mysql_real_connect(con, _STR_MHOST, _STR_MUSER, _STR_MPASSWD, _STR_MDB, 0, NULL, 0))
        _mysql_check(con);

    // 这里读取数据
    if (mysql_query(con, "select * from workers;"))
        _mysql_check(con);

    /*
     * 这里拉取数据
     */
    MYSQL_RES * res = mysql_store_result(con);
    if (NULL == res)
        _mysql_check(con);

    MYSQL_ROW row;

    // 打印行数据
    while ((row = mysql_fetch_row(res))) {
        // 得到各个列长度
        unsigned long * clens = mysql_fetch_lengths(res);
        if(NULL == clens)
            _mysql_check(con);

        // 得到最后一个数据返回
        struct workers worker;
        worker.id = (unsigned)strtoul(row[0], NULL, 0);
        strcpy(worker.name, row[1]);
        worker.sex = (char)atoi(row[2]);
        strcpy(worker.email, row[3]);
        strcpy(worker.department, row[4]);
        worker.employtime = (unsigned)strtoul(row[5], NULL, 0);
        worker.salary = atoi(row[6]);
        memcpy(&worker.ext, row[7], clens[7]);

        // 简单打印数据
        printf("{ %u, '%s', %d, '%s', '%s', %u, %d, { %u, '%s' } }\n",
            worker.id, worker.name, worker.sex, worker.email,
            worker.department, worker.employtime, worker.salary,
            worker.ext.id, worker.ext.name);
    }

    // 释放结果内存
    mysql_free_result(res);

    // 释放mysql客户端链接对象
    mysql_close(con);

    getchar();
    return 0;
}

 

简单说明一下 worker.id = (unsigned)strtoul(row[0], NULL, 0); 这行代码,  我们先看一下 strtoul 原型

_Check_return_
_ACRTIMP unsigned long __cdecl strtoul(
    _In_z_                   char const* _String,
    _Out_opt_ _Deref_post_z_ char**      _EndPtr,
    _In_                     int         _Radix
    );

返回值存在 unsigned, 这个很重要. 因为有符号和无符号数值之间转换存在符号位问题. 上面做法采用高精度的无符号转过来, 精度不损失.符号位不参与影响.

最终的效果如下

oh Yeah!

一切都搞定了, 通过这些步骤练习, 关于mariadb connector/c api funciton 基本操作, 还有如何打渔已经都有些眉目了.  以后那就看以后的复杂业务了.

遇到, 只需要多查查官网API说明就能迎刃而解. 当然最重要的还是勤思考, 多动手.

 

后记 - 如果还有明天, 你要怎样装扮你的脸

  错误是难免的,  发现会及时更正....

      Here We Are Again  http://music.163.com/#/song?id=27876900

     

 

posted on 2016-09-11 23:16  喜欢兰花山丘  阅读(5443)  评论(0编辑  收藏  举报