Spring data - search Documents by search string and two parameters - mongodb

I will search workingBook documents in my mongoDB database by a search string and two parameters: project and tenant.
Is there a possibility to do this?
This is my current Spring data query but it does not work:
#Query("{$text : { $search : ?0 } }")
List<WorkingBook> findWorkingBookByProjectAndTenantAndSearchString(final Project project, final Tenant tenant,
final String searchString);
Thats a sample document:
db.WorkingBook.find().pretty()
{
"_id" : ObjectId("589f2af42f7a97b95842afc6"),
"_class" : "my.domain.dao.domain.WorkingBook",
"dateTimeFrom" : ISODate("2017-02-11T15:16:33Z"),
"dateTimeUntil" : ISODate("2017-02-11T16:17:33Z"),
"categoryType" : "SERVICE",
"workingText" : "That's a working book text ",
"creationDateTime" : ISODate("2017-02-11T15:17:08.550Z"),
"user" : DBRef("User", ObjectId("5589929b887dc1fdb501cdba")),
"project" : DBRef("Project", ObjectId("5899eb0dbfb41f1382eac15a")),
"tenant" : DBRef("Tenant", ObjectId("58500aed747a6cddb55ba094"))
}

Assuming you have id field like below in both Project and Tenant classes.
#Id
private String id;
and DBRef like below in WorkingBook
#DBRef
private Project project;
#DBRef
private Tenant tenant;
With #Query annotation
Pass the project and tenant id along with search string.
#Query("{$text : { $search : ?0 }, 'project' : ?1, 'tenant' : ?2 }")
List<WorkingBook> findWorkingBookByProjectAndTenantAndSearchString(final String searchString, final String projectId, final String tenantId);
Without #Query annotation
You can define the the below method in repository.
List<WorkingBook> findByProjectAndTenant(final TextCriteria searchString, final String projectId, final String tenantId);
Calling Code
TextCriteria textCriteria = new TextCriteria().matching(searchString);
List<WorkingBook> workingBookList = workingBookRepository.findByProjectAndTenant(textCriteria, projectId, tenantId);

Spring Data does not combine the query from an annotation with a query derived from the method name. So if you want to combine the search query with what would otherwise generated from the name you have to do it yourself and put it in the annotation.

Related

Get likes count and whether user is liked record from MongoDB

I am trying to get count of likes and if user is liked this post in Mongo.
I managed to get this via native query with facets, but problems is how can i map this two fields on my custom java class (LikeStatus.class)?
thanks in advance!
please code below:
POJO:
public class LikeStatus {
String entityId;
LikedEntityType entityType;
long likesCount;
boolean isLikedByUser;
}
Document class:
public class Like {
#Id
private String id;
#EqualsAndHashCode.Include
private String entityId;
#EqualsAndHashCode.Include
private String profileId;
#EqualsAndHashCode.Include
private LikedEntityType entityType;
private LocalDateTime createdAt = LocalDateTime.now();
}
Query i used in Mongo:
> db.likes.aggregate({$facet:
{count:[
{$match:{entityId:"entityId"},
$match:{entityType:"OFFER"}}, {$count:"count"}],
isliked:[{$match:{profileId:"profileId4"}}, {$count:"isliked"}]}}).pretty();
and gives me result:
{
"count" : [
{
"count" : 3
}
],
"isliked" : [
{
"isliked" : 1
}
]
}
I managed to find solution which is suited my needs, hope it will be useful who faced with the same kind of queries in Mongodb and it will give some idea how it can be solved)
Java solution: i used Facet object to collect two aggregation request in one query like this:
In repository layer i created query:
default Aggregation getLikeStatus(String entityId, String entityType, String profileId){
FacetOperation facet = Aggregation.facet(match(where(ENTITY_ID_FIELD).is(entityId).and(ENTITY_TYPE_FIELD).is(entityType)),
Aggregation.count().as(LIKES_COUNT_FIELD)).as(LIKES_COUNT_FIELD)
.and(match(where(ENTITY_ID_FIELD).is(entityId)
.and(ENTITY_TYPE_FIELD).is(entityType)
.and(PROFILE_ID_FIELD).is(profileId)),
Aggregation.count().as(IS_LIKED_BY_USER_FIELD)).as(IS_LIKED_BY_USER_FIELD);
ProjectionOperation project = project()
.and(ConditionalOperators.ifNull(ArrayOperators.ArrayElemAt.arrayOf(LIKES_COUNT_FIELD).elementAt(0)).then(0)).as(LIKES_COUNT_FIELD)
.and(ConditionalOperators.ifNull(ArrayOperators.ArrayElemAt.arrayOf(IS_LIKED_BY_USER_FIELD).elementAt(0)).then(0)).as(IS_LIKED_BY_USER_FIELD)
.andExclude("_id");
return newAggregation(facet, project);
}
then in service layer it returns Document object which is mapped on my custom class LikeStatus fields:
Document status = template.aggregate(likeRepo.getLikeStatus(entityId, entityType, profileId), Like.class, Document.class).getUniqueMappedResult();
my custom POJO:
#Data
#NoArgsConstructor
#AllArgsConstructor
public class LikeStatus {
String entityId;
LikedEntityType entityType;
long likesCount;
boolean isLikedByUser;
}
Also i post native query solution in Mongo for reference:
db.likes.aggregate([
{$facet:
{"likesCountGroup":[
{$match:{entityId:"entityId", entityType:"TYPE"}},{$count:"likesCount"}
],
"isUserLikedGroup":[
{$match:{entityId:"entityId", entityType:"TYPE", profileId:"604cd12c-1633-4661-a773-792a6ec22187"}},
{$count:"isUserLiked"}
]}},
{$addFields:{}},
{$project:{"likes":{"$ifNull":[
{$arrayElemAt:["$likesCountGroup.likesCount", 0]},0]},
"isUser":{"$ifNull:[{$arrayElemAt["$isUserLikedGroup.isUserLiked",0]},0]}}}]);

Spring data MongoDb Aggregation lookup from String to ObjectId

I'm using Spring (boot) data 2.2.7 with mongodb 4.0.
I've set 3 collections that I'm trying to join via an aggregation lookup operation.
catalog
stock
operations
catalog
{
"_id" : ObjectId("5ec7856eb9eb171b72f721af"),
"model" : "HX711",
"type" : "DIGITAL",
....
}
mapped by
#Document(collection = "catalog")
public class Product implements Serializable {
#Id
private String _id;
#TextIndexed
private String model;
....
stock
{
"_id" : ObjectId("5ec78573b9eb171b72f721ba"),
"serialNumber" : "7af646bb-a5a8-4b86-b56b-07c12a625265",
"bareCode" : "72193.67751691974",
"productId" : "5ec7856eb9eb171b72f721af",
......
}
mapped by
#Document(collection = "stock")
public class Component implements Serializable {
#Id
private String _id;
private String productId;
....
the productId field refers to the _id one in the catalog collection
operations
{
"_id" : ObjectId("5ec78671b9eb171b72f721d3"),
"componentId" : ""5ec78573b9eb171b72f721ba",
.....
}
mapped by
public class Node implements Serializable {
#Id
private String _id;
private String componentId;
....
the componentId field refers to the _id one in the stock collection
I want to query operations or stock collection to retreive the corresponding Node or Component object list ordered by the Product.model field (in the catalog collection.)
While the goal is to code in Java I've tried to make the request first in the Mongo shell but I can't even get it working as I'm trying to join (lookup) a string with an ObjectId : Node.componentId -> Component._id
Component.productId -> Product._id
For the relationship Component(stock) -> Product(Catalog) I've tryed
LookupOperation lookupOperation = LookupOperation.newLookup()
.from("catalog")
.localField("productId")
.foreignField("_id")
.as("product");
TypedAggregation<Component> agg =
Aggregation.newAggregation(
Component.class,
lookupOperation
);
AggregationResults<Component> results = mongoTemplate.aggregate(agg, "stock", Component.class);
return results.getMappedResults();
but it returns the whole components records without product info.
[{"_id":"5ec78573b9eb171b72f721b0","uuId":"da8800d0-b0af-4886-80d1-c384596d2261","serialNumber":"706d93ef-abf5-4f08-9cbd-e7be0af1681c","bareCode":"90168.94737714577","productId":"5ec7856eb9eb171b72f721a9","created":"2020-05-22T07:55:31.66","updated":null}, .....]
thanks for your help.
Note:
In addition to #Valijon answer to be able to get the result as expected the returned object must include a "product" property either nothing is returned (using JSON REST service for example)
public class ComponentExpanded implements Serializable {
private String product;
....
with
AggregationResults<ComponentExpanded> results =
mongoTemplate.aggregate(agg,mongoTemplate.getCollectionName(Component.class), ComponentExpanded.class);
The problem resides in the mismatch of types between productId and_id as you have observed.
To join such data, we need to perform uncorrelated sub-queries and not every "new" feature makes it immediately into abstraction layers such as spring-mongo.
Try this:
Aggregation agg = Aggregation.newAggregation(l -> new Document("$lookup",
new Document("from", mongoTemplate.getCollectionName(Product.class))
.append("let", new Document("productId", new Document("$toObjectId", "$productId")))
.append("pipeline",
Arrays.asList(new Document("$match",
new Document("$expr",
new Document("$eq", Arrays.asList("$_id", "$$productId"))))))
.append("as", "product")),
Aggregation.unwind("product", Boolean.TRUE));
AggregationResults<Component> results = mongoTemplate.aggregate(agg,
mongoTemplate.getCollectionName(Component.class), Component.class);
return results.getMappedResults();
MongoPlayground Check here how shell query looks like.
Note: For Java v1.7, you need to implement AggregationOperation like below:
AggregationOperation l = new AggregationOperation() {
#Override
public Document toDocument(AggregationOperationContext context) {
return new Document(...); // put here $lookup stage
}
};

Spring data mongo: Projection not working when path includes a key of a HashMap

Problem
When I try to project a value which is inside a java.util.Map then I get below exception. But when I run the generated shell query in roboMongo then It works. I would be very grateful if somebody could point out the problem.
org.springframework.data.mapping.context.InvalidPersistentPropertyPath: No property Germany found on com.nntn.corona.snapshot.repo.model.StatWithDelta!
Query Code In Java
spring boot parent: 2.0.5.RELEASE
Criteria matchCriteria = Criteria.where("timestamp").gte(startDate);
MatchOperation match = Aggregation.match(matchCriteria);
SortOperation sort = sort(new Sort(Sort.Direction.ASC, "timestamp"));
// #formatter:off
ProjectionOperation projection = project()
.andExpression("timestamp").as("timestamp")
.andExpression("countries.germany.total").as("total")
.andExpression("countries.germany.today").as("today");
// #formatter:on
Aggregation aggregation = newAggregation(match, sort,projection);
AggregationResults<Document> result = mongoTemplate.aggregate(aggregation, SnapshotEntry.class,
Document.class);
return result.getMappedResults();
Data Model
Java representation
#Document(collection = "snpashots")
public class SnapshotEntry {
#Id
private String id;
#Type(type = "org.jadira.usertype.dateandtime.joda.PersistentDateTime")
#Temporal(TemporalType.TIMESTAMP)
private DateTime timestamp;
private Map<String, StatWithDelta> countries;
private StatEntity total;
private StatEntity today;
private String source;
private String previousRecordId;
#Data
#NoArgsConstructor
#AllArgsConstructor
public class StatWithDelta {
private StatEntity total;
private StatEntity today;
}
}
Json representation
{
"_id" : "21-03-2020",
"timestamp" : ISODate("2020-03-21T09:26:00.965Z"),
"countries" : {
"germany" : {
"total" : {
"born" : NumberLong(81008),
"dead" : NumberLong(3255),
"sick" : NumberLong(30000)
},
"today" : {
"born" : NumberLong(50),
"dead" : NumberLong(10),
"sick" : NumberLong(12)
}
}
},
"_class" : "com.nntn.snapshot.repo.model.SnapshotEntry"
}
The problem is in TypedAggregation. This is a special aggregation where Spring holds information of the input aggregation type.
To avoid it, use raw aggregation (as if you run in MongoDB shell) this way:
AggregationResults<SnapshotEntry> result = mongoTemplate.aggregate(aggregation,
mongoTemplate.getCollectionName(SnapshotEntry.class),
SnapshotEntry.class);

Spring Boot: Get an object from MongoDB document?

I am using Spring Boot 2.1.3.RELEASE and MongoDB. And I am looking for a way to retrieve a part of a document.
Repository:
#Repository
public interface CompanyRepository extends MongoRepository<Company, String> {
}
Object:
#Data
#Document
public class Company {
public GeneralInfo info;
public Map<String, List<Employee>> officeIdEmployeeMap;
#Data
public class GeneralInfo {
#Id
public String companyId;
public String name;
}
#Data
public class Employee {
public String firstName;
public String lastName;
}
}
I need to get only GeneralInfo objects and then if some conditions are true get the List<Employee> from the officeIdEmployeeMap, not the whole map.
Can this be done by MongoRepository?
This is possible with MongoTemplate.
finalString officeName = "IT";
final Query query = new Query();
query.addCriteria(Criteria.where("officeIdEmployeeMap." + officeName).exists(true));
query.fields().exclude("officeIdEmployeeMap");
final Company company = mongoTemplate.findOne(query, Company.class, "collection");
This will return the Company object but only GeneralInfo will be set.
In CompanyRepository add the following method:
#Query(value="{}", fields = "{'info':1}")
public List<Company> getCompanies();
Call this when you need to get only General Info otherwise, call findAll() to get all details. You may write a new Query in the repository restricting the selected fields and optionally the criteria.
In case you want to achieve both the scenarios (get GeneralInfo only and get both) in the same query conditionally, instead of annotation create a query like below:
#Autowired
private MongoTemplate mongoTemplate;
Query query = new Query();
boolean someCondition = true; // Put your condition here
if(someCondition) {
query.fields().exclude("officeIdEmployeeMap");
}
return mongoTemplate.find(query, Company.class);
Assuming your company data in the database looks like this:
{ "_id" : ObjectId("5d3afb75b2ebf8a9ec2f906f"), "info" : { "companyId" : "1", "name" : "abc" }, "officeIdEmployeeMap" : { "g1" : [ { "firstName" : "A", "lastName" : "B" }, { "firstName" : "C", "lastName" : "D" } ] } }

Morphia storing empty values

I am using Morphia+MongoDB as a backend for a simple CRUD input mask.
I prepare the framework the simplest possible way, creating a new MongoClient and initializing Morphia like that:
Morphia morphia = new Morphia();
morphia.mapPackage("it.trew.omg.model");
Both instances are injected into a DAO:
public class ClientiDao extends BasicDAO<Cliente, String>
My entity is still pretty simple:
#Entity("clienti")
public class Cliente {
#Id ObjectId id;
String name;
String address;
String city;
String state;
String email;
public Cliente() {
}
// getters+setters
}
Let's say I create a Cliente by just filling the name inside my form.
When a controller calls the save method, the operation is successful:
getClientiDao().save(cliente);
But when I query the 'clienti' collection from the mongo console I get this:
{ "_id" : ObjectId("547edf630364677dd2f911b8"), "className" : "it.trew.omg.model.Cliente", "name" : "Fabio Bozzo", "indirizzo" : "", "citta" : "", "cap" : "", "provincia" : "", "stato" : "", "email" : "", "telefono" : "", "fax" : "", "note" : "" }
I thought that empty fields would not have been inserted. Is there something wrong?
Versions are:
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.12.4</version>
</dependency>
<dependency>
<groupId>org.mongodb.morphia</groupId>
<artifactId>morphia</artifactId>
<version>0.108</version>
</dependency>
Empty fields mean null. Those fields have "" values and so are persisted. If you don't want them persisted, they'd need to be null.