couchbase
几天前,我在Twitter上开了一个玩笑 :
将Java从Couchbase迁移到MongoDB pic.twitter.com/Wnn3pXfMGi
— Tugdual Grall(@tgrall) 2015年1月26日
因此,我决定将其从简单的图片转移到真实的项目中。 让我们看一下这个所谓的项目的两个阶段:
- 将数据从Couchbase移至MongoDB
- 更新应用程序代码以使用MongoDB
查看此截屏视频以查看实际效果:
移动数据
我创建了一个复制服务器,该服务器使用Couchbase XDCR协议将文档取出并插入到MongoDB中。 该服务器使用此处提供的Couchbase CAPI Server项目。
该服务器将接收在Couchbase中进行的所有更改:
- 插入或更新文档后,将发送完整文档
- 删除文档后,仅发送元数据
-
replication server
将数据保存到MongoDB中(插入和/或更新-不删除),然后将该列表作为XDCR协议的一部分返回给Couchbase。
挑战之一是Couchbase没有“类型”或“集合”的概念。 您将所有内容都放在了存储桶中 ,应用程序代码知道如何处理数据。 不一定要有问题,只是实现的选择,但要编写工具时,有时会比预期的要难。 因此,这里是我在复制服务器中应用的逻辑,用于在有意义的情况下(并尽可能)将数据组织成多个集合:
- 如果JSON文档不包含类型字段 ,则所有文档将保存在一个集合中
- 如果JSON文档包含类型字段,则将为每种类型创建一个集合,并将在这些集合中插入/更新文档
- MongoDB不允许属性key具有。 和$符号,因此有必要使用其他字符更改名称。 这是在复制数据期间自动完成的。
所有这些以及更多内容都可以在该工具中进行配置。
如您在截屏中所见,这很简单。 (请注意,我只测试了非常简单的用例和部署)
您可以在此处下载该工具和源代码:
更新应用程序代码
下一步是在应用程序中使用这些数据。 为此,我仅使用Couchbase存储库上可用的Beer Sample Java应用程序。
我只是重新创建了项目并修改了一些东西,以使应用程序启动并运行:
- 更改连接字符串
- 删除生成视图的代码
- 用MongoDB操作替换设置/获取
- 通过简单查询替换对视图的调用
MongoDBeer应用程序的代码可在此处获得:
我没有更改任何业务逻辑,也没有添加功能,甚至没有替换导航和页面显示的方式。 我只关注数据库访问,例如:
// Couchbase Query
View view = client.getView("beer", "by_name");
Query query = new Query();
query.setIncludeDocs(true).setLimit(20);
ViewResponse result = client.query(view, query);
ArrayList<HashMap<String, String>> beers =
new ArrayList<HashMap<String, String>>();
for(ViewRow row : result) {
HashMap<String, String> parsedDoc = gson.fromJson(
(String)row.getDocument(), HashMap.class);
HashMap<String, String> beer = new HashMap<String, String>();
beer.put("id", row.getId());
beer.put("name", parsedDoc.get("name"));
beer.put("brewery", parsedDoc.get("brewery_id"));
beers.add(beer);
}
request.setAttribute("beers", beers);
// MongoDB Query
DBCursor cursor = db.getCollection("beer").find()
.sort( BasicDBObjectBuilder.start("name",1).get() )
.limit(20);
ArrayList<HashMap<String, String>> beers =
new ArrayList<HashMap<String, String>>();
while (cursor.hasNext()) {
DBObject row = cursor.next();
HashMap<String, String> beer = new HashMap<String, String>();
beer.put("id", (String)row.get("_id"));
beer.put("name", (String)row.get("name"));
beer.put("brewery", (String)row.get("brewery_id"));
beers.add(beer);
}
// Couchbase update
client.set(beerId, 0, gson.toJson(beer));
// MongoDB update
db.getCollection("beer").save(new BasicDBObject(beer));
我没有参加优化MongoDB代码的工作,只是为了替换尽可能少的代码行。
注意:在此过程中,我还没有创建任何索引。 显然,如果您的应用程序具有越来越多的数据,并且您对其进行了大量工作,则必须分析您的应用程序/查询以查看必须创建哪些索引。
增加新功能
将数据存入MongoDB之后,您可以做很多事情,而无需做任何其他事情,而不仅仅是MongoDB:
全文搜索
您可以在集合中的各个字段上创建文本索引,以为用户提供高级搜索功能。
db.brewery.ensureIndex(
{
"name" : "text",
"description" : "text"
},
{
"weights" :
{
"name" : 10,
"description" : 5
},
"name" : "TextIndex"
}
);
然后,您可以使用$text
操作查询数据库,例如所有有比利时且没有Ale的啤酒厂
db.brewery.find( { "$text" : { "$search" : "belgium -ale" } } , { "name" : 1 } );
{ "_id" : "daas", "name" : "Daas" }
{ "_id" : "chimay_abbaye_notre_dame_de_scourmont", "name" : "Chimay (Abbaye Notre Dame de Scourmont)" }
{ "_id" : "brasserie_de_cazeau", "name" : "Brasserie de Cazeau" }
{ "_id" : "inbev", "name" : "InBev" }
{ "_id" : "new_belgium_brewing", "name" : "New Belgium Brewing" }
{ "_id" : "palm_breweries", "name" : "Palm Breweries" }
一些分析
不确定这些查询是否真的有意义,但这只是表明现在您可以利用文档而无需任何第三方工具。
啤酒的种类(从最常见到较少):
db.beer.aggregate([
{"$group" : { "_id" : "$category","count" : {"$sum" : 1 } } },
{"$sort" : { "count" : -1 } },
{"$project" : { "category" : "$_id", "count" : 1, "_id" : 0 } }
]);
{ "count" : 1996, "category" : "North American Ale" }
{ "count" : 1468, "category" : null }
{ "count" : 564, "category" : "North American Lager" }
{ "count" : 441, "category" : "German Lager" }
...
...
例如,按啤酒厂的特定ABV的啤酒数量:啤酒产量最高的前3家啤酒厂,其abv等于或等于某个值,比方说5:
db.beer.aggregate([
... { "$match" : { "abv" : { "$gte" : 5 } } },
... { "$group" : { "_id" : "$brewery_id", "count" : { "$sum" : 1} }},
... { "$sort" : { "count" : -1 } },
... { "$limit" : 3 }
... ])
{ "_id" : "midnight_sun_brewing_co", "count" : 53 }
{ "_id" : "troegs_brewing", "count" : 33 }
{ "_id" : "rogue_ales", "count" : 31 }
地理空间查询
处理数据的第一件事是更改数据结构,以将各种数据保存为GeoJSON格式,为此,我们可以简单地在MongoDB Shell中使用脚本:
>mongo
use beers
db.brewery.find().forEach(
function( doc ) {
var loc = { type : "Point" };
if (doc.geo && doc.geo.lat && doc.geo.lon) {
loc.coordinates = [ doc.geo.lon , doc.geo.lat ];
db.brewery.update( { _id : doc._id } , {$set : { loc : loc } } );
}
}
);
db.brewery.ensureIndex( { "loc" : "2dsphere" } );
此调用将使用所有啤酒厂并添加一个新属性,名称loc
为GeoJSON点。 我还可以选择使用'$ unset'删除旧的地理信息,但我没有这样做; 让我们想象一些API /应用程序正在使用它。 这是灵活模式的一个很好的例子。
现在,我可以搜索距离旧金山金门大桥不到30公里的所有啤酒厂:[-122.478255,37.819929]
db.brewery.find(
{ "loc" :
{ "$near" :
{ "$geometry" :
{
"type" : "Point",
"coordinates" : [-122.478255,37.819929]
},
"$maxDistance" : 20000
}
}
}
, { name : 1 }
)
您还可以在上面使用的聚合查询中使用地理空间索引和运算符
结论
正如引言中所述,本周结束的项目在Twitter上只是一个玩笑而已,最后是一个小博客文章和Gitub存储库。
我的目的不是要比较这两种解决方案-我几个月前就做出了选择-只是简单地展示了如何轻松地从一个迁移到另一个,不仅是数据,还包括应用程序代码。
翻译自: https://www.javacodegeeks.com/2015/02/moving-my-beers-from-couchbase-to-mongodb.html
couchbase