Date:

Share:

Spring Data MongoDB – Guide to the @Aggregation Annotation

Related Articles

introduction

MongoDB Is a NoSQL document-based database that stores data in BSON (JSON Binary) format.

As with any database, you will routinely make calls to read, write, or update data stored in the document repository. In many cases, retrieving data is not as simple as writing a single query (although queries can be quite complex).

If you want to read more about writing MongoDB queries with Spring Boot – read our Spring Data MongoDB – Guide to @Query!

With MongoDB – accrual Are used to process many documents, and to return some calculated result. This is achieved by creating a plumbing Of operations, where each operation takes a set of documents, and filters them given some criteria.

Spring Data MongoDB is the Spring module that acts as an interface between the Spring Boot app and MongoDB. Naturally, it offers a set of notes that allow us to easily “enable” and turn off features, as well as let the module itself know when it needs to take care of matters for us.

God @Aggregation Note is used to explain the Spring Boot Repository methods, and to enable a pipeline() Of the actions you provide to @Aggregation Note.

In this tutorial, we will review how to leverage the @Aggregation Note for aggregation of results in a MongoDB database, what are aggregation tubes, how to use method arguments with a name and location for dynamic aggregations, as well as how to sort and meet results!

Domain and repository model

Let’s start with our domain model and simple repository. We will create a Property, Serves as a model for a real estate property with several relevant areas:

@Document(collection = "property")
public class Property 

    @Id
    private String id;
    @Field("price")
    private int price;
    @Field("area")
    private int area;
    @Field("property_type")
    private String propertyType;
    @Field("transaction_type")
    private String transactionType;
    
    
    

And with that, just related MongoRepository:

@Repository
public interface PropertyRepository extends MongoRepository<Property, String> 

reminder: MongoRepository he PagingAndSortingRepository, Which is ultimately a CrudRepository.

Through aggregations, you can, naturally, sort and stand the results as well, taking advantage of the fact that it expands the PagingAndSortingRepository Interface from Spring Data.

Understanding the comment @Aggregation

God @Aggregation An annotation is applied at the level of the method, within a @Repository. The annotation accepts a pipeline – An array of strings, where each string represents a Step in the piping (Operation to activate). Each subsequent step operates on the results of the previous step.

There are different steps, and they allow for a wide range of actions. Some of the more common are:

  • $match – Filter documents based on whether their field matches a given predicate.
  • $count – Returns the count of documents left in the piping.
  • $limit – Limits the number (slices) of returned documents, starting from the beginning of the set and approaching the limit.
  • $sample Random sampling of a given number of documents from a group.
  • $sort – Sort the documents received field and sort order.
  • $merge – Writes the documents in the pipeline for the collection.

Some of them are terminal operations (applied at the end), such as $merge. The sorting should be done even after the rest of the filtering has been completed.

In our case, add a @Aggregation To our database, we just need to add a method and specify it:

@Aggregation(pipeline = 
        "Operation/Stage 1...",
        "Operation/Stage 2...",
        "Operation/Stage 3...",
)
List<Property> someMethod();

Alternatively, you can save them within:

@Aggregation(pipeline = "Operation/Stage 1...", "Operation/Stage 2...", "Operation/Stage 3...")
List<Property> someMethod();

Depending on the number of steps you have, the second option may become unreadable pretty quickly. In general, it helps to break down the steps in new lines into readings.

However, let’s add some operations to the pipe! Let’s look for, for example, properties that have a field that matches a given value, such as their properties transactionType equal to "For Sale":

@Aggregation(pipeline = {
    "'$match':'transaction_type':'For Sale'",
)
List<Property> findPropertiesForSale();

However, such a single game wins the accumulation point. Let’s add a few more matching terms. Do not forget, you can provide here any number of matching conditions, including such selectors / operators $gt Filter more:

@Aggregation(pipeline = {
    "'$match':'transaction_type':'For Sale', 'price' : $gt : 100000",
)
List<Property> findExpensivePropertiesForSale();

Now, we’ll look for properties that match the transaction_type, but also Have price bigger than ($gt) 100000! Even with two such, there is only a $match A stage does not have to justify @Aggregation, Although this is still a perfectly legal way to achieve results based on a number of conditions.

Plus, it’s not fun when you’re dealing with fixed values. Who said it was an expensive property? It would be much more useful to be able to provide a lower mark for reading the method, and use it with $gt Operator in place.

It’s where Parameter parameters with name and location Sign in.

Reference to parameters of a method with name and location

We rarely deal with static numbers, because they are, well, inflexible. We want to offer flexibility to end users as well, though also For developers. In the previous example, we used two constant values ​​- 'For Sale', And 100000. In this section, we will replace these two with named and location method parameters, and provide them using the method parameters!

Using named or positioned arguments does not change the code functionally, and it is usually up to the engineer / team to decide which option to go for, based on their preferences. You should be consistent with one type, once you have chosen it:

@Aggregation(pipeline = {
        "'$match':'transaction_type': ?0, 'price' : $gt : ?1",
)
List<Property> findPropertiesByTransactionTypeAndPriceGTPositional(String transactionType, int price);

@Aggregation(pipeline = {
        "'$match':'transaction_type': ##transactionType, 'price' : $gt : ##price",
)
List<Property> findPropertiesByTransactionTypeAndPriceGTNamed(@Param("transactionType") String transactionType, @Param("price") int price);

Refer to our practical and practical guide to learning Git, with best practices, industry-accepted standards and a cheat sheet included. Stop Google Git commands and actually Learn This!

The first is more concise, but it requires you to enforce the order of incoming arguments. In addition, if the field in the database itself does not indicate the expected type / value (which is a bad design, but is sometimes out of your control) – using location arguments may add to the confusion, as there is a small amount of ambiguity about the value you can expect.

The latter is admittedly more literal, but it does allow the order of the parameters to be mixed. There is no need to enforce their position, as they are suitable for @Param Annotation with the SPEL expressions, linking them to There In the operation pipeline.

There is no better option here objectively, nor is it one that is accepted in the industry. Choose the one that makes you feel more comfortable with yourself.

advice: If enabled DEBUG As your listing level – you can see the query sent to Mongo in the logs. You can copy and paste the query into MongoDB Atlas to check if the query returns the correct results there, and make sure you mistakenly misspelled the locations. Chances are – your query is fine, but you just mixed the placements so the result is empty.

Now, you can provide values ​​for the method calls, and they will be used in @Aggregation Dynamically! This allows you to reuse the same methods for different calls, such as, for example, receiving active properties. This is going to be a common call, so whether you return 5, 10 or 100 of them, you can use the same method again.

When dealing with larger data corpora, you should also consider sorting and replacing. End users should not be expected to sort the data themselves.

Sort and replace

Sorting is usually done at the end, as pre-sorting may be unnecessary. You can apply sorting methods after the accumulation occurs, or during the accumulation.

Here, we will investigate the possibility of applying sorting methods within the aggregation itself. We will sample a certain number of assets, and sort them, for example, by area. It can be any other domain, such as price, datePublished, sponsoredEtc. e $sort The action gets a sort field, as well as the order (where 1 he ascending and -1 Descending).

@Aggregation(pipeline = 
        "'$match':'transaction_type':?0, 'price': $gt: ?1 ",
        "'$sample':size:?2",
        "'$sort':'area':-1"
)
List<Property> findPropertiesByTransactionTypeAndPriceGT(String transactionType, int price, int sampleSize);

Here, sort the properties by area, in descending order – that is, the properties with the largest area will appear first in the sort. The transaction type, price and sample size all vary and can be dynamically defined.

If you want to combine pagination Into this, the standard Spring Boot layout approach is applied – you just add a Pageable pageable To define the method and read:

@Aggregation(pipeline = 
        "'$match':'transaction_type':?0, 'price': $gt: ?1 ",
        "'$sample':size:?2",
        "'$sort':'area':-1"
)
Iterable<Property> findPropertiesByTransactionTypeAndPriceGTPageable(String transactionType, int price, int sampleSize, Pageable pageable);

When calling the method a visitor, you will want to build a Pageable Object to move in:

int page = 1;
int size = 5;

Pageable pageable = new PageRequest.of(page, size);
Page<Property> = propertyRepository.findPropertiesByTransactionTypeAndPriceGTPageable("For Sale", 100000, 5, pageable);

God Page Will be the second page (index 1), With 5 Results.

Note: Since we have already sorted the properties in the aggregation, there is no need to include another sort configuration there. Alternatively, you can skip $sort In aggregation, and sort it using e Pageable for example.

Create a REST API

Let’s quickly rotate the REST API that reveals the results of these methods to an end user, and send curl Request to verify the results, starting with the controller with an automatic line buffer:

@RestController
public class HomeController 
    @Autowired
    private PropertyRepository propertyRepository;
    

If you want to read more about the @RestController and @Autowired comments, read the @Controller and @RestController comments in Spring Boot and the @Autowired Section in Spring Annotations: Core Frame Comments!

First we want to add some properties to the database:

@GetMapping("/addProperties")
public ResponseEntity addProperies() 

    List<Property> propertyList = List.of(
            new Property(100000, 45, "Apartment", "For Sale"),
            new Property(65000, 48, "Apartment", "For Sale"),
            new Property(280000, 75, "Apartment", "For Sale"),
            new Property(452000, 110, "House", "For Sale"),
            new Property(400000, 125, "House", "For Rent"),
            new Property(125000, 100, "Apartment", "For Sale"),
            new Property(95000, 70, "House", "For Rent"),
            new Property(35000, 25, "Apartment", "For Sale")
    );

    for (Property property : propertyList) 
        propertyRepository.save(property);
    

    return ResponseEntity.ok().body(propertyList);

Now, come on curl Request this endpoint to add the properties to the database:

$    curl localhost: 8080 / addProperties

[ 
  "id" : "61dedea6799b5758bb857292",
  "price" : 100000,
  "area" : 45,
  "propertyType" : "Apartment",
  "transactionType" : "For Sale"
, 
  "id" : "61dedea6799b5758bb857293",
  "price" : 65000,
  "area" : 48,
  "propertyType" : "Apartment",
  "transactionType" : "For Sale"
,
...

Note: For a pretty-print response, remember to turn Jackson’s INDENT_OUTPUT to true in your application.properties.

And now, let’s define a /getProperties endpoint, which calls one of the PropertyRepository methods which performs an aggregation:

@GetMapping("/getProperties")
public ResponseEntity home() 
    return ResponseEntity
    .ok()
    .body(propertyRepository.findPropertiesByTransactionTypeAndPriceGT("For Sale", 100000, 5));

This should return up to 5 randomly selected properties from a set of properties that are for sale, over 100k in price, sorted by their area. If there are no 5 samples to choose from – all of the fitting properties are returned:

$ curl localhost:8080/getProperties

[ 
  "id" : "61dedea6799b5758bb857295",
  "price" : 452000,
  "area" : 110,
  "propertyType" : "House",
  "transactionType" : "For Sale"
, 
  "id" : "61dedea6799b5758bb857297",
  "price" : 125000,
  "area" : 100,
  "propertyType" : "Apartment",
  "transactionType" : "For Sale"
, 
  "id" : "61dedea6799b5758bb857294",
  "price" : 280000,
  "area" : 75,
  "propertyType" : "Apartment",
  "transactionType" : "For Sale"
 ]

Works like magic!

Summary

In this guide, we went over @Aggregation Note in the Spring Data MongoDB module. We reviewed what aggregations are, when they can be used and how they differ from regular queries.

I reviewed some of the common operations in aggregation piping, before writing our own pipes with static and dynamic arguments. We tested location parameters and names for the aggregation, and finally, created a simple REST API to serve the results.

Source

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Popular Articles