java mongo arraylist save - java

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);

Related

Spring mongo repository slice

I am using spring-sata-mongodb 1.8.2 with MongoRepository and I am trying to use the mongo $slice option to limit a list size when query, but I can't find this option in the mongorepository.
my classes look like this:
public class InnerField{
public String a;
public String b;
public int n;
}
#Document(collection="Record")
punlic class Record{
public ObjectId id;
public List<InnerField> fields;
public int numer;
}
As you can see I have one collection name "Record" and the document contains the InnerField. the InnerField list is growing all the time so i want to limit the number of the selected fields when I am querying.
I saw that: https://docs.mongodb.org/v3.0/tutorial/project-fields-from-query-results/
which is exactly what I need but I couldn't find the relevant reference in mongorepository.
Any ideas?
Providing an abstraction for the $slice operator in Query is still an open issue. Please vote for DATAMONGO-1230 and help us prioritize.
For now you still can fall back to using BasicQuery.
String qry = "{ \"_id\" : \"record-id\"}";
String fields = "{\"fields\": { \"$slice\": 2} }";
BasicQuery query = new BasicQuery(qry, fields);
Use slice functionality as provided in Java Mongo driver using projection as in below code.
For Example:
List<Entity> list = new ArrayList<Entity>();
// Return the last 10 weeks data only
FindIterable<Document> list = db.getDBCollection("COLLECTION").find()
.projection(Projections.fields(Projections.slice("count", -10)));
MongoCursor<Document> doc = list.iterator();
while(doc.hasNext()){
list.add(new Gson().fromJson(doc.next().toJson(), Entity.class));
}
The above query will fetch all documents of type Entity class and the "field" list of each Entity class document will have only last 10 records.
I found in unit test file (DATAMONGO-1457) way to use slice. Some thing like this.
newAggregation(
UserWithLikes.class,
match(new Criteria()),
project().and("likes").slice(2)
);

How to get just the desired field from an array of sub documents in Mongodb using Java

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);
}`

How to convert Neo4j JSON to Java Object

I've been playing with Neo4j 2.0 RC1 for a couple of weeks. I'm writing a Spring Security implementation using Neo4j as the database. When I load a user, the response I get from Neo4j looks like this:
{
"columns" : [ "username", "password", "accountNonExpired","accountNonLocked", "credentialsNonExpired", "enabled" ],
"data" : [ [ "admin", "admin", true, false, true, false ]
}
Originally the only fields returned where username and password (both strings) and I was able to do this:
class Result
{
private List<String> columns = new ArrayList<String>();
private List<ArrayList<String>> data = new ArrayList<ArrayList<String>>();
};
ClientRespose resp = webResource.path(path).type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).post(ClientResponse.class, body );
String s = response.getEntity(String.class);
Result r = new Gson().fromJson(s, Result.class);
Of course when I added the other fields (booleans) I needed to change Result to look like this:
class Result
{
private List<String> columns = new ArrayList<String>();
private List<ArrayList<Object>> data = new ArrayList<ArrayList<Object>>();
};
My code still worked, but when I tried to cast any of the data items to String or Boolean I got a 'failed to cast Object to...' exception. This is of course because there is no type information, so GSon is creating Object instances to put everything it.
So I'm guessing there must be a better way to process the JSON that comes back from Neo4j?
Can I somehow skip the JSON conversion stage and get the Jersey HTTP client to populate my User objects directly?
The problem is that the data is not returned as map.
I usually do this:
get the result as Map
get the columns list
get the data nested list
using an for loop over data to get each row
using a for loop over columns and it's index to access the data in the row
You can also add a factory method to your user Object that gets one of these results rows and constructs your User object.
e.g.
class User {
public static User from(List<Object> row) {
User u=new User((String)row.get(0),(String)row.get(1));
u.setAccountNonExpired((Boolean)row.get(2));
u.setAccountNonLocked((Boolean)row.get(3));
u.setCredentialsNonExpired((Boolean)row.get(4));
u.setEnabled((Boolean)row.get(5));
return u;
}
}
Something you can try with Neo4j 2.0 is to use the transactional endpoint and return the node object (which will return just the node-properties as a map), which you then can map directly with Gson or Jackson to an object (the row).
match (u:User {username:{username}})
return u
otherwise you can also use the literal map syntax in Neo4j 2.0
match (u:User {username:{username}})
return {username : u.username, password: u.password, ....} as user

Morphia to return List of strings which are the fields of all documents

If I got a collection full of following elements
#Entity
public void MyEntity{
public String name;
public String type;
...
}
And I want to return a List<String> (or Set) of not the elements, but only their name fields.
List<String> allNames = datasotre.find(MyEntity.class).asList("name");
This is sample query, there is no such method of Morphia datastore.
To limit the fields returned call the "retrievedFields" method on Query. For example, to only get the name field of all MyEntity objects:
datastore.find(MyEntity.class).retrievedFields( true, "name").asList()
Edit - You can get a list Strings using the following query as long as you don't mind that the list will only contain unique values (i.e. no duplicate names):
DBCollection m = datastore.getCollection( MyEntity.class );
List names = m.distinct( "name", new BasicDBObject() );
The "names" list will only contain Strings.
The problem here is that there is no actual query for "keys". The queries all return "key/value pairs".
In theory, the fields in datastore.find() should map to the fields in MyEntity so you could just use reflection. However, if you have other people writing to the DB from different places they may have seeded extra tables.
If this is the case, you will need to run a Map/Reduce to get the list of all the "key" names.
There is a sample here.
You are looking for datastore.find(MyEntity.class).retrievedFields(true, "name").asList();. This will contain the _id and name attribute.
See http://code.google.com/p/morphia/wiki/Query#Ignoring_Fields

Retrieve sub-document in array as DBObject(s)

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();

Categories