I'm very new to MongoDB, and I'm using it along with the Java driver. I have this document structure:
{ "_id" : ObjectId("4f7d2ba6fd5a306d82687d48"), "room" : "Den" }
{ "_id" : ObjectId("4f7d2baafd5a306d82687d49"), "room" : "Foyer" }
{ "_id" : ObjectId("4f7d2fdcfd5a306d82687d4a"), "room" : "Master Bedroom" }
{ "_id" : ObjectId("4f7d301afd5a306d82687d4b"), "room" : "Guest Bedroom" }
{ "_id" : ObjectId("4f7d2b98fd5a306d82687d47"), "code" : "A", "lights" : [ { "name" : "Overhead", "code" : "1" } ], "room" : "Kitchen" }
Where the last line is of particular interest in illustrating what I want to do. Each document is a room and may have a "lights" key corresponding to a value that is an array of sub-documents. From a modeling perspective, I have a house, which has 0-n rooms, each of which has 0-n lights in it. What I want to do in Java is take the name of the room as a parameter, and return a collection of DBObject corresponding to the sub-documents in the lights array -- "get me all lights for room 'kitchen'", for example.
So far, proceeding incrementally in TDD style, I've constructed this query:
public static final String ROOM_KEY = "room";
public static final String EQUALS_KEY = "$eq";
private BasicDBObject buildRoomNameQuery(String roomName) {
BasicDBObject myQuery = new BasicDBObject();
myQuery.put(ROOM_KEY, new BasicDBObject(EQUALS_KEY, roomName));
return myQuery;
}
I realize that this is going to get me the entire room document for the room name I pass in. I'm a bit stuck on what the best way to proceed from here is to get what I want. Is what I'm doing even possible with a simple query, or will I have to retrieve the array and iterate through it in code, casting the elements as DBObject? I'm also open to suggestions for a better document structure for my purpose -- I'm not married to this structure by any means.
For a bit of perspective, I'm quite well versed in SQL and traditional relational databases, if that helps in terms of explanatory analogies. Also, if I'm butchering the MongoDB terminology, please correct me. Thanks in advance.
So, you can do something like this:
DBCollection coll = db.getCollection("test");
BasicDBObject query = new BasicDBObject("room", "Kitchen");
// optional, limit the fields to only have the lights field
BasicDBObject fields = new BasicDBObject("lights",1).append("_id",false);
DBCursor curs = coll.find(query, fields);
while(curs.hasNext()) {
DBObject o = curs.next();
// shows the whole result document
System.out.println(o.toString());
BasicDBList lights = (BasicDBList) o.get("lights");
// shows the lights array -- this is actually a collection of DBObjects
System.out.println(lights.toString());
// optional: break it into a native java array
BasicDBObject[] lightArr = lights.toArray(new BasicDBObject[0]);
for(BasicDBObject dbObj : lightArr) {
// shows each item from the lights array
System.out.println(dbObj);
}
}
Also, I recommend using the QueryBuilder in the Java driver--it's a bit more concise than creating Queries from DBObjects. Even better, check out Morphia, which is an object mapper that uses the Java driver. It natively supports entity models that have lists in them, and serializes/deserializes them to Mongo without needing to deal with the DBObject stuff.
Look at spring mongo package. A really good way to work with mongo using POJO documents
http://www.springsource.org/spring-data/mongodb
You will not need to perform casting and work with strings
You can use an iterator for the fields
Iterator<DBObject> fields = curs.iterator();
while(fields.hasNext()){
DBObject field = (DBObject) fields.next().get("lights");
System.out.println(field.get("name"));
}
For newer versions, consider the use of the Document. To avoid unchecked casts and linter warnings, along with writing your own loop, use the libary's get(final Object key, final Class<T> clazz) method:
List<Document> comments = posts.get("comments", docClazz)
where docClazz is something that you create once:
final static Class<? extends List> docClazz = new ArrayList<Document().getClass();
Related
I have just started using Mongo Db . Below is my data structure .
It has an array of skillID's , each of which have an array of activeCampaigns and each activeCampaign has an array of callsByTimeZone.
What I am looking for in SQL terms is :
Select activeCampaigns.callsByTimeZone.label,
activeCampaigns.callsByTimeZone.loaded
from X
where skillID=50296 and activeCampaigns.campaign_id= 11371940
and activeCampaigns.callsByTimeZone='PT'
The output what I am expecting is to get
{"label":"PT", "loaded":1 }
The Command I used is
db.cd.find({ "skillID" : 50296 , "activeCampaigns.campaignId" : 11371940,
"activeCampaigns.callsByTimeZone.label" :"PT" },
{ "activeCampaigns.callsByTimeZone.label" : 1 ,
"activeCampaigns.callsByTimeZone.loaded" : 1 ,"_id" : 0})
The output what I am getting is everything under activeCampaigns.callsByTimeZone while I am expecting just for PT
DataStructure :
{
"skillID":50296,
"clientID":7419,
"voiceID":1,
"otherResults":7,
"activeCampaigns":
[{
"campaignId":11371940,
"campaignFileName":"Aaron.name.121.csv",
"loaded":259,
"callsByTimeZone":
[{
"label":"CT",
"loaded":6
},
{
"label":"ET",
"loaded":241
},
{
"label":"PT",
"loaded":1
}]
}]
}
I tried the same in Java.
QueryBuilder query = QueryBuilder.start().and("skillID").is(50296)
.and("activeCampaigns.campaignId").is(11371940)
.and("activeCampaigns.callsByTimeZone.label").is("PT");
BasicDBObject fields = new BasicDBObject("activeCampaigns.callsByTimeZone.label",1)
.append("activeCampaigns.callsByTimeZone.loaded",1).append("_id", 0);
DBCursor cursor = coll.find(query.get(), fields);
String campaignJson = null;
while(cursor.hasNext()) {
DBObject campaignDBO = cursor.next();
campaignJson = campaignDBO.toString();
System.out.println(campaignJson);
}
the value obtained is everything under callsByTimeZone array. I am currently parsing the JSON obtained and getting only PT values . Is there a way to just query the PT fields inside activeCampaigns.callsByTimeZone .
Thanks in advance .Sorry if this question has already been raised in the forum, I have searched a lot and failed to find a proper solution.
Thanks in advance.
There are several ways of doing it, but you should not be using String manipulation (i.e. indexOf), the performance could be horrible.
The results in the cursor are nested Maps, representing the document in the database - a Map is a good Java-representation of key-value pairs. So you can navigate to the place you need in the document, instead of having to parse it as a String. I've tested the following and it works on your test data, but you might need to tweak it if your data is not all exactly like the example:
while (cursor.hasNext()) {
DBObject campaignDBO = cursor.next();
List callsByTimezone = (List) ((DBObject) ((List) campaignDBO.get("activeCampaigns")).get(0)).get("callsByTimeZone");
DBObject valuesThatIWant;
for (Object o : callsByTimezone) {
DBObject call = (DBObject) o;
if (call.get("label").equals("PT")) {
valuesThatIWant = call;
}
}
}
Depending upon your data, you might want to add protection against null values as well.
The thing you were looking for ({"label":"PT", "loaded":1 }) is in the variable valueThatIWant. Note that this, too, is a DBObject, i.e. a Map, so if you want to see what's inside it you need to use get:
valuesThatIWant.get("label"); // will return "PT"
valuesThatIWant.get("loaded"); // will return 1
Because DBObject is effectively a Map of String to Object (i.e. Map<String, Object>) you need to cast the values that come out of it (hence the ugliness in the first bit of code in my answer) - with numbers, it will depend on how the data was loaded into the database, it might come out as an int or as a double:
String theValueOfLabel = (String) valuesThatIWant.get("label"); // will return "PT"
double theValueOfLoaded = (Double) valuesThatIWant.get("loaded"); // will return 1.0
I'd also like to point out the following from my answer:
((List) campaignDBO.get("activeCampaigns")).get(0)
This assumes that "activeCampaigns" is a) a list and in this case b) only has one entry (I'm doing get(0)).
You will also have noticed that the fields values you've set are almost entirely being ignored, and the result is most of the document, not just the fields you asked for. I'm pretty sure you can only define the top-level fields you want the query to return, so your code:
BasicDBObject fields = new BasicDBObject("activeCampaigns.callsByTimeZone.label",1)
.append("activeCampaigns.callsByTimeZone.loaded",1)
.append("_id", 0);
is actually exactly the same as:
BasicDBObject fields = new BasicDBObject("activeCampaigns", 1).append("_id", 0);
I think some of the points that will help you to work with Java & MongoDB are:
When you query the database, it will return you the whole document of
the thing that matches your query, i.e. everything from "skillID"
downwards. If you want to select the fields to return, I think those will only be top-level fields. See the documentation for more detail.
To navigate the results, you need to know that a DBObjects are returned, and that these are effectively a Map<String,
Object> in Java - you can use get to navigate to the correct node,
but you will need to cast the values into the correct shape.
Replacing while loop from your Java code with below seems to give "PT" as output.
`while(cursor.hasNext()) {
DBObject campaignDBO = cursor.next();
campaignJson = campaignDBO.get("activeCampaigns").toString();
int labelInt = campaignJson.indexOf("PT", -1);
String label = campaignJson.substring(labelInt, labelInt+2);
System.out.println(label);
}`
In mySQL the describe statement can be used to retrieve the schema of a given table, unfortunately I could not locate a similar functionality for the MongoDB java driver :(
Let's say I have I have the flowing BSON documents:
{
_id: {$oid:49},
values: { a:10, b:20}
}
,
{
_id: {$oid:50},
values: { b:21, c:31}
}
Now let's suppose I do:
DBObject obj = cursor.next();
DBObject values_1 = (DBObject) obj.get("values");
and the schema should be something like this:
>a : int
>b : int
and for the next document:
DBObject obj = cursor.next();
DBObject values_2 = (DBObject) obj.get("values");
the schema should be:
>b : int
>c : int
Now that I explained what the schema retrial is, can some1 be nice and tell me how to do it?
If it helps, In ma case I only need to know the field names (because the datatype is always the same, but it would be nice also know how to retrieve the datatypes).
A work arround, is convert the DBObject to a Map, then the Map to a Set, the Set to an Iterator and extract the attribute names/values... Still have now idea how to extract data types.
this is:
DBObject values_1 = (DBObject) obj.get("values");
Map _map = values_1.toMap();
// Set set = _map.entrySet(); // if you want the <key, value> pairs
Set _set_keys = _map.keySet();
Iterator _iterator = _set_keys.iterator();
while (_iterator.hasNext())
System.out.println("-> " + _iterator.next());
A DBObject has a method called keySet (documentation). You shouldn't need to convert to a Map first.
There's no exposed method to determine the underlying BSON data type at this point, so you'd need to investigate using instanceof or getClass to determine the underlying data type of the Object that is returned from get.
If you look at the source code for BasicBSONObject for example, you'll see how the helper functions that cast do some basic checks and then force the cast.
--- Business
--- Sub-Business
--- Organization Group
I have a data structure like this that I want to persist into Mongo.
I do not want to persist duplicate sub-businesses and I don't want to persist duplicate Organization Groups. I'm using the Business as the "_id" so there will not be any duplicates there.
These are two classes, persist is in one class and the other methods are in another. I mean the code, works but its not giving my multiple groups.
I may not understand Mongo/Java too well to get it to do what I want.
Here is what I have
public void persist(Business business) {
DB db = pool.getConnection();
LOG.info("Business toString: " + business.toString()); // REMOVE AFTER
try {
db.requestStart();
DBObject dbObjSetColumns = writeConverter.convert(business);
DBCollection dbCol = db.getCollection(BUSINESS_COLLECTION);
DBObject update = new BasicDBObject("$set",dbObjSetColumns);
DBObject query = new BasicDBObject("_id", business.getName());
LOG.debug(query.toString());
LOG.debug(update.toString());
dbCol.update(query, update, true, false);
} catch (Exception ex) {
throw new RuntimeException(ex);
} finally {
if (db != null) {
db.requestDone();
}
}
}
public DBObject convert(Business business) {
if (business == null) {
return null;
}
final DBObject dbObj = new BasicDBObject();
this.addSubBusinesses(business,dbObj);
return dbObj;
}
private void addSubBusinesses(Business b, DBObject dbObj){
BasicDBObject subBusiness = new BasicDBObject();
for(SubBusiness sub : b.getSubBusinesses()){
subBusiness.put("name", sub.getName());
if(sub.getGroups() != null){
this.addGroupsForSubBusiness(sub, subBusiness);
}
}
dbObj.put("subBusiness", subBusiness);
}
private void addGroupsForSubBusiness(SubBusiness sub, DBObject dbObj){
BasicDBList groups = new BasicDBList();
final DBObject dbGroupObj = new BasicDBObject();
dbGroupObj.put("group", sub.getName());
groups.add(dbGroupObj);
dbObj.put("groups", groups);
}
Here is what I'm receiving. Fortunately it doesn't seem to be duplicating the subBusiness, but I do have multiple groups, that is not showing up here.
/* 0 */
{
"_id" : "ABC",
"subBusiness" : {
"name" : "subBus",
"groups" : [{
"group" : "Ground"
}]
}
}
/* 1 */
{
"_id" : "XYZ",
"subBusiness" : {
"name" : "Sub2",
"groups" : [{
"group" : "Air"
}]
}
}
To put it in perspective on how this works,
We persist one Business Object, that has one Sub-Business and one Group. The database may already know about the Business object, if that is the case then we do not save it twice. The same logic applies for sub business and group. The collection should only show us the unique business information.
Please let me know if there is any more information I can provide. I've been running in circles for a day now trying to get this seemingly easy task done.
Everything except for the addSubBusiness and addGroupForSubBusiness methods were already present, I'm just working on adding this new part, so I am refactoring this to meet my needs.
I'm not familiar with your classes, but it does seem like your addGroupsForSubBusiness method needs to change to something like:
private void addGroupsForSubBusiness(SubBusiness sub, DBObject dbObj){
BasicDBList groups = new BasicDBList();
for (Group group: sub.getGroups()) {
DBObject dbGroupObj = new BasicDBObject();
dbGroupObj.put("group", group.getName());
groups.add(dbGroupObj);
}
dbObj.put("groups", groups);
}
}
Similar change in your subBusiness method since as you currently have it, your method loops through the subBusinesses for your Business, but only adds one object, the last subBusiness, at end.
Will you also persist only once for the Business? Or could you call persist multiple times for a Business, as in first time for a Business "ABC", with a subBusiness "sub1", and then another time for Business "ABC", and subBusiness "sub2", in which case, you want to append to an existing document. If your Business object is complete (as in the object contains all its subBusinesses and Groups when you call persist), then I think the method changes mentioned above should help. Also, you can use the save() method since your determining existence on the _id field. http://docs.mongodb.org/manual/reference/method/db.collection.save/
If you require only one sub-business and one group, then you would be better off restructuring your document to something like this:
{_id:"business_val", sub_business:"sub_business_val", group:"group_val"}
Your code should look like:
new BasicDBObject().append("_id", "business_val").append("sub_business", "sub_business_val").append("group", "group_val");
Additionally, you need to ensure unique index on the collection using:
db.[your_collection].ensureIndex({_id:1,sub_business:1,group:1},{unique:true})
I have List of complex object and the complex object contains more than two fields. Something like - {car1, car2, car3} and car has name and type field
Is there a simpler way of inserting a list of car? Something like
DBObject updateObject = new BasicDBObject().append("$push", new BasicDBObject().append("cars", cars)
I tried with $pushAll and it does not seems to be work. I did bit more research and I found it needs information about mapping and that is one of the reasons why this insertion is failing.
What's the best way to do this insertion into MongoDB? Some sample code or direction would be helpful. Please note this has to be done through Java.
Well, if you don't want to translate your car objects to DBObjects manually, there are Mapping Frameworks out there. like Morphia.
Personally, I would just wire up a mapping method manually though. Code could look like this (untested, typos to be expected)
BasicDBObject updateObject = new BasicDBObject();
BasicDBList dbCarList = mapCars(cars);
updateObject.append("$push", new BasicDBObject("cars", dbCarList));
...
private BasicDBList mapCars(List<Car> cars) {
BasicDBList result = new BasicDBList();
for (Car car: cars) {
BasicDBObject dbCar = new BasicDBObject();
dbCar.append("name", car.getName());
result.add(dbCar);
}
return result;
}
Update: as Sammaye pointed out in the comments, replace $push with $set for replacing the list. $push will append non-existing elements to the array without removing what's there form before.
We are using a Java server and Mongo DB [plain Java-Mongo and not Morphia or some such tool for the CRUD].
We are having a Image Pojo class and its related Metadata like below,
public class Img{
private String name;
private List<Metadata> imgMetaList = new ArrayList<Metadata>();
//Getters, setters etc...
public List<Metadata> getImgMetaList() {
return imgMetaList;
}
}
Metadata class has some data, implementing Serializable didn't work, so
i extended ReflectionDBObject,
public class Metadata extends ReflectionDBObject{
private String tag;
private String val;
//Getters, setters etc...
}
I want to save the Img into Mongo. I used the following code and it worked.
BasicDBObject updateQuery = new BasicDBObject();
updateQuery.put("name", img.getName());
BasicDBObject updMetadata = new BasicDBObject();
updMetadata.put("$push", new BasicDBObject("imgMetaList",img.getImgMetaList()) );
collection.update(updateQuery,updMetadata, true, false);
This inserts a document in Mongo as below,
{
"_id" : ObjectId("503a1991db2e9f431cf0d162"),
"name" : "test.jpg",
"imgMetaList" : [
[
{
"Tag" : "tag1",
"Val" : "val1",
"_id" : null
}
]
]
}
Here there are 2 issues,
1. The code is inserting two square brackets instead of one to house the array
2. why isn't the _id getting generated for the list.
Please let me know.
Regards, Vish
$push appends a value to an array. If the array does not exist yet, a new array is created. The item you are pushing is an array, so you end up with an array inside an array.
An _id is not being generated for the collection because this is normal behaviour for MongoDB. When you put a sub-document in an document, the sub-document does not get an _id. Hence the array does not have an _id.
Perhaps instead you are asking why your metadata document, inside the array, has a null _id. This is a behaviour of the ReflectionDBObject class: it includes an _id field, but it is up to you to set its value. You can set this _id to a non-null value by calling set_id() on your ReflectionDBObject instance. A good place to do this might be in the constructor of your Metadata class. To generate an _id, use the ObjectId.get() static method (org.bson.types.ObjectId).
Your code for inserting the image in MongoDB is more complicated than it needs to be. The following will work (and will get rid of the nested array):
BasicDBObject imageDoc = new BasicDBObject();
imageDoc("name", img.getName());
imageDoc("imgMetaList", img.getImgMetaList());
collection.insert(imageDoc);