On some occasions, you just don’t have all the info you need to find a document in a MongoDb instance. That’s why you need to do pattern searching.

Pattern searching, if you’re unfamiliar with it, is similar to “like” searching on a traditional, relational database.

Consider the following query:

select * from momawork where title like '%Plate%';

That query will find any row in the table where the contents of the title column include the word “Plate” anywhere.

In other words, if the title is “Hi, I’m a Plate” or “Plate This!”, the query will match and the rows will be returned.

But MongoDb instances don’t use tables or SQL.

So how do you do something similar to a “like” search in a MongoDb instance?

Moreover, how do you do that kind of search if you’re using Spring Boot with a MongoDB instance?

Those are the questions that we’ll answer in this article.

The Use Case

You’re playing Tetris in your office when your boss, Smithers, walks in.

He tells you he’s got an issue with the app that lets people browse Metropolitan Museum of Art (MOMA) pieces online. Apparently, some people want to search for art by entering only a portion of the title name!

He can’t believe that people would want to do such a thing. But he’s sure that you can make those folks happy.

Smithers leaves your office, telling you to deploy a Spring Boot app that handles pattern searching on the MOMA MongoDb instance.

You get to work.

The Tech Stack

It’s a microservices environment. People browse the MOMA works on an Angular app.

That app sends HTTP requests to the Spring Boot application, which in turn interacts with the MongoDb instance.

Spring Boot returns the requested data to the Angular app in JSON format.

We won’t cover the intricacies of an Angular app here. That’s a subject for a different day.

We will, however, go over the implementation on the Spring Boot side.

Getting the Ball Rolling

Within Eclipse, create a new Maven project. That’s the project that you’ll use to build your Spring Boot app.

First things first: update the pom.xml file with the necessary dependencies. Here’s what it looks like:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.careydevelopment</groupId>
    <artifactId>mongodb-pattern-search</artifactId>
    <version>1.0</version>
    <name>mongodb-pattern-search</name>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.0.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20180813</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-mongodb</artifactId>
        </dependency>    
    </dependencies>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Pay particular attention to the last dependency: spring-boot-starter-data-mongodb

That tells Spring Boot you’re ready to do business with a MongoDb instance.

The Properties

You also need to get your application.properties file up to snuff. Here’s what it will look like:

server.port=8090

spring.data.mongodb.uri=mongodb://name:password!@host:port/moma

logging.level.com.careydevelopment=DEBUG

The most important piece of info there is the second line. That’s the URI you’ll use to access the MongoDb instance.

As you can see, that line includes the user name and password as well as the host name, port number, and database name.

As you can also see, you’ll need to replace that info with data specific to your MongoDb instance.

The Application Class

Next, create a class that launches a Spring Boot app.

@SpringBootApplication
public class PatternSearchApplication {

    public static void main(String[] args) {
        SpringApplication.run(PatternSearchApplication.class,args);
    }
}

Nothing fancy there. It’s all the usual stuff.

The Model

Create a Java bean that reflects the MOMA work you persist in the MongoDb instance.

That’s easier than many people think because Mongo databases are document-driven. They allow you to add whatever fields you want on-the-fly without defining anything like a schema.

So all the fields you define in the Java class will automagically map to fields in MongoDb documents.

An instance of the Java class represents a single document in the collection.

However, the collection itself will have the same name as the Java class.

To some folks, that’s confusing. But it makes sense if you think about it.

Just as a Java class can have many instances, a MongoDb collection can have many documents.

Anyhoo, here’s what the MomaWork class looks like.

public class MomaWork { 

    private boolean isHighlight;
    private boolean isPublicDomain;
    private String primaryImage;
    private List<String> additionalImages;
    private String artist;
    private String title;
    private String culture;
    private String period;
    private String dynasty;
    private String reign;
    private String date;
    private String id;
 //getters and setters
}

It’s a standard Java bean that defines a bunch of fields specific to works of art.

The Ingestion Has Happened

Before we go on with the code, let’s make one thing abundantly clear: for the purposes of this tutorial, the database is already populated with info retrieved from the MOMA API.

So you won’t need to persist anything. All you need to do is handle the search feature.

If you want real data to test against, feel free to use that API to populate your own MongoDb collection.

The Repository

And now we get to where the magic happens: the repository.

Here’s what the code for that interface looks like:

public interface MomaRepository extends MongoRepository<MomaWork,String> {

    @Query("{}")
    Page<MomaWork> findMomaWorks(Pageable page);

    @Query("{'title':{'$regex':'?0','$options':'i'}}")  
    Page<MomaWork> searchByTitle(String name, Pageable page);
}

The class declaration is pretty standard for a Spring Boot repository. In this case, though, it extends MongoRepository instead of the usual CrudRepository.

Additionally, the ID is defined as a String object instead of a Long object as is often the case.

Why is that?

Because Mongo databases store IDs in a format that looks like this: “5bdb14a486bcaf40bc2593dc”. That’s hexadecimal and requires a String instead of a number.

The first method in the class is easy to understand. It returns all documents with pagination.

The second method is the one you want to focus on. Take a look at the @Query annotation.

Believe it or not, that annotation is handling pattern searching. Let’s go through it in pieces.

The first part ('title') tells the MongoDb instance that you’re looking for matches on the title field. The rest of the query tells the MongoDb instance the kind of matches that you’re looking for.

That '$regex' bit tells the database that you’re performing a regular expression search. That’s cool, because regex is well-suited to a pattern search.

And what about '$options'? In this case that “i” you see tells the database to run a search that’s case-insensitive. In other words “Hey” and “hey” would return the same results.

You might also notice that the whole query looks like a JSON object. That’s understandable because MongoDb uses BSON, binary encoding strikingly similar to JSON.

We also shouldn’t ignore the '?0' in the room. What’s that?

That’s telling Spring Boot to substitute the value of the first parameter with that ?0 in the query.

So if the value of name in is “finkelstein,” then the database will perform the following query:

{'title':{'$regex':'finkelstein','$options':'i'}}

What does the zero have to do with anything? The system is using 0-based indexing. The first parameter is #0, the second parameter is #1, and so on.

Next, let’s run a few tests directly against the database to see if this query structure works.

Mucking About in MongoDb

As we’ve seen, there’s already plenty of MOMA data in the MongoDb instance. Now it’s time to take a look at some of it.

Please note: you won’t be able to do this in your database unless you’ve ingested Metropolitan Museum of Art data. The purpose of this section is to ensure that the query is structured correctly.

The name of the database that holds the MOMA data is unimaginatively named “moma.” You can access that with the following command from within the Mongo shell:

use moma

The name of the collection should match the name of the Java class. View all collections with this command:

show collections

That results in this:

momaWork

Bingo! There’s a collection called momaWork. Now, it’s time to take a peek at its contents.

View all documents with this command:

db.momaWork.find({})

The empty braces within the parentheses mean that there’s no filter criteria. The query will return everything.

Well, not quite everything. It paginates the results.

Again, MongoDb documents look like JSON objects. That makes this result pretty easy to read.

If you look at the last result at the bottom, you’ll see it has the title: “Plate (one of a set of twelve).”

Now, put the query structure to the test. Try to find anything with the word “plate” in its title. This should do the trick:

db.momaWork.find({'title':{'$regex':'plate','$options':'i'}})

Looks good! All the results have the word “Plate” in the title. So you know the query works.

The Controller

Now it’s time to put a controller in place so a client app can perform searches. Here’s what that class looks like:

The controller is annotated with @RestController so it accepts REST requests and returns JSON data.

Next, the code injects an instance of the MomaRepository interface so it can access the database.

The only public method is search(). It’s annotated with @GetMapping because it accepts HTTP GET requests.

The URL path is defined in the parentheses following the @GetMapping annotation: /moma/artworks.

There are two parameters defined in search(). One of them is required, the other isn’t.

The match parameter is required. It’s the string pattern to search for.

In the example above, the pattern was “plate.”

The next parameter in the search() method isn’t required. It’s the page number. It will default to the first page if no page number is specified.

Note that both parameters are annotated with @RequestParam. That means they’ll get passed in the URL as request parameters.

A search for “plate” would look like this:

http://localhost:8090/moma/artworks?match=plate

The body of the method itself does three things:

  • It creates a Pageable object
  • It fetches the results that match the pattern
  • It returns the paginated results
  • That’s really all there is to it.

Testing It Out

It’s time to test.

Right-click on the PatternSearchApplication class within Eclipse and run it.

Give it a few seconds to start up.

Once it’s started, it’s easy to run a test. You can do it from a browser since you’re doing a GET request.

Fire up Google Chrome and visit the following URL:

http://localhost:8090/moma/artworks?match=plate

Remember, this demo uses port 8090. If your Spring Application is using a different port, adjust the URL accordingly.

If you have MOMA data loaded in your MongoDB instance, you’ll get the results you're expecting.

Results are paginated so you’ll only see the first 10 artworks.

The test is a success. Pattern searching works with MongoDb!

Wrapping It Up

Again, there’s fully functioning code on GitHub if you want to grab it. Feel free to fork it an mess around some more.

Have fun!

Photo by Scott Webb from Pexels