MongoDB append simple java data types as 'Object' to a document fields - mongodb

I'm just starting with MongoDB.
I have a Map<String, Object> that contains several data types as a values. For example:
{ "price":235837210, "name":"hello", "position":1, "onAir":false, ... }
Now I'm trying to append these elements to a MongoDB Document as follows:
for (Map.Entry<String, Object> entry : map.entrySet()) {
document.append(entry.getKey(), entry.getValue());
}
As a result I get the following exception:
org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class [Ljava.lang.Object;.
The use case is much more complicated, but the question is if I can do this in any way in order to avoid specific casting. Something like:
document.append("price", (long)map.get("price"));
Thanks,
Joan.

Sorry, it was a problem with using Object[] instead of ArrayList.

Related

ReactiveMongoRepository / MongoRepository does not return _id field

I think this issue probably has to do with my Mongo Document Koltin Data class, but for our business case we need to allow the user to add on any JSON fields to describe their RF data set.
Extending the BasicDBObject was the best way I have found.
The mono being returned when I save a SigMfMetaDocument does not contain the _id field.
I cannot figure out why the save method does not return a Mono wrapping a SigMfDocument with and _id
If there is a better way to create a Type for ReactiveMongoRepository that can dynamically accept any fields I am all ears.
#Document(collection = "sigmfmeta")
class SigMfMetaDocument : BasicDBObject {
#Id
#JsonProperty("id")
val id: String? = UUID.randomUUID().toString()
constructor(map: Map<String, Any>) : super(map)
constructor() : super()
constructor(key: String, value: Object): super()
}
#Repository
interface SigMfMetaRepository : ReactiveMongoRepository<SigMfMetaDocument, String>
So I found a way to solve this for my use case. I was originally assuming the description in the documentation for the save method would apply
(Saves a given entity. Use the returned instance for further operations as the save operation might have changed the entity instance completely).
My thought Mongo auto inserting the _id value would apply to this description.
I changed my model to:
#Document(collection = "sigmfmeta")
class SigMfMetaDocument : BasicBSONObject {
constructor(map: Map<String, Any>) : super(map) {
val id = ObjectId()
this.put("_id", id)
}
constructor() : super()
}
This way I have the _id value after saving for some business logic. Again I defined my Model this way because the metadata file we are accepting needs to allow a client to add any fields they wish to describe a binary file of RF measurement data.

Foreign Key Object Missing in LINQ GroupBy?

I am using GroupBy in my LINQ queries. My query is working fine, except my foreign key objects are missing. Then, I tried to add Include in my query. Following is my code:
public ActionResult GetEmployees()
{
var Emloyees = db.Employees.AsQueryable();
Emloyees.GroupBy(e=> new {e.JobTitleId, e.GenderId})
.Select(tr => new MyObject
SalaryTotal = tr.Sum(r=>r.Salary)
}).Include(tr=>tr.JobTitle).Include(tr=>tr.Gender).ToList();
}
I am getting this exception:
The result type of the query is neither an EntityType nor a CollectionType with an entity element type. An Include path can only be specified for a query with one of these result types.
I tried to add it before GroupBy and directly in db.Employees.AsQueryable(), but nothing worked. What am I doing wrong?
The problem is that you are doing a projection with the .Select(...), after which your includes cannot be resolved. The result of that query will be a list MyObject, which does not work for includes that are actually on Employee.
So Try it like this:
Emloyees
.GroupBy(e=> new {e.JobTitleId, e.GenderId})
.Select(tr => new MyObject {
SalaryTotal = tr.Sum(r=>r.Salary),
JobTitle = tr.FirstOrDefault().JobTitle,
Gender = tr.FirstOrDefault().Gender
}).ToList();
You will have to extend MyObject with 2 additional properties, but the result of your query will be what you want (I assume).

How to save semi-structured data in MongoDB

I'm trying to figure out how to persist a semi-structured document to MongoDB, using Spring Mongo. Here's an example of what I'm trying to accomplish:
{
"name": "Test",
"admin": false,
"unstructured_field_one": "Some arbitrary data"
}
I know how to do this with fully unstructured data as a field in a parent document, where I can just use something like
//...
private Object someRandomObject;
But how can I accomplish a semi-structured document (at parent level), where I have, as in the example, name and admin as required fields, and whatever else comes in along with the request gets added automagically?
You can do it without any pojo, just with a Json Parser(Jackson) and MongoTemplate. Since MongoTemplate can save any DbObject, You need to convert your json to a DBObject.
Something like this will do
ObjectMapper mapper = new ObjectMapper();
TypeReference<Map<String,Object>> typeRef
= new TypeReference<Map<String,Object>>() {};
Map<String,Object> map = mapper.readValue(json, typeRef);
DBObject dbObject = new BasicDBObject(map);
mongoTemplate.getCollection("blahblah").save(dbObject);

Custom expression in JPA CriteriaBuilder

I have an Entity with a String field (storing JSON), and need to compare value from its database column with another value. Problem is that type of this database column is TEXT, but in fact it contains JSON. So, is there a way to write something like this? I.e. I need to compare my value with some field of JSON from TEXT column.
criteriaBuilder.equal(root.get("json_column").customExpressionn(new Expression{
Object handle(Object data){
return ((Object)data).get("json_field")
}
}), value)
Assuming, you have a MySQL server with version > 5.7.x
I just had the same issue. I wanted to find all entities of a class that had a JSON field value inside a JSON object column.
The solution that worked for me was something along the lines (sorry typing from a phone)
(root, query, builder)->{
return builder.equal(
builder.function("JSON_EXTRACT", String.class, root.get("myEntityJsonAttribute"), builder.literal("$.json.path.to.json.field")),
"searchedValueInJsonFieldOfJsonAttribute"
)
}

CouchDB Map/Reduce view query from Ektorp

I'm trying to execute a query from java against a Map/Reduce view I have created on the CouchDB.
My map function looks like the following:
function(doc) {
if(doc.type == 'SPECIFIC_DOC_TYPE_NAME' && doc.userID){
for(var g in doc.groupList){
emit([doc.userID,doc.groupList[g].name],1);
}
}
}
and Reduce function:
function (key, values, rereduce) {
return sum(values);
}
The view seems to be working when executed from the Futon interface (without keys specified though).
What I'm trying to do is to count number of some doc types belonging to a single group. I want to query that view using 'userID' and name of the group as a keys.
I'm using Ektorp library for managing CouchDB data, if I execute this query without keys it returns the scalar value, otherwise it just prints an error saying that for reduce query group=true must be specified.
I have tried the following:
ViewQuery query = createQuery("some_doc_name");
List<String> keys = new ArrayList<String>();
keys.add(grupaName);
keys.add(uzytkownikID);
query.group(true);
query.groupLevel(2);
query.dbPath(db.path());
query.designDocId(stdDesignDocumentId);
query.keys(keys);
ViewResult r = db.queryView(query);
return r.getRows().get(0).getValueAsInt();
above example works without 'keys' specified.
I have other queries working with ComplexKey like eg:
ComplexKey key = ComplexKey.of(userID);
return queryView("list_by_userID",key);
but this returns only a list of type T (List) - using CouchDbRepositorySupport of course - and cannot be used with reduce type queries (from what I know).
Is there any way to execute the query with reduce function specified and a complex key with 2 or more values using Ektorp library? Any examples highly appreciated.
Ok, I've found the solution using trial and error approach:
public int getNumberOfDocsAssigned(String userID, String groupName) {
ViewQuery query = createQuery("list_by_userID")
.group(true)
.dbPath(db.path())
.designDocId(stdDesignDocumentId)
.key(new String[]{userID,groupName});
ViewResult r = db.queryView(query);
return r.getRows().get(0).getValueAsInt();
}
So, the point is to send the complex key (not keys) actually as a single (but complex) key containing the String array, for some reason method '.keys(...)' didn't work for me (it takes a Collection as an argument). (for explanation on difference between .key() and .keys() see Hendy's answer)
This method counts all documents assigned to the specific user (specified by 'userID') and specific group (specified by 'groupName').
Hope that helps anybody executing map/reduce queries for retrieving scalar values from CouchDB using Ektorp query.
Addition to Kris's answer:
Note that ViewQuery.keys() is used when you want to query for documents matching a set of keys, not for finding document(s) with a complex key.
Like Kris's answer, the following samples will get document(s) matching the specified key (not "keys")
viewQuery.key("hello"); // simple key
viewQuery.key(documentSlug); // simple key
viewQuery.key(new String[] { userID, groupName }); // complex key, using array
viewQuery.key(ComplexKey.of(userID, groupName)); // complex key, using ComplexKey
The following samples, on the other hand, will get document(s) matching the specified keys, where each key may be either a simple key or a complex key:
// simple key: in essence, same as using .key()
viewQuery.keys(ImmutableSet.of("hello"));
viewQuery.keys(ImmutableSet.of(documentSlug1));
// simple keys
viewQuery.keys(ImmutableSet.of("hello", "world"));
viewQuery.keys(ImmutableSet.of(documentSlug1, documentSlug2));
// complex key: in essence, same as using .key()
viewQuery.keys(ImmutableSet.of(
new String[] { "hello", "world" } ));
viewQuery.keys(ImmutableSet.of(
new String[] { userID1, groupName1 } ));
// complex keys
viewQuery.keys(ImmutableSet.of(
new String[] { "hello", "world" },
new String[] { "Mary", "Jane" } ));
viewQuery.keys(ImmutableSet.of(
new String[] { userID1, groupName1 },
new String[] { userID2, groupName2 } ));
// a simple key and a complex key. while technically possible,
// I don't think anybody actually does this
viewQuery.keys(ImmutableSet.of(
"hello",
new String[] { "Mary", "Jane" } ));
Note: ImmutableSet.of() is from guava library.
new Object[] { ... } seems to have same behavior as ComplexKey.of( ... )
Also, there are startKey() and endKey() for querying using partial key.
To send an empty object {}, use ComplexKey.emptyObject(). (only useful for partial key querying)