Mongodb query for embedded doc - spring

I am having trouble to query that, I want to find all the docs that contains the id "5418a26ce4b0e4a40ea1d548" in the individualUsers field. would be esp useful if you know how to do that in Spring Data MongoDB query.
db.collection.find({individualUser:{"5418a26ce4b0e4a40ea1d548"}})
example of one doc
{ "_id" : ObjectId("5418c3b9e4b03feec4345602"), "creatorId" : "5418a214e4b0e4a40ea1d546", "individualUsers" : { "5418a26ce4b0e4a40ea1d548" : null, "5418a278e4b0e4a40ea1d54a" : null } }
Update #001
Entity code
#Document
class Idea{
#Id
String id;
String creatorId;
Map<String,String> individualUsers;
/*getter and setter omitted*/
}
Interface
public interface IdeaRepository extends MongoRepository<Idea,String> {
}
Update #002
So when spring-mongodb saves hashmap to json, it will look like
"individualUsers" : { "5418a26ce4b0e4a40ea1d548" : null, "5418a278e4b0e4a40ea1d54a" : null }
In java program I can easily get the data using the key value. but in the mongodb query, I can't query the key?
so the question is can I query inside the "individualUsers": {} with the key ??

Related

MongoDb Upsert in Spring removes properties set to null

Consider the following Java Class.
class Transaction {
#Id
public String id;
public String firstProperty;
public String secondProperty;
}
In Java following code is executed :
(mongoTemplate is of type Mongoperations)
Transaction transaction = new Transaction("T1");
transaction.setFirstProperty("first");
Query query = Query.query(Criteria.where("_id").is("T1"));
DBObject dbObject = new BasicDBObject();
mongoTemplate.getConverter().write(transactionInfo, dbObject);
Update update = Update.fromDBObject(dbObject);
mongoTemplate.upsert(query , update , TransactionInfo.class);
Following document is created.
{
_id:123,
firstProperty: "first"
}
If this piece of code is executed later :
Transaction transaction = new Transaction("T1");
transaction.setSecondProperty("second");
Query query = Query.query(Criteria.where("_id").is("T1"));
mongoTemplate.upsert(query , update , TransactionInfo.class);
Expected Document :
{
_id:123,
firstProperty: "first",
secondProperty: "second"
}
Actual Document:
{
_id:123,
secondProperty: "second"
}
From what I read in MongoDB docs I expect the document to be updated with "secondProperty" but it results in the removal of "firstProperty" . I think the document is getting created again, instead of getting updated. Please let me know if I am missing something.

MongoDB embedded Document Array: Get only one embedded document with a spezific attribute

I want to get one Embedded Document with a specific field (version) from an array with mongodb and spring boot.
This is the data structure:
{
"_id": 5f25882d28e40663719d0b52,
"versions": [
{
"versionNr": 1
"content": "This is the first Version of some Text"
},
{
"versionNr": 2
"content": "This is the second Version of some Text"
},
...
]
...
}
Here are my entities:
#Data
#Document(collection = "letters")
public class Letter {
#Id
#Field("_id")
private ObjectId _id;
#Field("versions")
private List<Version> versions;
}
//There is no id for embedded documents
#Data
#Document(collection = "Version")
public class Version{
#Field("content")
private String content;
#Field("version")
private Long version;
}
And this is the query that doesn't work. I think the "join" isn't correct. But can't figure out the right way.
public Optional<Version> findByIdAndVersion(ObjectId id, Long version) {
Query query = new Query(Criteria.where("_id").is(id).and("versions.version").is(version));
return Optional.ofNullable(mongoTemplate.findOne(query,Version.class,"letters"));
}
}
EDIT: This is a working Aggregation, I'm sure it isn't a pretty solution but it works
#Override
public Optional<Version> findByIdAndVersion(ObjectId id, Long version) {
MatchOperation match = new MatchOperation(Criteria.where("_id").is(id).and("versions.version").is(version));
Aggregation aggregate = Aggregation.newAggregation(
match,
Aggregation.unwind("versions"),
match,
Aggregation.project()
.andInclude("versions.content")
.andInclude("versions.version")
);
AggregationResults<Version> aggregateResult = mongoTemplate.aggregate(aggregate, "letters", Version.class);
Version version = aggregateResult.getUniqueMappedResult();
return Optional.ofNullable(mongoRawPage);
}
Query query = new Query(Criteria.where("_id").is(id).and("versions.version").is(version));
return Optional.ofNullable(mongoTemplate.findOne(query,Version.class,"letters"));
You are querying the Letter document but your entity class is specified as Version.class, since findOne from MongoDB doesn't return the subdocument by itself but rather the whole document, you need to have Letter.class as return type and filter (project) what fields to get back. So you can either project the single version subdocument that you want to receive, like so:
Query query = new Query()
.addCriteria(Criteria.where("_id").is(id).and("versions.version").is(version))
.fields().position("versions", 1);
Optional.ofNullable(mongoTemplate.findOne(query, Letter.class))
.map(Letter::getVersions)
.findFirst()
.orElse(null);
or use aggregation pipeline:
newAggregation(
Letter.class,
match(Criteria.where("_id").is(id)),
unwind("versions"),
replaceRoot("versions"),
match(Criteria.where("version").is(version))),
Version.class)
Note -- I typed this on a fly.

Field with #Field not getting translated to the correct value

I have a document with one of the fields' names overridden by #Field:
public User {
#Id
private String id;
private String username;
#Field("profiles")
private List<BusinessProfile>
businessProfiles;
...
}
And a aggregation operation with a match operation as follows:
match(where("businessProfiles.services").elemMatch(Criteria.where("category").is(serviceCategory)))
However, in the query that this ultimately generates, the businessProfiles is not traslated to profiles. Here is the query I got from the log files:
Executing aggregation: [ { "$match" : { "businessProfiles.services" : { "$elemMatch" : { "category" : "Cloud_Initiation"}}}} ...]
This behavior seems very odd. Is this supposed to work this way? Thanks.
Field mapping is only done for TypedAggregation providing the mapping source type.
TypedAggregation<Product> agg = newAggregation(User.class,
match(where("businessProfiles.services")...
I created DATAMONGO-2310 to improve the documentation in that area.

Lite DB not finding inner object query

I have two objects.
[DataContract]
public class Record
{
[DataMember]
public string Id { get; set; }
}
And this class:
public class BatteryStatus : Record
{
[DataMember]
public DateTime RetrieveTime { get; set; }
}
I'm using Lite DB as a local NoSQL option to query and save the data. I'm needing to find and delete the values based after some time. Here's my code doing so:
var col = db.GetCollection<BatteryStatus>(CollectionName);
var test = col.FindAll()
.Where(x => x.Id == status.Id).ToList();
var result = col.Find(Query.EQ("Id", status.Id.ToString())).ToList();
Test returns with the with the object, but the result value doesn't. Lite DB only uses the Query or the BSONId as a way to delete an object. I don't have a BSON id attached to it (it's a referenced definition so I can't change it).
How can I use the "Query" function in order to get a nested value so I can delete it?
Classes has properties, BSON documents has fields. By default, LiteDB convert all property names to same name in BSON document except _id field which is document identifier.
If you want query using Linq, you will use properties expressions. If you are using Query object class, you must use field name.
var result = col.FindById(123);
// or
var result = col.FindOne(x => x.Id == 123);
// or
var result = col.FindOne(Query.EQ("_id", 123));
Find using _id always returns 1 (or zero) document.
I figured out the problem with LiteDB, since I was using the property name of "Id", the BSON interpreted that as the "_id" of the JSON object, and merging their two values. I solve the issue by renaming the "Id" property to something else.

Spring Mongo mapping variable data

I'm using Spring Data MongoDB for my project. I work with a mongo database containing a lot of data, and I want to map this data within my Java application.
The problem I have is that some data back in time had a different structure.
For example sport_name is an array now, while in some old records is a String:
sport_name: "Soccer" // Old data
sport_name: [ // Most recent entries
{
"lang" : "en",
"val" : "Soccer"
},
{
"lang" : "de",
"val" : "Fussball"
}
]
Here is what I have until now:
#Document(collection = "matches")
public class MatchMongo {
#Id
private String id;
private ??? sport_name; // Best way?!
(What's the best way to)/(How would you) handle something like this?
If old data can be considered as "en" language, then separate structure can be used to store localized text:
class LocalName {
private String language;
private String text;
// getters/setters
}
So mapped object will store the collection of localized values:
public class MatchMongo {
// it can also be a map (language -> text),
// in this case we don't need additional structure
private List<LocalName> names;
}
This approach can be combined with custom converter, to use plain string as "en" locale value:
public class MatchReadConverter implements Converter<DBObject, MatchMongo> {
public Person convert(DBObject source) {
// check what kind of data located under "sport_name"
// and define it as "en" language text if it is an old plain text
// if "sport_name" is an array, then simply convert the values
}
}
Custom mapping is described here in details.
Probably you can write a utility class which will fetch all the data where sport_name is not an array and update the element sport_name to array. But this all depends on the amount of data you have.
You can use query {"sport_name":{$type:2}}, here 2 stands for String.
Refer for more details on $type: http://docs.mongodb.org/manual/reference/operator/query/type/

Resources