Looking for something specific in your MongoDB collection? If so, then you'll probably enlist the aid of the match stage filter in your aggregation.

And that's pretty easy to do.

In fact, it's one of the easier stages to deal with in aggregations.

In this guide, I'll go over how you can implement MongoDB aggregations in your Spring Boot applications using MongoTemplate.

Of course, you can always just grab the code on GitHub.

The Business Requirements

Your boss Smithers walks into your office, unusually cheery.

"Need somethin' from ya," he says while sitting down. "On that CRM app you're working on, I need you to enable users to search for contacts based on how they were discovered."

Smithers smiles more, then sniffles.

"And remember, I need you to sort the results by the contact's last name."

He starts to laugh as he gets up and walks away.

You're not sure why he's so happy.

Spring Boot Scootin' Boogie

The CRM app that Smithers mentioned uses Spring Boot as the intermediary between the Angular front end and MongoDB.

You've already got the Spring Boot service wired up to interact with MongoDB. If you're not sure how to do that, I covered that subject in my guide on storing user credentials.

But now you need to do something more complicated than a simple CRUD operation. You need to retrieve the data and sort it.

For that, you decide to use aggregates.

(Before I explain aggregates, let me say that  you could do this without aggregates. But you're here to learn about aggregates so let's do it that way.)

So what are aggregates?

Aggregation Nation

In MongoDB terminology, you can think of aggregates as compound queries or multi-stage queries. In other words, they handle multiple operations as part of a collective.

Consider Smithers' requirement above. He says you need to do two things:

  • Match by contact source
  • Sort by last name

So that requirement has two stages: first match on source and then sort the matched documents by the contact's last name.

Full confession once more: that's an astonishingly simple aggregation. Some aggregations have several stages.

But you're here to learn as a beginner so I'll keep things simple.

Now let's see how you handle this in your Spring Boot application.

The Client (Not Just a Novel by John Grisham)

The first thing you need to do is to get yourself a MongoClient instance. Easy to do in Spring Boot.

Take a look at this code:

@Configuration
@EnableCaching
@EnableMongoRepositories(basePackages = {"com.careydevelopment.contact.repository"})
public class MongoConfig extends AbstractMongoClientConfiguration {

    @Value("${mongo.db.name}") 
    private String contactDb;
    
    @Value("${contact.properties.file.location}")
    private String contactPropertiesFile;
    
    @Override
    protected String getDatabaseName() {
        return contactDb;
    }
  
    
    @Override
    @Bean
    public MongoClient mongoClient() {
        PropertiesUtil propertiesUtil = new PropertiesUtil(contactPropertiesFile);
        String connectionString = propertiesUtil.getProperty("mongodb.carey-contact.connection");
        String fullConnectionString = connectionString + "/" + contactDb;
        
        MongoClient client = MongoClients.create(fullConnectionString);
        return client;
    }
}

Hone in on the mongoClient() method there. Unsurprisingly, that creates the Mongo client.

Here's how it works: the code reads the connection string from an external properties file.

Why does it use an external properties file? For no other reason than I don't want to accidentally check in my real connection string into GitHub for all the world to see.

That's why.

So in your case, feel free to use application.properties for the same purpose. If you're of a mind.

Anyhoo, inside that external properties file there's a property called mongodb.carey-contact.connection. That property is set to a connection string that looks something like this:

mongodb://brian:password@myhost.com:1997

That's going to enable Spring Boot to connect to your MongoDB instance. But you also need to specify the database you're using.

Take a look above, and you'll see that the fullConnectionString variable appends the database name to the connection string.

Then, voila! The code instantiates a MongoClient instance in the next line.

You can do a lot with that MongoClient instance, but to make your life easier you should use MongoTemplate to perform aggregate actions.

What the Heck Is MongoTemplate?

It's the primary instance of an interface called MongoOperations. That interface defines a variety of MongoDB operations that you can use to perform all sorts of actions.

And guess what? You got it for free.

Yep. You don't need to do anything more than what you just did above. An instance of MongoTemplate is yours for the using whenever and wherever you want it in your Spring Boot project.

Just inject it with an @Autowired annotation.

Now It's Time to Do Some Stuff

Okay, time to get busy doing some aggregate-related work.

Create a new class called ContactService. Make it look like this:

@Service
public class ContactService {

    private static final Logger LOG = LoggerFactory.getLogger(ContactService.class);
	
    
	@Autowired
	private MongoTemplate mongoTemplate;
	
	
	public List<Contact> findContactsBySource(Source source) {
		AggregationOperation match = Aggregation.match(Criteria.where("source").is(source));
		AggregationOperation sort = Aggregation.sort(Direction.ASC, "lastName");
		
		Aggregation aggregation = Aggregation.newAggregation(match, sort);
		
		List<Contact> contacts = mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(Contact.class), Contact.class).getMappedResults();
		
		return contacts;
	}
}

First, note that the code is autowiring MongoTemplate just like I described in the previous section. Again, you can do that anywhere.

This class, as of now, only contains a single method: findContactsBySource(). The method accepts a Source enum as its only parameter.

The method itself performs the much-anticipated aggregation in just a few lines of code.

The first line handles the match operation. The second line handles the sort operation.

The next line aggregates those two operations. Then it returns the result.

The last line translates that result into a List of Contact objects.

Now, let's break all of that apart by looking at the classes and methods used in the code block above.

  • AggregationOperation - Represents a single operation in an entire aggregation. If you have a large aggregation with 10 operations, you'll likely use 10 AggregationOperation objects.
  • Aggregation.match() - Handles the operation that returns only results that match a specific criteria. 
  • Criteria.where() - Performs a simple "where" clause search. In this case, the query is searching for a specific value in the "source" property. But Criteria.where() doesn't do everything by itself. Instead, it returns a Criteria object.
  • is() - Creates a criteria using equality. It's an instance method from the Criteria object instantiated with Criteria.where(). In this case, the criteria searches for all documents where the "source" property is equal to whatever value is passed in the method. 
  • Aggregation.sort() - Handles the operation that sorts all results based on a provided field and direction. The first parameter is the direction (Direction.ASC) and the second parameter is the property name to sort by ("lastName").
  • Aggregation - Combines all the operations into a single aggregation. But keep in mind: the Aggregation object doesn't do anything. It's just there waiting for somebody to use it.

So how do you use that aggregation? Well that's where the MongoTemplate object comes in handy.

MongoTemplate includes several aggregate() methods. The code above uses a method that accepts three parameters:

  • The Aggregation object
  • The collection name
  • The type to return

Pay attention to that second bullet point above. All this time when you were reading about matches, sorts, and aggregates, you might have been asking yourself: "Okay but how does it know which collection to use?"

Well, that's how. That second parameter.

Remember: the MongoTemplate object is tied to a specific database. But it's not tied to any collection. You have to specify which collection you're using when you execute the aggregation.

But there's another piece of info you need to provide: the class you want to map the results to. That's the third parameter.

In this project the Contact class represents a single document in the contacts collection in MongoDB. That's why you see Contact.class as the third parameter there.

Unfortunately, though, even after all that, you're going to get an AggregationResults object back. That's not terribly useful to other classes.

So you need to translate that AggregationResults object to a List of Contacts. That's why the code uses the getMappedResults() method above.

Testing It Out

You can do a quick-and-dirty test on this thing easily. I like to use an implementation of ApplicationListener for that purpose.

Create a component class that looks like this:

@Component
public class ApplicationListenerInitialize implements ApplicationListener<ApplicationReadyEvent>  {
	
	@Autowired
	private ContactService contactService;
	
    public void onApplicationEvent(ApplicationReadyEvent event) {
    	List<Contact> contacts = contactService.findContactsBySource(Source.EMAIL);
    	System.err.println(contacts);
    }

}

Now, when you launch the Spring Boot app, it will run the code above after everything has been initalized.

As you can see, it autowires ContactService. That's the class you created in the last section.

The onApplicationEvent() method makes use of ContactService to find all contacts who were reached via email.

Then, it uses System.err to print out the results so they show up in a nice shade of red.

When my system starts up, I see this results list:

[com.careydevelopment.contact.model.Contact@664e848c[id=5fde1ac084dad94dbb7f82ae,firstName=R2D2,lastName=Droid,email=r2d2@xmail.com,phones=[],addresses=[],source=EMAIL,sourceDetails=<null>,status=ACTIVE,statusChange=0,linesOfBusiness=[JAVA_ENTERPRISE],company=For Luke,title=Droid,authority=false,salesOwner=com.careydevelopment.contact.model.SalesOwner@251c4280[id=5f78d8fbc1d3246ab4303f2b,firstName=Darth,lastName=Vader,email=darth@xmail.com,username=darth,phoneNumber=474-555-1212]], com.careydevelopment.contact.model.Contact@35650279[id=5fdd0cedaac5f75d62564ee7,firstName=Jabba,lastName=Hutt,email=jabba@xmail.com,phones=[],addresses=[],source=EMAIL,sourceDetails=<null>,status=NEW,statusChange=0,linesOfBusiness=[ANGULAR],company=Sandz,title=Large,authority=false,salesOwner=com.careydevelopment.contact.model.SalesOwner@5f61371d[id=5f78d8fbc1d3246ab4303f2b,firstName=Darth,lastName=Vader,email=darth@xmail.com,username=darth,phoneNumber=474-555-1212]], com.careydevelopment.contact.model.Contact@420a8042[id=5fd5f37bde602d3bacef69db,firstName=Luke,lastName=Skywalker,email=luke@tat2.com,phones=[],addresses=[],source=EMAIL,sourceDetails=He emailed me,status=NEW,statusChange=0,linesOfBusiness=[ANGULAR],company=International Business,title=President,authority=false,salesOwner=com.careydevelopment.contact.model.SalesOwner@3292d91a[id=5f78d8fbc1d3246ab4303f2b,firstName=Darth,lastName=Vader,email=darth@xmail.com,username=darth,phoneNumber=474-555-1212]], com.careydevelopment.contact.model.Contact@5921b93c[id=5fdd0f2aea599836ca3ddbf1,firstName=Han,lastName=Solo,email=han@xmail.com,phones=[],addresses=[com.careydevelopment.contact.model.Address@faea4da[street1=111 Millennium Way,street2=<null>,city=Nessy,state=CO,zip=<null>,country=<null>,addressType=HOME]],source=EMAIL,sourceDetails=<null>,status=ACTIVE,statusChange=0,linesOfBusiness=<null>,company=<null>,title=Pirate,authority=false,salesOwner=com.careydevelopment.contact.model.SalesOwner@3dce6dd8[id=5f78d8fbc1d3246ab4303f2b,firstName=Darth,lastName=Vader,email=darth@xmail.com,username=darth,phoneNumber=474-555-1212]]]

And that's exactly what I expect to see for the data that's in my database. All the contacts in the list above have the "source" property set to "EMAIL" and the list is sorted by last name in ascending order.

In other words: it works.

Wrapping It Up

Congratulations! You now know how to use match in aggregates with MongoDB. 

Now, it's over to you. Make some changes to the code you see above to suit your own business logic. Change the sort direction. Filter on another field.

As always, feel free to just grab the code on GitHub.

Have fun!