Check for Unique Documents in MongoDB with Spring Boot

shadowphoenix

Rose

Posted on August 19, 2020

Check for Unique Documents in MongoDB with Spring Boot

Whilst figuring out how exceptions work, I wanted to make sure that the blog posts are always unique. But how do we do that? That's why I'll show you how to check for unique documents in MongoDB with Spring Boot.

@Indexed Annotation

At first, I looked into the @Indexed annotation from Spring Boot. This annotation will make a new index in MongoDB. You can add extra parameters within the annotation to make it do something more specific for you. In my case, I am using the unique parameter to let MongoDB check if a specific field is unique. With that, my blogpost class looks like this.

@Document(collection = "blogposts")
public class BlogPost {

    @Id
    private String documentId;
    @Indexed(unique = true)
    private String endPoint;
    @JsonFormat(pattern="dd-MM-yyyy HH:mm")
    private Date publishDate;
    private List<String> categories;
    private List<String> tags;
    private String article;
Enter fullscreen mode Exit fullscreen mode

When I test this configuration, nothing happens. I can still add blog posts with duplicate endpoints. Later, I come across this post on StackOverFlow, stating that I have to add the line spring.data.mongodb.auto-index-creation=true to my application.properties. So, I added the line, even completely dropped the database and restarted MongoDB, but to no avail.

Query for Unique Documents

After a couple hours of arguing with the @Indexed annotation, I finally give in. Still, I want to find a way to check for unique documents in MongoDB with Spring Boot. So, I resort to another way of getting there: using a database query. It might not be the quickest one, but it will suffice for now.

Ideally, what I want the query to do, is tell me whether there is already a blogpost in my database with the same value for endPoint. I start out working my way through the Spring Data Documentation for MongoDB, trying to figure out how to setup my query. After another search,, I come along the MongoDB Documentation on $exists. One of their examples uses $exists combined with $nin to collect documents where a field exists and its value does not equal to what you've set it to.

$exists and $nin

Brilliant! Let's try that out. First, I start with creating a boolean to store my result in with the following bit of code. Then I use the boolean in my if-statement to decide whether to save the object or not.

@Override
public void createBlogPost(BlogPost blogPost) {
    boolean postExistsAlready = mongoTemplate.query(BlogPost.class)
        .matching(Query.query(where("endPoint").exists(true).nin(blogPost.getEndPoint()))).exists();

    if(!postExistsAlready) {
        mongoTemplate.save(blogPost, "blogposts");
        System.out.println("Posted!!!");
    }
    else {
        System.out.println("Post already exists!!!");
    }
}
Enter fullscreen mode Exit fullscreen mode

I test again, and, of course, it didn't work. With no errors appearing in my console, I resorted to PowerShell and getting right into MongoDB. After logging in, I check what data is already present in my database collection by using db.blogposts.find(). Then, I try the MongoDB version of my query with the following line: db.blogpost.find(endPoint:{$exists:true, $nin:"/test"}}). It might have been a bit late when I was working on this, because there is clearly a typo. However, I did find out one reason why nothing was happening, because I got an error when trying to execute the query.

Error message from mongo: $nin needs an array

Apparently, $nin requires an array as input, rather than just a string. Fortunately, that is fairly easy to fix! With a little adjustment of the code, it now looks like this.

@Override
public void createBlogPost(BlogPost blogPost) {
    String[] endPoint = new String[] {blogPost.getEndpoint()};

    boolean postExistsAlready = mongoTemplate.query(BlogPost.class)
        matching(Query.query(where("endPoint").exists(true).nin(endPoint))).exists();

    if(!postExistsAlready) {
        mongoTemplate.save(blogPost, "blogposts");
        System.out.println("Posted!!!");
    else {
        System.out.println("Post already exists!!!");
    }
}
Enter fullscreen mode Exit fullscreen mode

Awesome! Let's try that again. No luck either.

$exists and $in

At this point, I start questioning whether I'm using the correct query methods, especially considering that there will be many more blog posts to come. On a hunch, I fiddled around in mongo with PowerShell. Instead of using $nin, I tried $in with a count method. So, I tried the following query in my PowerShell: db.blogposts.count({"endPoint":{$exists:true,$in:["/test"]}}).

Query Works!

Well, look at that! When I use $nin, my count returns 0, because there are no documents in my database that don't have /test as their endpoint. However, when I use $in, the query returns me the number of articles that do have /test as their endpoint. That is exactly what I need!

So, I adjusted my code again to use this information. The final version so far, looks like this.

@Override
public void createBlogPost(BlogPost blogPost) {
    String[] endPoint = new String[] {blogPost.getEndPoint()};

    Query query = new Query();
    query.addCriteria(Criteria.where("endPoint").in(endpoint));

    boolean postExistsAlready = mongoTemplate.exists(query, BlogPost.class);

    if(!postExistsAlready) {
        mongoTemplate.save(blogPost, "blogposts");
        System.out.println("Posted!!!");
    }
    else {
        System.out.println("Post Already Exists!!!");
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

It took a bit to figure it all out, and I am not yet done with the @Indexed annotation. Using a query like this works for the time being, but ultimately I think it would be better to use what MongoDB has available for us. I have seen ways to add the index manually, but it requires more research. For now, I'm happy to have solved this little riddle.

Now, I do agree that these System.out.println() statements are far from pretty. There is no way to notify the user that something has gone wrong. So, the next step is to figure out how to handle exceptions being thrown!

💖 💪 🙅 🚩
shadowphoenix
Rose

Posted on August 19, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related