Spring MongoDB - ProjectionOperation Question for multiple values - java

I have a DAOImpl with an override function. I'm trying to use an aggregation to first filter then match then to project.
public Map<Long, Integer> getUsersByAccount() {
MatchOperation filterByAccountId = match(new Criteria(ACCOUNT_ID).nin(Arrays.asList(null, "")));
GroupOperation groupByMasterAccount = group(DEFAULT_MASTER_ACCOUNT_ID).count().as("countOfFooUsers");
ProjectionOperation projectByIDandCount = project()
.and("_id")
.as("defaultMasterAccountId");
projectByIDandCount.and("countOfFooUsers");
Aggregation aggregation = newAggregation(
filterByAccountId,
groupByMasterAccount,
projectByIDandCount
);
AggregationResults<MAandUsers> maUsersResult = mongoTemplate
.aggregate(aggregation, USERS_COLLECTION, MAandUsers.class);
The inner class above MAandUsers within the IMPL:
#Data
public static class MAandUsers {
private Long defaultMasterAccountId;
private Integer countOfFooUsers;
}
However, my maUsersResultreturns a null value for countOfFooUsers when, in my test case, there should be 1. I'm assuming my projection operation is off ---> projectByIDandCount.and("countOfFooUsers");
Is there a way for me to use spring's mongotemplate to receive back multiple values on projection operations? I'm fairly new to this

Try using, andInclude
ProjectionOperation projectByIDandCount = project()
.and("_id").as("defaultMasterAccountId")
.andInclude("countOfFooUsers");

Related

Get the first element from an array with mongodb java client

I have a JavaScript code.
db.xxx.aggregate([
{$project: {object:{$first:'$object.array'}, _id:0}}
])
I don't know how to overwrite it by java client.
import org.springframework.data.mongodb.core.MongoTemplate;
...
Aggregation.newAggregation(
Aggregation.project().andExclude("_id").???
)
And where to find the usage?
This is TestDocument
public class TestArrayDocument {
#Id
String id;
Integer[] x;
}
Inject MongoTemplate
#Autowired
MongoTemplate mongoTemplate;
This is aggregate method
TypedAggregation<TestArrayDocument> aggregation = Aggregation.newAggregation(TestArrayDocument.class,Aggregation
.project()
.andExclude("_id")
.andExpression("first(x)").as("object"));
List<Map> mapData= mongoTemplate
.aggregate(aggregation,Map.class)
.getMappedResults();
x is your $object.array.
return in LinkedHashMap "object"=>"firstElement".

Querying with Timestamps using SpringBoot and JPA Criteria Query in custom repository

How do you query based off timestamps in custom JPA repositories using JPA Criteria Query?
I have a startTime and endTime that I want to use to query for entires with a field time between them, but I need to do this in a custom repository because there are a lot of other fields that I need to use to build a complex query.
I've can succcessfully build up a complex query and get correct results, except when I try to query by startTime and endTime. I've been trying to do something like the following but with no luck
#PersistenceContext private EntityManager em;
...
final CriteriaBuilder cb = this.em.getCriteriaBuilder();
final CriteriaQuery<AutoenrollmentEvent> cr = cb.createQuery(Event.class);
final Root<AutoenrollmentEvent> root = cr.from(Event.class);
final List<Predicate> predicates = new ArrayList<>();
...
predicates.add(cb.between(root.get("time"), data.getStartTime(), data.getEndTime()));
...
cr.select(root).where(predicates.toArray(new Predicate[0]));
return this.em.createQuery(cr).getResultList();
where data.getStartTime() and data.getEndTime() are of type java.sql.Timestamp.
The idea is to get all the entries with a timestamp in field time between startTime and endTime.
Any ideas on how to do this?
Thanks.
You can achieve that by using Spring Data JPA CrudRepository interface:
public interface EventRepository extends CrudRepository<Event, Long> {
List<Event> findAllByTimeAfterAndTimeBefore(Timestamp start, Timestamp end);
}
And then using it:
public class MyClass {
#Autowired
private EventRepository eventRepository;
public void myMethod() {
Timestamp startTime = // create start timestamp
Timestamp endTime = // create end timestamp
List<Event> events = eventRepository.findAllByTimeAfterAndTimeBefore(startTime,endTime);
}
}
Spring will implement the query automatically and return the results. Official docs.

how to apply mongo db update operators as $inc in spring boot

All I want to do is a simple query using spring boot and mongodb but I can't find resources online, update query to increment frequency field by one given searchString or create a new document if searchString is not found.
#Query("{'searchString': ?0 } , {'$inc' : {'frequency':1}} ")
public void incFreq(String query);
Hope you use spring-data-mongodb. Since you haven't mentioned about the Document class, I assume it as Person.class
First you #Autowire the MongoTemplate in the service implementation.
#Autowire
MongoTemplate mongoTemplate;
Then what you can do is, you can call a query like following,
public void incFreq(String given_str){
Query query=new Query(Criteria.where("searchString ").is("given_str"));
Person person=mongoTemplate.find(query,Person.class);
if(person!=null){
Update update=new Update().inc("frequency",1)
UpdateResult result=mongoTemplate.updateOne(query,update,Person.class);
// bu using the result, you can see modifiedCount(),matchCount()
}else{
// insert query
}
}
If you going to use JPA methods, then
public void incFreq(String given_str){
Optional<Person> p=personRepository.findBySearchString(String given_str);
if(p.isPresent()){
p.get().setFrequency(p.get().getFrequency()+1);
personRepository.save(p);
}else{
Person p=new Person();
p.setName("some name");
p.setFrequency(1);
personRepository.save(p);
}
}
Refer Spring data mongodb
#Autowired
MongoTemplete mongotemplate;
Update update = new Update();
Query query = new Query();
update.inc("listPrice",productItem.getListPrice());
mongoTemplate.updateMulti(query,update,ProductCatalogItem.class);
#"listPrice" : for which you wanna use $inc.
#productItem : instance of your class

Spring data mongo aggregation with #Field annotation

I have requirement to perform group by on nested fields in mongo.
The second level nested field is annotated with #Field. I am using projection with groupBy.
Example
ProjectionOperation projectionOperation = Aggregation.project("id")
.and("author.eid").as("user");
GroupOperation groupOperation = Aggregation.group(aggregationBy, "user").count().as("total");
Aggregation aggregation =
Aggregation.newAggregation(projectionOperation groupOperation);
AggregationResults<Document> aggregationResults = myRepository.getMongoTemplate().aggregate(aggregation, MyClass.class, Document.class);
On execution I am getting error "org.springframework.data.mapping.PropertyReferenceException: No property eid found for type User!"
public class MyClass {
User author;
}
public class User {
#Field("eid")
#JsonProperty("eid") // fasterxml
public String externalId;
}
What I can think of is when casting the aggregation result to MyClass, it is unable to find "eid" because it is annotated.
How to handle this usecase ?
The #Field annotation parsed to replace the pojo property with the field name.
So you should be using
ProjectionOperation projectionOperation = Aggregation.project("id")
.and("author.externalId").as("user");
the query generated will be
{ "$project" : { "user" : "$author.eid" }

Spring boot mongoDB aggregation piple-line no property found for type

I have a collection of documents called 'appointment', and I am trying to group together the number of appointments per day and populate an List of objects call AppointmentSummary, where there will be one object per day, I am using Spring boot to try and achieve this however I keep running into issues.
I have created the following three classes in the same package
AppointmentSummaryRepository.java
public interface AppointmentSummaryRepository extends
MongoRepository<Appointment,String>, AppointmentSummaryRepositoryCustom {
}
AppointmentSummaryRepositoryCustom.java
public interface AppointmentSummaryRepositoryCustom {
List<AppointmentSummary> aggregate(LocalDate startDate, LocalDate endDate);
}
AppointmentSummaryRepositoryImpl.java
public class AppointmentSummaryRepositoryImpl implements AppointmentSummaryRepositoryCustom {
private final MongoTemplate mongoTemplate;
private final Logger log = LoggerFactory.getLogger(AppointmentSummaryRepositoryImpl.class);
#Autowired
public AppointmentSummaryRepositoryImpl(MongoTemplate mongoTemplate){
this.mongoTemplate = mongoTemplate;
}
#Override
public List<AppointmentSummary> aggregate(LocalDate startDate, LocalDate endDate){
log.debug("This is a request to aggerate appointment summary between {} to {}", startDate.toString(), endDate.toString());
MatchOperation matchOperation = getMatchOperation(startDate, endDate);
GroupOperation groupOperation = getGroupOperation();
log.debug("End group operaton");
ProjectionOperation projectionOperation = getProjectOperation();
return mongoTemplate.aggregate(Aggregation.newAggregation(
matchOperation,
groupOperation,
projectionOperation
), Appointment.class, AppointmentSummary.class).getMappedResults();
}
private MatchOperation getMatchOperation(LocalDate startDate, LocalDate endDate) {
log.debug("Begin Match Operation");
Criteria appointmentCriteria = where("appointment_date").gt(startDate).andOperator(where("appointment_date").lt(endDate));
log.debug("End Match Operation");
return match(appointmentCriteria);
}
private GroupOperation getGroupOperation() {
log.debug("Performing Group Operation");
return group("appointment_date")
.last("appointment_date").as("appointment_date")
.addToSet("id").as("appointmentIds")
.sum("id").as("count");
}
private ProjectionOperation getProjectOperation() {
log.debug("Begin project operation");
return project("appointment_date","appointmentIds","count")
.and("appointment_date").previousOperation();
}
Whenever I run the it, I keep getting the following error:
org.springframework.data.mapping.PropertyReferenceException: No property appointment found for type Appointment!
I believe the issue is happening in the following code segment, my understanding is that I initialize the different stages of the pipeline and pass them to the mongoTemplate and the 'getMappedResults' will map the fields from the two objects and populate the AppointmentSummary.class with the output of the aggregation pipeline?
return mongoTemplate.aggregate(Aggregation.newAggregation(
matchOperation,
groupOperation,
projectionOperation
), Appointment.class, AppointmentSummary.class).getMappedResults();
To note that the object Appointment does not have a field/property appointment. I added this in but when I ran the code I received another error message complaining of cannot find type date for Appointment.
Thanks in advance for your help!
Use below variant of mongo template aggregate which takes collection name instead of class.
mongoTemplate.aggregate(Aggregation.newAggregation(
matchOperation,
groupOperation,
projectionOperation
), "appointment", AppointmentSummary.class).getMappedResults();
The reason was that when you use typed variant spring runs validation on the fields used in the aggregation pipeline to match the field names in the pojo and fails when it doesn't find the alias.

Categories