I work with Spring Boot and MongoDB. I am looking for a solution to get a list of hobbies but not all of them. The hobbies have to meet the conditions (name or description have to include searched phrase).
@Document("persons")
@Data
class Person {
@Id
private String personId;
private String name;
private List<Hobby> hobbies;
}
@Data
class Hobby {
private String name;
private String description;
private String notImportantField;
}
Example
I want to get a person with reduced list of hobbies (all the hobbies have to contain searched phrase in one of its field).
Person document from the database
{
"_id" : ObjectId("id1"),
"name" : "some person",
"hobbies" : [
{
"name" : "A",
"description" : "AB",
"notImportantField" : "ABCDEF"
},
{
"name" : "ABC",
"description" : "ABCD",
"notImportantField" : "ABCDEF"
}
]
}
What I want to receive:
- I want person with id
id1
and I am looking for phraseab
in person's hobbies. I should get a list with 2 hobbies (first hobby's description containsab
, second hobby's name and description containsab
) - I want person with id
id1
and I am looking for phrased
in person's hobbies. I should get a list with 1 hobby (second hobby's description containsd
)
I tried something like this but I get a person with all of its hobbies.
@Repository
interface PersonRepository extends MongoRepository<Person, String> {
@Query("{'$and': ["
"{'_id': :#{#personId}},"
"{'$or':["
"{'hobbies.name': {$regex: :#{#searchPhraseRegex}, $options: 'i'}},"
"{'hobbies.description': {$regex: :#{#searchPhraseRegex}, $options: 'i'}}"
"]}"
"]}")
List<Person> method(@Param("personId") String personId, @Param("searchPhraseRegex") String searchPhraseRegex);
}
The result method should return a person with filtered hobbies or only list of hobbies. Thanks in advance for help.
UPDATE: RESOLVED
Thanks @user20042973 for the help :) I used your query and change it a little to match my query in Mongo repository. It works as I expected. The result method is:
@Repository
interface PersonRepository extends MongoRepository<Person, String> {
@Aggregation(pipeline = {
"{'$match': {'_id': :#{#personId}}}",
"{'$addFields': {'hobbies': {'$filter': {"
"'input': '$hobbies', "
"'cond': "
"{'$or': ["
"{'$regexMatch': {'input': '$$this.name', 'regex': :#{#searchPhraseRegex}, 'options': 'i' }},"
"{'$regexMatch': {'input': '$$this.description', 'regex': :#{#searchPhraseRegex}, 'options': 'i' }}"
"]}"
"}}}}"
})
Optional<Person> findPersonByIdAndFilterHobbies(@Param("personId") String personId, @Param("searchPhraseRegex") String searchPhraseRegex);
}
CodePudding user response:
Helpfully, getting a filtered list of objects from an array can be done using the $filter
operator. The operator takes an array as input and can process each item individually via an expression. In your situation that expression would include a $regexMatch
to look for the value in the string(s) of interest. So the stage would look something like this:
{
"$addFields": {
hobbies: {
$filter: {
input: "$hobbies",
cond: {
$or: [
{
$regexMatch: {
input: "$$this.name",
regex: "d",
options: "i"
}
},
{
$regexMatch: {
input: "$$this.description",
regex: "d",
options: "i"
}
}
]
}
}
}
}
}
Sample Mongo Playground example is here.
From your question it's not quite clear if you will separately be using this as selection criteria for the document itself. I've left the leading $match
stage to just use _id
for the moment, but of course you can add any other filters to it (such as 'hobbies.name': /d/i
) as needed.
I'm not personally familiar enough with Spring Boot to say what the exact syntax is to create such a pipeline, but I do know that aggregations are supported.