C++ 调用MongoDB C Driver

嗯……环境都准备好了,那么现在可以开始用我们敬爱的C++通过接口来间接操控MongoDB数据库。
注意,要有一定的MongoDB数据库基础知识哦~

启动MongoDB服务器

首先要做的,就是启动服务器

mongod --config ${MONGODB}/conf.d/mongo.conf

要停止服务器,不建议直接kill掉,而是加上一个 –shutdown 参数

我们还要创建一个可读写指定数据库的用户,比如

use admin
db.createUser({user:’user_test’,pwd:’12345’,roles:[{role:’readWrite’,db:’db_test’}]}

这里创建了用户 user_test,密码 12345,只能读写数据库 db_test

mongoc

我们只需引用头文件 libmongoc-1.0/mongoc.h
main 函数要调用 mongoc_init() 来初始化库以及结尾 mongoc_cleanup() 来释放

要连接到MongoDB数据库,就得提供一个连接字符串,类似于

mongodb://username:password@host:port/db
例如: mongodb://localhost:27017表示连接到本地回环地址127.0.0.1,以及端口27017的服务器,这个连接方式无需验证身份(没有开启 auth)。
然而,这个例子的连接字符串为: mongodb://user_test:[email protected]:27017/db_test

有了连接字符串,那么我们怎么连接到服务器?可以通过 mongoc_client_get_database() 来建立一个client

1
mongoc_client_t * mongoc_client_new (const char *uri_string);

该函数返回一个 mongoc_client_t* 结构指针,之后的操作就是通过该指针来访问 database、collection……

除了直接通过一个连接字符串来建立client,还可以‘构造’一个 uri 建立client。这一步也很简单,可以调用 mongoc_uri* 系列函数,再
mongoc_client_new_from_uri(),如:
mongoc_uri_t uri=mongoc_uri_new(“mongodb://127.0.0.1:27017”);
mongoc_uri_set_database(uri,”xxxx”);
mongoc_uri_set_username(uri,”xxxx”);
mongoc_uri_set_password(uri,”xxxx”);
mongoc_client_t
client= mongoc_client_new_from_uri(uri);

连接成功后,可以通过来获取 mongoc_client_get_default_database 来获取默认的数据库(前提是刚才连接字符串提供了一个数据库),或者 mongoc_client_get_database 来获取指定名称的数据库

1
2
mongoc_database_t * mongoc_client_get_database (mongoc_client_t *client, const char *name);
mongoc_database_t * mongoc_client_get_default_database (mongoc_client_t *client);

通过 mongoc_database_has_collection() 判断是否存在一个 collection

1
2
3
4
5
6
bool mongoc_database_has_collection (mongoc_database_t *database,const char *name,bson_error_t *error);
typedef struct _bson_error_t {
uint32_t domain;
uint32_t code;
char message[BSON_ERROR_BUFFER_SIZE];
} bson_error_t;

有了database,那么就可以获取某个collection,方法有两个

1
2
3
mongoc_collection_t * mongoc_client_get_collection (mongoc_client_t *client,const char *db,const char *collection);
mongoc_collection_t *
mongoc_database_get_collection (mongoc_database_t *database, const char *name);

接下来,就是重头戏了

Creating BSON Documents

BSON,类似与JSON,但又有点不同。通过创建BSON Document来实现与MongoDB的交互。如:

1
2
3
4
5
6
7
8
9
10
11
> db.test.insert({'name':'xiaoming','age':10,info:['shy','helpful']})
> db.test.find().pretty()
{
"_id" : ObjectId("5a8e1dced077dc147db71f81"),
"name" : "xiaoming",
"age" : 10,
"info" : [
"shy",
"helpful"
]
}

BSON文档就有点类似与 {‘name’:’xiaoming’,’age’:10,info:[‘shy’,’helpful’]}

mongoc有几种方式创建BSON文档: appending key-value pairs, using BCON, or parsing JSON
其中,最简单的就是BCON,不过再次之前先了解下使用 bson**,只需引用 bson.h 即可

bson
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 创建一个BSON文档
bson_t *doc=bson_new();
// 添加一个 UTF-8 字符串,-1 表示该字符串长度
bson_append_utf8(doc,"name",-1,"xiaodong",-1);
// 添加一个 int32 整数
bson_append_int32(doc,"age",-1,10);
BSON_APPEND_UTF8(doc,"name","xiaoqiang");
BSON_APPEND_INT32(doc,"age",11);
// 返回一个 JSON 字符串
char *jsonstr= bson_as_canonical_extended_json(doc,NULL);
printf(jsonstr)
// 释放由 bson_as_canonical_extended_json 返回char *
bson_free(jsonstr);
// 释放 bson_new 分配的内存
bson_destroy(doc);

结果
{ “name” : “xiaodong”, “age” : { “numberInt” : “10” }, “name” : “xiaoqiang”, “age” : { “numberInt” : “11” } }

可通过 bson** 系列函数 或者 BSON**宏 来添加BSON文档
通过 bson_t 获取 JSON 字符串,可以调用以下函数

1
2
3
char * bson_as_canonical_extended_json(const bson_t *bson, size_t *length)
char * bson_as_json(const bson_t *bson, size_t *length)
char * bson_as_relaxed_extended_json(const bson_t *bson, size_t *length)

必须要有对应的 bson_free() 来释放该分配的内存;bson_destroy() 来释放一个BSON文档

BCON

用 BCON_* 宏来操控BSON Document我认为更简单些,而且写法也更高级。

BSON C Object Notation, BCON for short, is an alternative way of constructing BSON documents in a manner closer to the intended format. It has less type-safety than BSON’s append functions but results in less code

1
2
3
4
5
6
7
8
9
bson_t *bson=BCON_NEW(
"name",BCON_UTF8("xiaoHong"),
"age",BCON_INT32(15),
"info",
"[",
"{","country",BCON_UTF8("China"), "}",
"{","phone",BCON_UTF8("110"), "}",
"]"
);

{ “name” : “xiaoHong”, “age” : { “$numberInt” : “15” }, “info” : [ { “country” : “China” }, { “phone” : “110” } ] }

通过使用 BCON_* 函数更直观、方便简单的创建BSON文档。

Creating BSON from JSON

上面介绍了创建一个BSON文档以及从BSON文档获取一个JSON字符串。
那么,我们是否可以通过JSON字符串反向解析一个BSON文档呢?答案是肯定的。不过,这好像是对于单文档而言,

1
bson_t * bson_new_from_json (const uint8_t *data, ssize_t len, bson_error_t *error);
1
2
3
4
5
6
const char *json = "{\"name\":\"xiaoJun\",\"age\":15}";
bson_t *newbs= bson_new_from_json((const uint8_t*)json,-1,NULL);
jsonstr=bson_as_canonical_extended_json(newbs,NULL);
printf(json);
bson_free(jsonstr);
bson_destroy(newbs);

有了这些基础后,那么接下来就不会那么吃力了

遍历 collections

在MongoDB交互shell中,我们可以直接通过 db.test.find().pretty() 来列出所有的数据。注意 find() 是可以提供参数的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
> db.test.find
function (query, fields, limit, skip, batchSize, options) {
var cursor = new DBQuery(this._mongo,
this._db,
this,
this._fullName,
this._massageObject(query),
fields,
limit,
skip,
batchSize,
options || this.getQueryOptions());
{
const session = this.getDB().getSession();
const readPreference = session._serverSession.client.getReadPreference(session);
if (readPreference !== null) {
cursor.readPref(readPreference.mode, readPreference.tags);
}
const readConcern = session._serverSession.client.getReadConcern(session);
if (readConcern !== null) {
cursor.readConcern(readConcern.level);
}
}
return cursor;
}

mongoc提供了 mongoc_collection_find_with_opts 函数来获取一个 游标(Cursor) ,注意 mongoc_collection_find 被弃用了。

1
mongoc_cursor_t * mongoc_collection_find_with_opts (mongoc_collection_t *collection,const bson_t *filter,const bson_t *opts,const mongoc_read_prefs_t *read_prefs)

mongoc_collection_find_with_opts 要求4个参数,参数我们只关注前两个参数,其中 bson_t *filter 至关重要,类似于 MYSQL select from tb_xxx *where id = 5

1
2
3
4
5
6
7
8
9
10
11
12
13
// 空BSON文档 {}
bson_t *bson=BCON_NEW(NULL);
mongoc_cursor_t *cursor= mongoc_collection_find_with_opts(coll,bson,NULL,NULL);
// 获取当前文档,注意是一个 const bson_t *,并传入 mongoc_cursor_next 第二个参数
const bson_t *ps=mongoc_cursor_current(cursor);
while (mongoc_cursor_next(cursor,&ps))
{
char *json=bson_as_canonical_extended_json(ps,&size);
printf(json);
bson_free(json);
}
// 释放 cursor
mongoc_cursor_destroy(cursor);

bson_t bson=BCON_NEW(NULL); 等价于 db.test.find({}) 或者 db.test.find()当然,也可以这么来写bson_t bson=BCON_NEW(“age”,”{“,”gt”,BCON_INT32(13),”}”); 这就类似于 db.test.find({‘age’:{‘gt’:13}})

增删查改

像大多数数据库一样,增删查改是必不可少的基本步骤。

insert
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MONGOC_EXPORT (bool)
mongoc_collection_insert (mongoc_collection_t *collection,
mongoc_insert_flags_t flags,
const bson_t *document,
const mongoc_write_concern_t *write_concern,
bson_error_t *error);
MONGOC_EXPORT (bool)
mongoc_collection_insert_one (mongoc_collection_t *collection,
const bson_t *document,
const bson_t *opts,
bson_t *reply,
bson_error_t *error);
MONGOC_EXPORT (bool)
mongoc_collection_insert_many (mongoc_collection_t *collection,
const bson_t **documents,
size_t n_documents,
const bson_t *opts,
bson_t *reply,
bson_error_t *error);

千万别被这么多参数给吓倒了。这里我们只需关注

mongoc_collection_t *collectionconst bson_t *document
bson_error_t *error(可选,表示错误信息)

下面是一个简单的例子,省略了大部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
bson_t *bson=BCON_NEW(
"name",BCON_UTF8("xiaoDong"),
"age",BCON_INT32(18),
"info",
"[",
"{","country",BCON_UTF8("China"), "}",
"{","phone",BCON_UTF8("150"), "}",
"]"
);
// 插入数据
retr=mongoc_collection_insert_one(coll,,NULL,NULL,&error);
if(!retr){
printf(error.message);
}
bson_destroy(bson);
delete
1
2
3
4
5
6
7
8
9
10
11
12
MONGOC_EXPORT (bool)
mongoc_collection_delete_one (mongoc_collection_t *collection,
const bson_t *selector,
const bson_t *opts,
bson_t *reply,
bson_error_t *error);
MONGOC_EXPORT (bool)
mongoc_collection_delete_many (mongoc_collection_t *collection,
const bson_t *selector,
const bson_t *opts,
bson_t *reply,
bson_error_t *error);

mongoc_collection_delete 已被弃用,这里没有列出
这个类似Insert,例子就没有了哈 😃

find

这个前面就是前面的 遍历collections ……

update
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
MONGOC_EXPORT (bool)
mongoc_collection_update (mongoc_collection_t *collection,
mongoc_update_flags_t flags,
const bson_t *selector,
const bson_t *update,
const mongoc_write_concern_t *write_concern,
bson_error_t *error);
MONGOC_EXPORT (bool)
mongoc_collection_update_one (mongoc_collection_t *collection,
const bson_t *selector,
const bson_t *update,
const bson_t *opts,
bson_t *reply,
bson_error_t *error);
MONGOC_EXPORT (bool)
mongoc_collection_update_many (mongoc_collection_t *collection,
const bson_t *selector,
const bson_t *update,
const bson_t *opts,
bson_t *reply,
bson_error_t *error);

注意那个 mongoc_update_flags_t,这个经常用到。

1
2
3
4
5
typedef enum {
MONGOC_UPDATE_NONE = 0,
MONGOC_UPDATE_UPSERT = 1 << 0,
MONGOC_UPDATE_MULTI_UPDATE = 1 << 1,
} mongoc_update_flags_t;

现在就 mongoc_collection_update 这个函数为例,除了那个 const mongoc_write_concern_t *write_concern 参数以外,其他的似乎都挺常用。。。不过再次之前,先来看看Mongo下update数据。

当前 db_test 存在如下数据

1
2
3
4
5
db.test.find()
{ "_id" : ObjectId("5a8e30c0d077dc147db71f83"), "name" : "xiaolong", "age" : 14 }
{ "_id" : ObjectId("5a8e3102d077dc147db71f84"), "name" : "xiaoqiang", "age" : 10 }
{ "_id" : ObjectId("5a8e3108d077dc147db71f85"), "name" : "xiaofeng", "age" : 12 }
{ "_id" : ObjectId("5a8e38ba53d6700f027556c2"), "name" : "xiaoHong", "age" : 15, "info" : [ { "country" : "China" }, { "phone" : "110"

我要查看 age 大或等于 12 的记录,那么我可以这样做

1
2
3
4
5
db.test.find({'age':{'$gte':12}})
>{ "_id" : ObjectId("5a8e30c0d077dc147db71f83"), "name" : "xiaolong", "age" : 14 }
"xiaofeng", "age" : 12 }
{ "_id" : ObjectId("5a8e38ba53d6700f027556c2"), "name" : "xiaoHong", "age" : 15, "info" : [ { "country" : "China" }, { "phone" : "110" } ] }

要修改 age 大或等于 12 的 name 的所有记录,那么我么可以这样做
db.test.update({‘age’:{‘gte’:12}},{‘set’:{‘name’:’XXXXX’}},false,true)

类似与MySQL:update tb_xxx set name=’xxxxx’ where age >=12

这时候回过头来看看

1
2
3
const bson_t *selector => {'age':{'$gte':12} }
const bson_t *update => {'$set':{'name':'XXXXX'}}
mongoc_update_flags_t flags => MONGOC_UPDATE_MULTI_UPDATE

于是可以这样写

1
2
3
4
5
6
7
8
9
10
11
12
13
bson_t *query=BCON_NEW("age","{",
"$gte",BCON_INT32(12),
"}");
bson_t *update=BCON_NEW("$set","{",
"age",BCON_INT32(888),
"}");
bool retr= mongoc_collection_update(coll,MONGOC_UPDATE_MULTI_UPDATE,query,update,NULL,&error);
if(!retr){
cout<<error.message<<endl;
}
bson_destroy(query);
bson_destroy(update);

注意,“age”,”{“ 要单独写,不能写成 “age{“,否则 src/bson/bcon.c:784 bcon_append_ctx_va(): precondition failed: type == BCON_TYPE_UTF8 这是最坑爹的地方

内存释放

别以为这样就结束了哦,还要记得要释放所有的内存

1
2
3
4
5
6
7
8
9
// 释放char*指针
bson_free(void *mem);
// 释放 bson_t *bson文档
bson_destroy(bson_t *bson);
mongoc_cursor_destroy(cursor);
mongoc_collection_destroy(coll);
mongoc_database_destroy(database);
mongoc_client_destroy(client);
mongoc_cleanup();

编译

编译方法也很简单

g++ main.cpp -o main pkg-config –libs –cflags libmongoc-1.0

或者

g++ main.cpp -o main -lbson-1.0 -lmongoc-1.0

结尾

不得不说,用mongoc来操控mongoDB数据库还挺复杂的。因此建议自己把这些函数封装成类对象。其实,关于mongoc封装成类,Mongoc目前已经发展到了MongoCXX,以Mongoc为基础,所有操作都被封装到类里面。引入了几个特殊的对象 view、value、view_or_value。同时,构建(Build)一个BSON文档有两种方法:basic、stream。可 参阅这里

以后有空再介绍***mongocxx***吧~

参考 http://mongoc.org/libmongoc/current/tutorial.html