NoSQL Top

Build a Dashboard Using Cassandra, Astra, and Stargate:

>> CHECK OUT THE ARTICLE
Persistence top

Get started with Spring Data JPA through the reference Learn Spring Data JPA course:

>> CHECK OUT THE COURSE

1. Overview

When using Spring Data MongoDB, we may need to restrict the properties mapped from a database object. Typically, we may need this, for example, for security reasons – to avoid exposing sensitive information stored on a server. Or also, for example, we may need to filter out part of the data displayed in a web application.

In this short tutorial, we'll see how MongoDB applies field restriction.

2. MongoDB Fields Restriction Using Projection

MongoDB uses Projection to specify or restrict fields to return from a query. However, if we're using Spring Data, we want to apply this with MongoTemplate or MongoRepository.

Therefore, we want to create tests cases for both MongoTemplate and MongoRepository where we can apply field restrictions.

3. Implementing Projection

3.1. Setting up the Entity

First, let's create an Inventory class:

@Document(collection = "inventory")
public class Inventory {

    @Id
    private String id;
    private String status;
    private Size size;
    private InStock inStock;

    // standard getters and setters    
}

3.2. Setting up the Repository

Then, to test MongoRepository, we create an InventoryRepository. We'll also use a where condition with @Query. For instance, we want to filter for the inventory status:

public interface InventoryRepository extends MongoRepository<Inventory, String> {

    @Query(value = "{ 'status' : ?0 }", fields = "{ 'item' : 1, 'status' : 1 }")
    List<Inventory> findByStatusIncludeItemAndStatusFields(String status);

    @Query(value = "{ 'status' : ?0 }", fields = "{ 'item' : 1, 'status' : 1, '_id' : 0 }")
    List<Inventory> findByStatusIncludeItemAndStatusExcludeIdFields(String status);

    @Query(value = "{ 'status' : ?0 }", fields = "{ 'status' : 0, 'inStock' : 0 }")
    List<Inventory> findByStatusIncludeAllButStatusAndStockFields(String status);

    @Query(value = "{ 'status' : ?0 }", fields = "{ 'item' : 1, 'status' : 1, 'size.uom': 1 }")
    List<Inventory> findByStatusIncludeEmbeddedFields(String status);

    @Query(value = "{ 'status' : ?0 }", fields = "{ 'size.uom': 0 }")
    List<Inventory> findByStatusExcludeEmbeddedFields(String status);

    @Query(value = "{ 'status' : ?0 }", fields = "{ 'item' : 1, 'status' : 1, 'inStock.quantity': 1 }")
    List<Inventory> findByStatusIncludeEmbeddedFieldsInArray(String status);

    @Query(value = "{ 'status' : ?0 }", fields = "{ 'item' : 1, 'status' : 1, 'inStock': { $slice: -1 } }")
    List<Inventory> findByStatusIncludeEmbeddedFieldsLastElementInArray(String status);

}

3.3. Adding the Maven Dependencies

We'll also use Embedded MongoDB. Let's add the spring-data-mongodb and de.flapdoodle.embed.mongo dependencies to our pom.xml file:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-mongodb</artifactId>
    <version>3.0.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>de.flapdoodle.embed</groupId>
    <artifactId>de.flapdoodle.embed.mongo</artifactId>
    <version>3.2.6</version>
    <scope>test</scope>
</dependency>

4. Test Using MongoRepository and MongoTemplate

For MongoRepository, we'll see examples using @Query and applying Field Restriction, while for MongoTemplate, we'll use the Query class.

We'll try to cover all different combinations of include and exclude. In particular, we'll see how to restrict embedded fields or, more interestingly, arrays using the slice property.

For every test, we'll add the MongoRepository example first, followed by the one for MongoTemplate.

4.1. Only Include Fields

Let's start by including some fields. All excluded will be null. The projection adds the _id by default:

List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeItemAndStatusFields("A");

inventoryList.forEach(i -> {
  assertNotNull(i.getId());
  assertNotNull(i.getItem());
  assertNotNull(i.getStatus());
  assertNull(i.getSize());
  assertNull(i.getInStock());
});

Now, let's check out the MongoTemplate version:

Query query = new Query();
 query.fields()
   .include("item")
   .include("status");

4.2. Include and Exclude Fields

This time, we'll see examples that explicitly include some fields but exclude others – in this case, we'll exclude the _id field:

List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeItemAndStatusExcludeIdFields("A");

inventoryList.forEach(i -> {
   assertNotNull(i.getItem());
   assertNotNull(i.getStatus());
   assertNull(i.getId());
   assertNull(i.getSize());
   assertNull(i.getInStock());
});

The equivalent query using MongoTemplate would be:

Query query = new Query();
query.fields()
  .include("item")
  .include("status")
  .exclude("_id");

4.3. Only Exclude Fields

Let's continue by excluding some fields. All other fields will be non-null:

List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeAllButStatusAndStockFields("A");

inventoryList.forEach(i -> {
  assertNotNull(i.getItem());
  assertNotNull(i.getId());
  assertNotNull(i.getSize());
  assertNull(i.getInStock());
  assertNull(i.getStatus());
});

And, let's check out the MongoTemplate version:

Query query = new Query();
query.fields()
  .exclude("status")
  .exclude("inStock");

4.4. Include Embedded Fields

Again, including embedded fields will add them to our result:

List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeEmbeddedFields("A");

inventoryList.forEach(i -> {
  assertNotNull(i.getItem());
  assertNotNull(i.getStatus());
  assertNotNull(i.getId());
  assertNotNull(i.getSize());
  assertNotNull(i.getSize().getUom());
  assertNull(i.getSize().getHeight());
  assertNull(i.getSize().getWidth());
  assertNull(i.getInStock());
});

Let's see how to do the same with MongoTemplate:

Query query = new Query();
query.fields()
  .include("item")
  .include("status")
  .include("size.uom");

4.5. Exclude Embedded Fields

Likewise, excluding embedded fields keeps them out of our result, however, it would add the rest of the embedded fields:

List<Inventory> inventoryList = inventoryRepository.findByStatusExcludeEmbeddedFields("A");

inventoryList.forEach(i -> {
  assertNotNull(i.getItem());
  assertNotNull(i.getStatus());
  assertNotNull(i.getId());
  assertNotNull(i.getSize());
  assertNull(i.getSize().getUom());
  assertNotNull(i.getSize().getHeight());
  assertNotNull(i.getSize().getWidth());
  assertNotNull(i.getInStock());
});

Let's have a look at the MongoTemplate version:

Query query = new Query();
query.fields()
  .exclude("size.uom");

4.6. Include Embedded Fields in Array

Similarly to other fields, we can also add a projection of an array's field:

List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeEmbeddedFieldsInArray("A");

inventoryList.forEach(i -> {
  assertNotNull(i.getItem());
  assertNotNull(i.getStatus());
  assertNotNull(i.getId());
  assertNotNull(i.getInStock());
  i.getInStock()
    .forEach(stock -> {
      assertNull(stock.getWareHouse());
      assertNotNull(stock.getQuantity());
     });
  assertNull(i.getSize());
});

Let's implement the same using MongoTemplate:

Query query = new Query();
query.fields()
  .include("item")
  .include("status")
  .include("inStock.quantity");

4.7. Include Embedded Fields in Array using slice

MongoDB can use JavaScript functions to limit the results of an array – for example, getting only the last element in an array using slice:

List<Inventory> inventoryList = inventoryRepository.findByStatusIncludeEmbeddedFieldsLastElementInArray("A");

inventoryList.forEach(i -> {
  assertNotNull(i.getItem());
  assertNotNull(i.getStatus());
  assertNotNull(i.getId());
  assertNotNull(i.getInStock());
  assertEquals(1, i.getInStock().size());
  assertNull(i.getSize());
});

Let's perform the same query using MongoTemplate:

Query query = new Query();
query.fields()
  .include("item")
  .include("status")
  .slice("inStock", -1);

5. Conclusion

In this article, we looked at projections in Spring Data MongoDB.

We've seen examples using fields, both with the MongoRepository interface and the @Query annotation, as well as with the MongoTemplate and the Query class.

As always, the code for these examples is available over on GitHub.

NoSql Bottom

Build a Dashboard Using Cassandra, Astra, and Stargate

>> CHECK OUT THE ARTICLE
Persistence bottom
Get started with Spring Data JPA through the reference Learn Spring Data JPA course: >> CHECK OUT THE COURSE
Persistence footer banner
Comments are closed on this article!