How to optimize MongoDB find query? - mongodb

I have a mongo read query which looks like following.
data class Resource( val assignmentId: String, val referrerId: String, val studentIds: Set<String>)
fun findSubmissions(resources: List<Resource>): Flux<Submission> {
val criteria = resources.map { (assignmentId, referrerId, students) ->
Criteria.where("assignmentId").`is`(assignmentId).and("referrerId").`is`(referrerId).and("studentId").`in`(students)
}.toTypedArray()
val query = Query().addCriteria(Criteria().orOperator(*criteria))
return template.find(query, Submission::class.java)
}
}
Document schema of Submission looks like this
{
assignmentId: "",
referrerId: "",
studentId: "",
status: ""
}
Looks like this query is affecting the performance and hogging the CPU, which could be because of the way OR clause has been written.
Any ideas how this query can be rewritten in a better way?

Related

How to get String representation of ObjectId from response body?

I am trying to create a simple API which works with MongoDB documents created from this class:
#Document()
data class Student(#Id val id: ObjectId?,
val firstName: String,
val secondName: String) {}
And I have a REST controller which returns me Student documents.
{
"id": {
"timestamp": 1657005140,
"date": "2022-07-05T07:12:20.000+00:00"
},
"firstName": "Test",
"secondName": "Test"
}
But I also need a controller which returns me documents by id. How can I put this JSON id with timestamp and date in a request param like /getByName?id= ? Maybe there is a way to get an ID in one-string representation?
The problem is that my ObjectId was serialised.
To get simple one-string value I needed just to add: #JsonSerialize(using = ToStringSerializer::class)
So my document looks like this:
#Document()
data class Student(
#Id
#JsonSerialize(using = ToStringSerializer::class)
val id: ObjectId?,
val firstName: String,
val secondName: String) {}

Error inserting embedded documents into MongoDB using Scala

I use mongo-scala-driver 2.9.0 and this is a function saving a user's Recommendation List to MongoDB. The argument streamRecs is An Array of (productId:Int, score:Double). Now i want to insert a document consisting of an useId and its relevant reconmendation list recs. However, there is an error in the line val doc:Document = Document("userId" -> userId,"recs"->recs). Does anyone know what goes wrong?
def saveRecsToMongoDB(userId: Int, streamRecs: Array[(Int, Double)])(implicit mongoConfig: MongoConfig): Unit ={
val streamRecsCollection = ConnHelper.mongoClient.getDatabase(mongoConfig.db).getCollection(STREAM_RECS_COLLECTION)
streamRecsCollection.findOneAndDelete(equal("userId",userId))
val recs: Array[Document] = streamRecs.map(item=>Document("productId"->item._1,"score"->item._2))
val doc:Document = Document("userId" -> userId,"recs"->recs)
streamRecsCollection.insertOne(doc)
}
the document i want to insert into MongoDB is like this(it means an
user and his recommendation products and scores):
{
"_id":****,
"userId":****,
"recs":[
{
"productId":****,
"score":****
},
{
"productId":****,
"score":****
},
{
"productId":****,
"score":****
},
{
"productId":****,
"score":****
}
]
}
When creating a BSON document, declare the Bson type explicitly for each value in the key/value pair, like so:
/* Compose Bson document */
val document = Document(
"email" -> BsonString("email#domain.com"),
"token" -> BsonString("some_random_string"),
"created" -> BsonDateTime(org.joda.time.DateTime.now.toDate)
)
To see an example, please check https://code.linedrop.io/articles/Storing-Object-in-MongoDB.

Find entities containing a sublist of elements from an embedded array in MongoDB with Spring Data MongoDB

I have a MongoDB entity that has a list of tags:
#Document
#TypeAlias("project")
data class Project(#Id var id: String?,
val name: String,
val tags: MutableList<Tag>)
I would like to look for Project's containing a subset of the tags.
For example given these proyects:
projectService.save(Project(null, "someProject11",
mutableListOf(Tag("area","IT"), Tag("department","Architecture"))))
projectService.save(Project(null, "someProject12",
mutableListOf(Tag("area","IT"), Tag("department","HR"))))
If I define my repository as follows:
interface ProjectRepository : ReactiveCrudRepository<Project, String> {
fun findByTags(tags : List<Tag>): Flux<Project>
}
Looking for a subset doesn't work, e.g: projects with Tag("area","IT") should return two results but actually returns 0. The underlying mongo query is:
{"find": "project", "filter": {"tags": [{"key": "area", "value": "IT"}]}
It only works passing the complete content of the list listOf(Tag("area","IT"), Tag("department","Architecture")):
{"find": "project", "filter": {"tags": [{"key": "area", "value": "IT"},{"key": "department", "value": "Architecture"}]}
How can I query for entities containing a subset of the list?
Solved using Criteria and the $elemMatch operator:
interface CustomProjectRepository {
fun findByTags(tags: List<Tag>): Flux<Project>
}
class CustomProjectRepositoryImpl(private val mongoTemplate: ReactiveMongoTemplate) : CustomProjectRepository {
override fun findByTags(tags: List<Tag>): Flux<Project> {
val query = Query()
val criterias = mutableListOf<Criteria>()
tags.forEach {
criterias.add(Criteria.where("tags").elemMatch(Criteria.where("key").`is`(it.key).and("value").`is`(it.value)))
}
query.addCriteria(Criteria().andOperator(* criterias.toTypedArray()))
return mongoTemplate.find(query, Project::class.java)
}
}
interface ProjectRepository : ReactiveCrudRepository<Project, String>, CustomProjectRepository {
fun findByClientIdAndId(clientId: String, id: String): Mono<Project>
fun findByClientId(clientId: String): Flux<Project>
}

SpringBoot ReactiveMongoTemplate updating document partially

I am working on a kotlin reactive spring-boot mongodb project. I'm trying to update a document but it does not work well.
My problem is pretty similar to the following question in stackoverflow.
Spring reactive mongodb template update document partially with objects
So I have a document in mongo
{
"id": 1,
"name": "MYNAME",
"email": "MYEMAIL",
"encryptedPassword": "12345",
...........................
}
And when I call PATCH on the uri localhost:8080/user/1 with the one of following header
{
"name": "NEW NAME"
}
{
"email": "NEW EMAIL"
}
I want to update my document with received fields only.
My handler code
fun update(serverRequest: ServerRequest) =
userService
.updateUser(serverRequest.pathVariable("id").toLong(), serverRequest.bodyToMono())
.flatMap {
ok().build()
}
My Service Implement code
override fun updateUser(id: Long, request: Mono<User>): Mono<UpdateResult> {
val changes = request.map { it -> PropertyUtils.describe(it) }
val updateFields: Update = Update()
changes.subscribe {
for (entry in it.entries) {
updateFields.set(entry.key, entry.value)
}
}
return userRepository.updateById(id, updateFields)
}
My repository code
fun updateById(id: Long, partial: Update) = template.updateFirst(Query(where("id").isEqualTo(id)), partial, User::class.java)
My user code
#Document
data class User(
#Id
val id: Long = 0,
var name: String = "",
val email: String = "",
val encryptedPassword: ""
)
I have followed the advice that Spring reactive mongodb template update document partially with objects gave.
My code do updates, but it updates to the initial constructor of my User class.
Could anyone help with this?
I guess you should consider this problem as a general problem of patching an object in Java/Kotlin. I found an article about this: https://cassiomolin.com/2019/06/10/using-http-patch-in-spring/#json-merge-patch. Even if you won't update partially an object, there should not be such a big impact on performance of your application.
I figured out how to partially update my data.
First I changed the body request to string. (using bodyToMono(String::class.java)
Then I changed the changed JSON string to JSONObject(org.json).
And for each of its JSONObject's key I created Update that will be the partial data to update my entity.
Following is how I implemented this.
override fun updateUser(id: Long, request: Mono<String>): Mono<UpdateResult> {
val update = Update()
return request.map { JSONObject(it) }
.map {
it.keys().forEach { key -> update.set(key, it[key]) }
update
}
.flatMap { it -> userRepository.updateById(id, it) }
}
Please share more idea if you have more 'cleaner' way to do this. Thank you

mongoengine filter on DictField's dynamic keys

class UserThings(DynamicDocument):
username = StringField()
things = DictField()
dcrosta_things = UserThings(username='dcrosta')
dcrosta_things.things['foo'] = 'bar'
dcrosta_things.things['bad'] = 'quack'
dcrosta_things.save()
Results in a MongoDB document like:
{ _id: ObjectId(...),
_types: ["UserThings"],
_cls: "UserThings",
username: "dcrosta",
things: {
foo: "bar",
baz: "quack"
}
}
Im using mongoengine and I'm not able to query on dict field's keys.
for e.g I have a list of things thing_list = ['foo', 'faa', 'baz', 'xyz']
and I want to filter all UserThings that contains anyof these things...
something like .. UserThings.objeect.filter(things__in=thing_list)
definitely this wont work. IS there any way to perform filtering on variable/dynamic keys on dictfield. if not, can we do it using pymongo/raw query?