spring data mongodb #Query with exclude option - spring-data

Using spring data mongo repository class, how do we declare a method to return the documents with few fields excluded? Spring data reference document shows 'include' fields mechanism but not exclude.
Code from spring documentation:
public interface PersonRepository extends MongoRepository<Person, String>
#Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 1, 'lastname' : 1}")
List<Person> findByThePersonsFirstname(String firstname);
}
I need a mechanism to specify the fields to be excluded? Is this supported for repository methods?

specify the fields value as 0. Ex:
public interface PersonRepository extends MongoRepository<Person, String>
#Query(value="{ 'firstname' : ?0 }", fields="{ 'firstname' : 0}")
List<Person> findByThePersonsFirstname(String firstname);
}
This will not fetch firstname property of the document and value will be null in returned java object.

Add an empty filter criteria for findAll query:
public interface PersonRepository extends MongoRepository<Person, String> {
#Query(value = "{}", fields = "{ 'firstname' : 0 }")
List<Person> findAll(Sort sort);
}

Related

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
}
};

MongoDB - FindById is not working and giving null

I am using Spring Boot(V2.2.2.RELEASE) + Spring Data Mongo Example. In this example, I've records like below
{
"_id" : ObjectId("5cb825e566135255e0bf38a4"),
"firstName" : "John",
"lastName": "Doe"
}
My Repository
#Repository
public interface EmployeeRepository extends CrudRepository<Employee, ObjectId>{
Employee findById(String id);
}
Code
Employee findById = employeeRepository.findById("5cb825e566135255e0bf38a4");
System.out.println(findById);
Even below code not working
Query query = new Query(Criteria.where("id").is(new ObjectId("5cb825e566135255e0bf38a4")));
List<Employee> find = mongoTemplate.find(query, Employee.class);
Seems there might be two issues
ObjectId should be used
employeeRepository.findById(new ObjectId("5cb825e566135255e0bf38a4"))
ID field goes with underscore
new Query(Criteria.where("_id").is(new ObjectId("5cb825e566135255e0bf38a4")))
I'm not a Java guy, so might miss smth, but at least give it a try :)
Using the input document with _id: ObjectId("5cb825e566135255e0bf38a4") you can use either of the approaches. Assuming there is the document in the employee collection you can query by the _id's string value.
public class Employee {
#Id
private String id;
private String name;
// constructors (with and with arguments)
// get methods
// override toString()
}
// Spring-data app using MongoTemplate
MongoOperations mongoOps = new MongoTemplate(MongoClients.create(), "test");
Query query = new Query(where("id").is("5cb825e566135255e0bf38a4"));
Employee emp = mongoOps.findOne(query, Employee.class);
-or-
MongoRepository
interface extends CrudRepository and exposes the capabilities of the
underlying persistence technology in addition to the generic
persistence technology-agnostic interfaces such as CrudRepository.
#Repository
public interface EmployeeRepository extends MongoRepository<Employee, String> {
#Query("{ 'id' : ?0 }")
Optional<Employee> findById(String id);
}
// Spring-data app using the Repository
Optional<Employee> empOpt = employeeRepository.findById("5cb825e566135255e0bf38a4");
if (empOpt.isPresent()) {
System.out.println(empOpt.get());
}
else {
System.out.println("Employee not found!");
}
I too faced the issue. Interestingly things are working fine with version 2.1.7RELEASE
<documentRepository>.findById(<StringID>) treats as String Id in 2.2.2RELEASE
while 2.1.7RELEASE transforms it to ObjectId and hence the find query works.

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" } ] } }

Spring data - search Documents by search string and two parameters

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.

Generic MongoRepository Interface for Multiple Documents in MongoDB

Here is an example on accessing Person Document from MongoDB.
public interface PersonRepository extends MongoRepository<Person, String>
#Query("{ 'firstname' : ?0 }")
List<Person> findByThePersonsFirstname(String firstname);
}
If I have 5 documents ex: Person, Family, Relation. etc.
Do I need to create 5 Repositories and Extend MongoRepository or is there a way to achieve in one MongoRepository.