Naming a Query Methods for MongoRepository with multiple conditions in SpringBoot - spring-boot

According to the documentation, I'm trying to get a working name for a method of a Spring MongoRepository that have to meet two different conditions. In plain words, the query sounds like: "look up for a determinate object by his id and it is one of mine or I want to share it"
The object:
{
"_id" : ObjectId("5c497..."),
"owner" : "myUserId",
"shared" : false,
}
The query:
db.getCollection('collection').find({$or : [
{ $and : [ { '_id' : ObjectId("5c497...") }, { 'owner' : 'myUserId' } ] },
{ $and : [ { '_id' : ObjectId("5c497...") }, { 'shared' : true } ] }
]})
I solved the problem with #Query
#Query("{\$or: [{\$and: [{'_id' : ?0}, {'owner' : ?1}]}, {\$and: [{'_id' : ?0}, {'shared' : true}]} ]}")
fun findIfAvailable(id: ObjectId, owner: String, shared: Boolean = true): Mono<MyObj>
But now, I wondered if it's possible to writing a method name for simplify the code ( and learn how use it ).
I tried without success findByIdAndOwnerOrShared, findByIdAndOwnerOrIdAndShared and so on
Thank you

Related

How to search through nested array and retreive only matched elements with mongo and springdata [duplicate]

This question already has answers here:
Find in Double Nested Array MongoDB
(2 answers)
Spring data Match and Filter Nested Array
(1 answer)
Closed 3 years ago.
I'm looking to search into my collection and retreive only element who matched Criteria.
Here is my collection :
{
"_id" : "id",
"name" : "test",
"groupUsers" : [
{
"name" : "blabla",
"toys" : [
{
"createdAt" : ISODate("2019-10-30T12:59:41.409Z"),
},
{
"createdAt" : ISODate("2019-11-30T12:59:10.409Z"),
},
{
"createdAt" : ISODate("2019-12-30T12:59:12.409Z"),
}
],
"createdAt" : ISODate("2019-10-30T12:33:39.036Z")
},
{
"name" : "blabla2",
"toys" : [
{
"createdAt" : ISODate("2019-10-32T12:59:41.409Z"),
},
{
"createdAt" : ISODate("2019-11-30T12:59:56.409Z"),
},
{
"createdAt" : ISODate("2019-12-30T12:59:15.409Z"),
}
],
"createdAt" : ISODate("2019-10-32T12:33:39.036Z")
}
],
}
I want to retreive the whole collection but it depends when the user was added to the group for example, user blabla2 (in the example above) will only get the whole group but with only the two last toys of the first user in the response.
Anyway, I guess it's something really basic but I don't know why I can't figure it out.
What I'm Doing
I'm doing a first query to get the current user and get when he was added in the group (notice that the date gets converted into java Date Util here).
Aggregation groupAgg = newAggregation(match(Criteria.where("_id").is(groupId).and("groupUsers.userId").is(userId)));
GroupUser groupUser = mongoTemplate.aggregate(groupAgg, Group.class, GroupUser.class).getUniqueMappedResult();
In a second query, I want to get the whole document but only with the Criteria that I define before.
MatchOperation matchedGroup = match(new Criteria("_id").is(groupId));
MatchOperation matchedToys = match(
new Criteria("groupUsers.toys.createdAt").gte(groupUser.getCreatedAt()));
Aggregation aggregation = newAggregation(matchedGroup, matchedToys);
AggregationResults<Group> result = mongoTemplate.aggregate(aggregation, Group.class, Group.class);
Group group = result.getUniqueMappedResult();
This query doesn't work, and I'm looking to something like even if there is no match (for example, none toys has been created yet), it still return the group basic response and not null.
Maybe I need to unwind the nested array ?
Any help is appreciate. I'm using spring data.
Try this query
db.testers.aggregate([
{
$addFields:{
"groupUsers":{
$map:{
"input":"$groupUsers",
"as":"doc",
"in":{
$mergeObjects:[
"$$doc",
{
"toys":{
$filter:{
"input":"$$doc. toys",
"as":"sn",
"cond": {
"$and": [
{ "$gte": [ "$$sn.createdAt", ISODate('2015-06-17T10:03:46.000Z') ] },
]
}
}
}
}
]
}
}
}
}
}
]).pretty()

OrOperator in MongoTemplate not returning values?

I am working on Spring MVC with MongoDB.
I have added criteria for some fields with orOperator, Each Fields has value in database, but List returning is Empty.
criteriaQuery.addCriteria(Criteria.where("First_Name").is(docName.trim())
.orOperator(Criteria.where("Middle_Name").is(docName.trim())
.orOperator(Criteria.where("Last_Name").is(docName.trim()))));
Query printing in console is
Query: { "First_Name" : "ESTHER" , "$or" : [ { "Middle_Name" : "ESTHER" , "$or" : [ { "Last_Name" : "ESTHER"}]}]}, Fields: null, Sort: null
I tried the Like query, but that also results the same
criteriaQuery.addCriteria(Criteria.where("First_Name").regex(docName)
.orOperator(Criteria.where("Middle_Name").regex(docName))
.orOperator(Criteria.where("Last_Name").regex(docName)));
Mongo Query
db.doctor_details.find({ "$or" : {"First_Name" : { "$regex" : "ESTHER"} }, "$or" : [ { "Middle_Name" : { "$regex" : "ESTHER"} , "$or" : [ { "Last_Name" : { "$regex" : "ESTHER"}}]}]})
Help me to get the result, I dont know where my code goes wrong. Help is appreciated!
Thanks
As far as I remember in order to use orOperator you should do:
Query query = new Query(new Criteria().orOperator(criteriaV1,criteriaV2));
it is based on this answer

Springdata mongodb aggregation match

After asking question to understand a bit more of the aggregation framework in MongoDB I finally found the way to do aggregation for my need (thanks to a StackExchange user)
So basically here is a document from my collection:
{
"_id" : ObjectId("s4dcsd5s4d6c54s6d"),
"items" : [
{
type : "TYPE_1",
text : "blablabla"
},
{
type : "TYPE_2",
text : "blablabla"
},
{
type : "TYPE_3",
text : "blablabla"
},
{
type : "TYPE_1",
text : "blablabla"
},
{
type : "TYPE_2",
text : "blablabla"
},
{
type : "TYPE_1",
text : "blablabla"
}
]
}
The idea was to be able to filter only some elements of my collections (avoiding Type 2 and 3). In fact I have more than 30 types and 6 are not allowed but for simplicity I made this example.
So the aggregation command in command line is this one:
db.history.aggregate([{
$match: {
_id: ObjectId("s4dcsd5s4d6c54s6d")
}
}, {
$unwind: '$items'
}, {
$match: {
'items.type': { '$nin': [ "TYPE_2" , "TYPE_3"] }
}
},
{ $limit: 10 }
]);
With this I am able to retrieve the 10 elements items of this document which do not match TYPE_2 and TYPE_3
However when I am using spring data there is no output. I looked a bit at the example to build mine but its still not working.
So I did:
Aggregation aggregation = newAggregation(
match(Criteria.where("id").is(myID)),
unwind("items"),
match(Criteria.where("items.type").nin(ignoreditemstype)),
limit(3),
skip(offsetLong)
);
AggregationResults<PersonnalHistory> results = mongAccess.getOperation().aggregate(query,
"items", PersonnalHistory.class);
PersonnalHistory is marked with annotation #Document(collection = "history") and id with the #id annotation
ignoreditemstype is a list containing TYPE_2 and TYPE_3
Here is what I have in the toString method of aggregation:
{
"aggregate" : "__collection__" ,
"pipeline" : [
{ "$match": { "id" : "s4dcsd5s4d6c54s6d"} },
{ "$unwind": "$items"},
{ "$match": { "items.type": { "$nin" : [ "TYPE_2" , "TYPE_3" ] } } },
{ "$limit" : 3},
{ "$skip" : 0 }
]
}
I tried a lot of stuff (to have at least an answer :) ) like removing id or the nin:
aggregation = newAggregation(
unwind("items"),
match(Criteria.where("items.type").nin(ignoreditemstype)),
limit(3),
skip(offsetLong)
);
aggregation = newAggregation(
match(Criteria.where("id").is(myid)),
unwind("items")
);
For information when I do a simple query like:
query.addCriteria(Criteria.where("id").is(myID));
My document is returned. However I have thousands of items. So I just want to have the 15 first (in fact the 15 first are the 15 last added)
Do you maybe see what I am doing wrong?
Yeah looks like you are passing simple String while it is expecting ObjectId
Aggregation aggregation = newAggregation(
match(Criteria.where("_id").is(new ObjectId(myID))),
unwind("items"),
match(Criteria.where("items.type").nin(ignoreditemstype)),
limit(3),
skip(offsetLong)
);
Now the question is why it works with simple query, my answer would be because spring-data driver is not that mature at least not with aggregation pipeline.

Elastic search Update by Query to Update Complex Document

I have a use case of elastic search to update a doc.
My doc is something like this-
{
"first_name" : "firstName",
"last_name" : "lastName",
"version" : 1234,
"user_roles" : {
"version" : 12345,
"id" : 1234,
"name" : "role1"},
},
"groups" : {
"version" : 123,
"list": [
{"id":123, "name" : "ashd"},
{"id":1234, "name" : "awshd"},
]
}
}
Now depepeding on some feed I will either will be updating the parent doc or will be updating the nested doc.
I am able to find how to update the basic attributes like firstName and lastName but unable to get how to update complex/nested ones
I did something like from REST client-
"script": {
"inline": "ctx._source.user_roles = { "id" : 5678, "name" :"hcsdl"}
}
but its giving me exception-
Actual use case-
I will actually be getting a Map in java.
This key can be simple key like "first_name" or can be complex key like "user_role" and "groups"
I want to update the document using update by query on version.
The code I wrote is something like-
for (String key : document.keySet()) {
String value = defaultObjectMapper.writeValueAsString(document.get(key));
scriptBuilder.append("ctx._source.");
scriptBuilder.append(key);
scriptBuilder.append('=');
scriptBuilder.append(value);
scriptBuilder.append(";");
}
where document is the Map
Now I might get the simple fields to update or complex object.
I tried giving keys like user_roles.id and user_roles.name and also tried giving complete user_roles but nothing is working.
Can someone helpout
Try this with groovy maps instead of verbatim JSON inside your script:
"script": {
"inline": "ctx._source.user_roles = [ 'id' : 5678, 'name' : 'hcsdl']}
}

Spring Data Rest and excerpt Projections

I am using the inlineAddress sample of the Spring Data Rest documentation.
/persons return the address inline as expected.
Now I add a projection to the AddressRepository
#RepositoryRestResource(excerptProjection = AddressProjection.class)
Which is as below
#Projection(name = "AddressesProjection", types = Address.class)
public interface AddressProjection {
public String getStreet();
}
This is causing the /persons call to have an address projection as _embedded
{
"_embedded" : {
"persons" : [ {
"firstName" : "dfdf",
"lastName" : "2",
"addresses" : [ {
"street" : "tx",
"state" : "tx",
"country" : "dfd"
} ],
"_embedded" : {
"addresses" : [ {
"street" : "tx",
"_links" : {
"self" : {
"href" : "/api/addresses/1{?projection}",
"templated" : true
}
}
} ]
},
"_links" : {
"self" : {
"href" : " api/persons/1{?projection}",
"templated" : true
},
"addresses" : {
"href" : " /api/persons/1/addresses"
}
}
} ]
}
}
I dont know if this is expected. This behaviour is causing repeated information when I have a oneToMany relation like order/Comments and have projection on both order and comments and when I access order/1/comments I see the order also embedded for each comments.
I have a similar issue with spring-data-rest 2.5.6. So I'd like to add this.
If :
an A entity has a #OneToMany relationship to a B entity
the B entity's repository #RepositoryRestResource contains an excerptProjection
Then spring-data-rest will embed the B entity's list in any A entity (in _embedded).
If there is no excerptProjection, the list won't be embedded.
I'd like to be able to choose what I want to be embedded, but at moment, I found no solution to do so.
For anybody that is looking for the answer to this issue, I actually did find a solution.
Based on the example in Spring-RestBucks you will need to have a custom RepresentationModelProcessor on A entity.
Also consider the official Spring HATEOAS Documentation on RepresentationModelProcessor.
Applying to the above example, you would do:
public class PersonRepresentationProcessor implements RepresentationModelProcessor<EntityModel<Person>> {
private final EntityLinks entityLinks;
#Override
public EntityModel<Person> process(EntityModel<Person> personModel) {
// create new EntityModel without the embedded collection
TypedEntityLinks<Person> typedPersonLink = entityLinks.forType(Person::getId);
EntityModel<Person> newPerson = new EntityModel<>(personModel.getContent(),
personModel.getLinks());
// add more links or other modifications
return newPerson;
}
}

Resources