Mongo Repository find by condition is not working - java

I have a MongoRepository query method which needs to fetch data based on conditions.
I have the following record in database.
{
'name' : 'test',
'show' : false,
'free' : true
}
And the following query, But the query doesn't return this record.
repositiry.findByNameNotNullAndShowIsTrueOrFreeIsTrue()
as per the condition, Name is not null and Free is True. But Why am I not getting the record.

I reproduced your scenario. It's working here. The query logged was correct.
{
"$or": [
{
"name": {
"$ne": null
},
"show": true
},
{
"free": true
}
]
}
To enable the mongodb query logs you must DEBUG MongoTemplate.
logging.level.org.springframework.data.mongodb.core.MongoTemplate = DEBUG
Entity
#Document
#Data
#Builder
class Entity {
private String id;
private String name;
private boolean show;
private boolean free;
}
Repository
interface EntityRepository extends MongoRepository<Entity,String> {
List<Entity> findByNameIsNotNullAndShowIsTrueOrFreeIsTrue();
}
Test
#Test
public void testQuery() {
repository.deleteAll();
Entity entity = Entity.builder()
.free(true)
.show(false)
.name("test")
.build();
repository.save(entity);
List<Entity> entities = repository.findByNameIsNotNullAndShowIsTrueOrFreeIsTrue();
Assert.assertEquals(1, entities.size());
}

Related

Elasticsearch query in spring by POJO

I am creating an application that does not have a database inside (the database is located on the server that I will access). In this regard, I need to create an elastic search query based on the POJO object I have. For example, I have a class:
#AllArgsConstructor
class PersonalData{
private String lastName;
private String name;
}
and
#AllArgsConstructor
class Person{
private long id;
private PersonalData personalData;
}
Let's say there are objects of these classes:
Person person = new Person(1, new PersonalData("lastName", "name"));
From these classes, I would like to create an elastic query, for example:
"query": {
"bool": {
"filter": [
{
"term": {
"person.id.value": {
"value": 0,
"boost": 1.0
}
}
},
{
"wildcard": {
"person.personalData.lastName.value": {
"value": "lastName",
"boost": 1.0,
}
}
},
{
"wildcard": {
"person.personalData.name.value": {
"value": "name",
"boost": 1.0
}
}
}
]
}
}
I can create a request in this format like this:
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery()
.filter(
QueryBuilders.termQuery("person.id.value", person.getId()))
.filter(QueryBuilders.wildcardQuery("person.personalData.lastName.value", person.getPersonalData().getLastName()))
.filter(QueryBuilders.wildcardQuery("person.personalData.lastName.value", person.getPersonalData().getName())));
SearchSourceBuilder result = new SearchSourceBuilder();
result.query(queryBuilder);
However, this method does not suit me, I need to create a request based on a java object.
Further, I will need to transfer the resulting request in SUCH FORM via API to another back. Please help me, how can I get this request?

Nested class object with Spring data MongoDb aggregation returns null field

I have an issue problem with the inclusion of nested class in the aggregation.
This is a preview of the json document in my collection :
{
"id": "1234",
"typeApp": "API",
"name": "name",
"versionNum": "1",
"release": {
"author": "name",
//some other data
}
}
The document java class :
#Document(collection = "myClassExamples")
public class MyClassExampleDocument {
#Id
private String id;
private String typeApp;
private String name;
private String versionNum;
private Release release;
public static class Release {
private String author;
//Other fields...
}
}
I am trying to build a query, to find the last documents group by a given typeApp in parameter, and sort by versionNum DESC to get the new one by typeApp.
I started with an easier query, a simple group by typeApp :
Aggregation aggregation = newAggregation(
Aggregation.sort(Sort.Direction.DESC, "versionNum"),
Aggregation.group("typeApp"),
project(MyClassExampleDocument.class)
)
The query returns a list of MyClassExampleDocument, with all fields with null values except for the id which is populated with the typeApp.
Do you know how to build the aggregation in order to get the entire object, as stored in my collection ?
Thanks for the help !
You can use like following
public List<MyClassExampleDocument> test() {
Aggregation aggregation = Aggregation.newAggregation(
sort(Sort.Direction.DESC, "versionNum"),
group("typeApp").first("$$ROOT").as("data")
replaceRoot("data")
).withOptions(AggregationOptions.builder().allowDiskUse(Boolean.TRUE).build());
return mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(MyClassExampleDocument.class), MyClassExampleDocument.class).getMappedResults();
}
here is the aggregation
db.collection.aggregate([
{ "$sort": { versionNum: -1 } },
{
"$group": {
"_id": "$typeApp",
"data": { "$first": "$$ROOT" }
}
},
{ "$replaceRoot": { "newRoot": "$data" } }
])
Working Mongo playground
Note : The java code was not tested, it was implemented based on working mongo script

Spring Boot, MongoDB, Pageable, sort by a custom method in the object

Say for example I have the following setup,
A model like this:
public class Post {
#Id
private String id;
private String post;
private List<Vote> votes = new ArrayList<>();
// Getters & Setters...
public double getUpVotes() {
return votes.stream().filter(vote -> vote.getDirection() == 1).mapToInt(Vote::getDirection).count();
}
}
and
public class Vote {
private short direction;
// Getters & Setters...
}
and then a repository like this
#Repository
public interface PostRepository extends PagingAndSortingRepository<Post, String> {
List<Post> findAll(Pageable pageable);
}
And say I want to sort the posts by the result of the getter method getUpVotes()
I've tried the following localhost:3005/opinion?page=0&size=20&sort=upVotes but its doesn't work.
The sort document can specify ascending or descending sort on existing fields ...
https://docs.mongodb.com/manual/reference/method/cursor.sort/#sort-asc-desc
Workaround: You can perform MongoDB aggregation where you can add new field with calculated value and order by this value:
db.post.aggregate([
{
$addFields: {
upVotes: {
$size: {
$filter: {
input: "$votes.direction",
cond: {
$eq: [ "$$this", 1 ]
}
}
}
}
}
},
{
$sort: {
upVotes: 1
}
}
])
MongoPlayground | $project
Spring data
#Autowired
private MongoTemplate mongoTemplate;
...
Aggregation aggregation = Aggregation.newAggregation(addFields, sort);
List<Post> result = mongoTemplate
.aggregate(aggregation, mongoTemplate.getCollectionName(Post.class), Post.class)
.getMappedResults();

How to access sub-document in mongoDB with condition in spring-boot program

I want to write a spring-boot program to get values of name, id, and key where abc.active is true. I have written some code
#Repository
public interface SwitchRepoDao extends MongoRepository< SwitchRepo, String> {
public List<SwitchRepo> findByAbc_active(boolean active);
}
also, I have written class for interface.
#Document(collection="switchrepo")
public class SwitchRepo{
#Id
private String id;
private String type;
private List<Abc> abc;
// with getters and setters also constructors.
And Abc is class.
public class Abc{
private String name;
private String id;
private String key;
private boolean active;
This is the code I am using to display output.
#Bean
CommandLineRunner runner(SwitchRepoDao switchRepoDao) {
return new CommandLineRunner() {
#Override
public void run(String... args) throws Exception {
Iterable<SwitchRepo> personList = switchRepoDao.findAllWithStatus(true);
System.out.println("Configuration : ");
for (SwitchRepo config : personList)
{
System.out.println(config.getRegistries().toString());
}
}
};
}
Can anyone please help me with this. For any query related question do comment. Thank You in advance.
Given below is MongoDB Collection from database test. and collection name is switchrepo.
"_id" : "1234567890",
"type" : "xyz",
"abc" : [
{
"name" : "test",
"id" : "test1",
"key" : "secret",
"active" : true
},
{
"name" : "test2",
"id" : "test12",
"key" : "secret2",
"active" : false
}
]
}
In response, I need output as
"id" : "test1",
"key" : "secret",
"active" : true
because active is true in that sub-document array.
Actual Result what I got is "abc" : [{"name" : "test","id" : "test1","key" : "secret","active" : true},{"name" : "test2","id" : "test12","key" : "secret2","active" : false}]
You cannot use property-expressions for a proprety when the the field type is an Array.
here solutions
using the #Query or Aggregations
Solution 1 (Using #Query)
#Repository
public interface SwitchRepoDao extends MongoRepository< SwitchRepo, String> {
//public List<SwitchRepo> findByAbc_active(boolean active);
#Query(value = "{ 'abc.active' : ?0}", fields = "{ 'abc' : 1 }")
List<SwitchRepo> findAllWithStatus(Boolean status);
}
{ 'abc.active' : ?0} for filtring
{ 'abc' : 1 } for only return that part of the document (abc).
Calling findAllWithStatus will return all SwitchRepo with at least one ABC with active is true, you need to filter (using java 8 streams filter for examples all no active Abc from array)
Solution 2 (Using Mongodb aggregation)
Create a new dto class
import java.util.List;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection="switchrepo")
public class SwitchRepoDto {
#Id
private String id;
private String type;
private Abc abc;
// with getters and setters also constructors.
public SwitchRepoDto() {
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Abc getAbc() {
return abc;
}
public void setAbc(Abc abc) {
this.abc = abc;
}
}
Create custom method Add custom method to Repository or inject MongoOperations into your service layer.
#Autowired
private MongoOperations mongoOperations;
public List<SwitchRepoDto> findAllActive() {
UnwindOperation unwind = Aggregation.unwind("$abc");
MatchOperation match = Aggregation.match(Criteria.where("abc.active").is(true));
Aggregation aggregation = Aggregation.newAggregation(unwind,match);
AggregationResults<SwitchRepoDto> results = mongoOperations.aggregate(aggregation, SwitchRepoDto.class, SwitchRepoDto.class);
List<SwitchRepoDto> mappedResults = results.getMappedResults();
return mappedResults;
}

Spring data mongodb query converts String to ObjectId automatically

The title might not be super clear, here the problem
I'm executing an update in this form:
db.poi.update({
_id: ObjectId("50f40cd052187a491707053b"),
"votes.userid": {
"$ne": "50f5460d5218fe9d1e2c7b4f"
}
},
{
$push: {
votes: {
"userid": "50f5460d5218fe9d1e2c7b4f",
"value": 1
}
},
$inc: { "score":1 }
})
To insert a document in an array only if there isn't one with the same userid (workaround because unique indexes don't work on arrays). The code works fine from mongo console. From my application I'm using this:
#Override
public void vote(String id, Vote vote) {
Query query = new Query(Criteria.where("_id").is(id).and("votes.userid").ne(vote.getUserid()));
Update update = new Update().inc("score", vote.getValue()).push("votes", vote);
mongoOperations.updateFirst(query, update, Poi.class);
}
This works fine if as "userid" I use a String that can't be a mongo ObjectId, but if I use the string in the example, the query executed translates like this (from mongosniff):
update flags:0 q:{ _id: ObjectId('50f40cd052187a491707053b'), votes.userid: { $ne: ObjectId('50f5460d5218fe9d1e2c7b4f') } } o:{ $inc: { score: 1 }, $push: { votes: { userid: "50f5460d5218fe9d1e2c7b4f", value: 1 } } }
The string is now an Objectid. Is this a bug? BasicQuery do the same thing. The only other solution I see is to use ObjectId instead of String for all classes ids.
Any thoughts?
UPDATE:
This is the Vote class
public class Vote {
private String userid;
private int value;
}
This is the User class
#Document
public class User {
#Id
private String id;
private String username;
}
This is the class and mongo document where I'm doing this update
#Document
public class MyClass {
#Id
private String id;
#Indexed
private String name;
int score
private Set<Vote>votes = new HashSet<Vote>();
}
As Json
{
"_id" : ObjectId("50f40cd052187a491707053b"),
"name" : "Test",
"score" : 12,
"votes" : [
{
"userid" : "50f5460d5218fe9d1e2c7b4f",
"value" : 1
}
]
}
Userid in votes.userid is pushed as String, but the same String is compared as an ObjectId in the $ne
It seems to me the problem can be described like this: if you use String in your classes in place of an ObjectId, if you want to use those ids as references (no dbrefs) in other documents (and embedded documents), they are pushed as String (it's ok because they are Strings). It's fine because spring data can map them again to objectid, but it's not fine if you do a query like the one I mentioned; the field is converted to an objectid in the comparison (the $ne operator in this case) but is considered as a string in the embedded document. So, to wrap up, in my opinion the $ne operator in this case should consider the field a String.
My solution was to write a custom converter to store the String as an objectid in the documents where the id is a reference
public class VoteWriteConverter implements Converter<Vote, DBObject> {
#Override
public DBObject convert(Vote vote) {
DBObject dbo = new BasicDBObject();
dbo.put("userid", new ObjectId(vote.getUserid()));
dbo.put("value", vote.getValue());
return dbo;
}
}

Categories

Resources