Spring Data MongoDB Repositories

Examples of how to set up the MongoRepository to read and update MongoDB. Shows the count, exists, find, save and delete methods, plus paging and sorting.

Example Details

The example demonstrate how to:

  • Create a basic repository with the Repository interface or the @RepositoryDefinition annotation.
  • Provide create, read, update and delete operations with the CrudRepository interface .
  • Use PagingAndSortingRepository to introduce paging and sorting.
  • Include MongoDB specific functionality with MongoRepository.
  • Test the methods provided by these repositories using integration tests.

The Repository Interface

Extend the Repository interface and specify the domain class to be managed and the id type of the domain class as type arguments.

Spring will discover interfaces that extend Repository interfaces during classpath scanning.

Alternatively, if you do not want to extend Spring Data interfaces, you can annotate the repository interface with @RepositoryDefinition.

CountryRepository.java
package io.lishman.springdata.repository;

import io.lishman.springdata.domain.Country;
import org.springframework.data.mongodb.repository.Query;
import org.springframework.data.repository.Repository;

import java.math.BigInteger;
import java.util.List;

// Use this instead if you don't want to extend Spring Data interfaces
// @RepositoryDefinition(domainClass=Country.class, idClass=BigInteger.class)

public interface CountryRepository extends Repository<Country, BigInteger> {

    //------------------------------------------- equality
    
    public Country findByName(String countryName);
    
    @Query("{name : ?0}")
    public Country findByNameQuery(String countryName);

    //------------------------------------------- not equal
    
    public List<Country> findByNameNot(String countryName);

    @Query("{name : {$ne : ?0}}")
    public List<Country> findByNameNotQuery(String countryName);

    //------------------------------------------- like / regex

    public List<Country> findByNameLike(String countryName);

    public List<Country> readByNameRegex(String countryName);
    
    @Query("{name : {$regex : ?0}}")
    public List<Country> getByNameRegexQuery(String countryName);
    
    //------------------------------------------- nested
    
    public List<Country> findByContinentName(String continentName);
    
    @Query("{'continent.name' : ?0}")
    public List<Country> findByContinentNameQuery(String continentName);
    
    //------------------------------------------- null / not null
    
    public List<Country> findByPopulationIsNull();
    
    @Query("{'population' : null}")
    public List<Country> findByPopulationIsNullQuery();

    public List<Country> findByPopulationIsNotNull();

    @Query("{'population' : {$ne : null}}")
    public List<Country> findByPopulationIsNotNullQuery();
    
    //------------------------------------------- less than / greater than

    public List<Country> findByAreaInSquareMilesLessThan(int area);

    @Query("{'area' : {$lt : ?0}}")
    public List<Country> findByAreaInSquareMilesLessThanQuery(int area);

    public List<Country> findByPopulationGreaterThan(int population);

    @Query("{'population' : {$gt : ?0}}")
    public List<Country> findByPopulationGreaterThanQuery(int population);
    
    //------------------------------------------- between
    
    public List<Country> findByPopulationBetween(int start, int end);
    
    @Query("{'population' : {$gt : ?0, $lt : ?1}}")
    public List<Country> findByPopulationBetweenQuery(int start, int end);
    
    //------------------------------------------- and

    public List<Country> findByContinentNameAndPopulationLessThan(String continentName, int pop);

    @Query("{'continent.name' : ?0, population : {$lt : ?1}}")
    public List<Country> findByContinentNameAndPopulationLessThanQuery(String continentName, int pop);
    
    //------------------------------------------- or
    
    public List<Country> findByPopulationLessThanOrAreaInSquareMilesLessThan(int pop, int area);
    
    @Query("{'$or' : [{'population' : {$lt : ?0}}, {'area' : {$lt : ?1}}]}")
    public List<Country> findByPopulationLessThanOrAreaInSquareMilesLessThanQuery(int pop, int area);

    //------------------------------------------- order by
    
    public List<Country> findByContinentNameOrderByPopulationDesc(String continentName);

    //------------------------------------------- fields
    
    @Query(value="{'continent.name' : ?0}", fields="{_id : 0, name : 1}")
    public List<Country> findByContinentNameJustReturnNameQuery(String continentName);
}

CrudRepository

To create a repository that supports CRUD operations, simply extend the CrudRepository interface instead.

Spring Data will now provide functionality to create, read, update and delete documents, along with any additional custom methods that we provide.

CityRepository.java
package io.lishman.springdata.repository;

import java.math.BigInteger;

import org.springframework.data.repository.CrudRepository;

import io.lishman.springdata.domain.City;

public interface CityRepository extends CrudRepository<City, BigInteger> {

    public City findByName(String name);
    
}

In addition to our custom findByName query method we also get count, exists, findOne and findAll with which to read documents.

And, because we have extended CrudRepository, we also get insert and update functionality using save, and the ability to remove a document with delete.

CityRepositoryTest.java
package io.lishman.springdata.repository;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;
import static org.springframework.data.mongodb.core.query.Criteria.where;
import static org.springframework.data.mongodb.core.query.Query.query;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoOperations;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import io.lishman.springdata.TestData;
import io.lishman.springdata.config.MongoConfig;
import io.lishman.springdata.domain.City;
import io.lishman.springdata.domain.Country;

@ContextConfiguration(classes={MongoConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class CityRepositoryTest {

    @Autowired private CityRepository cityRepo;
    @Autowired private MongoOperations mongoOps;
    
    @Before
    public void reset() {
        TestData.countries();
        TestData.cities();
    }
    
    //------------------------------------------------- findByName

    @Test
    public void testFindByName() {
        City tokyo = cityRepo.findByName("Tokyo");
        assertThat(tokyo.getName(), equalTo("Tokyo"));
    }

    //------------------------------------------------- count
    
    @Test
    public void testCount() {
        assertThat(cityRepo.count(), equalTo(5L));
    }
    
    //------------------------------------------------- exists
    
    @Test
    public void testExists() {
        assertThat(cityRepo.exists(BigInteger.valueOf(2)), equalTo(true));
        assertThat(cityRepo.exists(BigInteger.valueOf(22)), equalTo(false));
    }
    
    //------------------------------------------------- find one
    
    @Test
    public void testFindOne() {
        City boston = cityRepo.findOne(BigInteger.valueOf(5));
        assertThat(boston.getName(), equalTo("Boston"));
    }
    
    //------------------------------------------------- find all
    
    @Test
    public void testFindAll() {
        Iterable<City> cities = cityRepo.findAll();
        assertThat(cities.toString(), equalTo("[Tokyo, Munich, Berlin, New York, Boston]"));
    }
    
    @Test
    public void testFindAllWithIds() {
        List<BigInteger> ids = new ArrayList<BigInteger>();
        ids.add(BigInteger.valueOf(2));
        ids.add(BigInteger.valueOf(4));
        
        Iterable<City> cities = cityRepo.findAll(ids);
        assertThat(cities.toString(), equalTo("[Munich, New York]"));
    }
    
    //------------------------------------------------- insert
    
    @Test
    public void testInsert() {
        Country usa = mongoOps.findOne(query(where("name").is("USA")), Country.class);
        City chicago = new City("Chicago", usa, Arrays.asList(new String[]{"Navy Pier", "Skyline Lake Tour", "SkyShedd Aquarium"}));
        chicago.setId(new BigInteger("6"));
        
        City chicagoInserted = cityRepo.save(chicago);
        
        assertThat(chicagoInserted.getName(), equalTo("Chicago"));
        assertThat(mongoOps.findById(BigInteger.valueOf(6), City.class).getName(), equalTo("Chicago"));
    }
    
    //------------------------------------------------- update
    
    @Test
    public void testUpdate() {
        City munich = mongoOps.findOne(query(where("name").is("Munich")), City.class);
        munich.getAttractions().add("Allianz Arena");
        munich.getAttractions().add("Marienplatz");
        
        City munichUpdated = cityRepo.save(munich);
        
        assertThat(munichUpdated.getName(), equalTo("Munich"));
        
        City munichRetrieved = mongoOps.findById(BigInteger.valueOf(2), City.class);
        assertThat(munichRetrieved.getName(), equalTo("Munich"));
        List<String> attractions = munichRetrieved.getAttractions();
        assertThat(attractions.toString(), equalTo("[English Garden, BMW Museum, Allianz Arena, Marienplatz]"));
    }
    
    //------------------------------------------------- delete
    
    @Test
    public void testDelete() {
        City berlin = mongoOps.findOne(query(where("name").is("Berlin")), City.class);
        
        cityRepo.delete(berlin);
        
        assertThat(mongoOps.findById(BigInteger.valueOf(3), City.class), equalTo(null));
    }

    
}

PagingAndSortingRepository

The PagingAndSortingRepository extends the CrudRepository interface to provide paging and sorting capabilities.

StateRepository.java
package io.lishman.springdata.repository;

import java.math.BigInteger;
import java.util.List;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.PagingAndSortingRepository;

import io.lishman.springdata.domain.State;

public interface StateRepository extends PagingAndSortingRepository<State, BigInteger> {

    public List<State> findByCapitalSinceGreaterThan(int since, Pageable pageable);

    public List<State> findByDateOfStatehoodGreaterThan(int since, Sort sort);
    
}

Passing a PageRequest object to the findAll method returns a single page of data into a Page object.

This object includes the data itself and some additional metadata such as the total number of elements and pages in the collection.

Alternatively, we can return a page of data directly into a List. In this case we loose the additional metadata but save on the extra request required to retrieve this information.

The Sort object specifies the properties to sort on, and the direction of the sort (ascending or descending).

This is passed to to the findAll method of the PagingAndSortingRepository interface or a custom query method.

Sort options can also be included in a page request for paging and sorting.

StateRepositoryTest.java
package io.lishman.springdata.repository;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;

import java.util.List;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import io.lishman.springdata.TestData;
import io.lishman.springdata.config.MongoConfig;
import io.lishman.springdata.domain.State;

@ContextConfiguration(classes={MongoConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class StateRepositoryTest {

    @Autowired private StateRepository stateRepo;
    
    @Before
    public void reset() {
        TestData.states();
    }
    
    //------------------------------------------------- paging to Page

    @Test
    public void testPagingToPage() {
        int page = 2;
        int pageSize = 5;
        
        Pageable pageable = new PageRequest(page, pageSize);
        Page<State> states = stateRepo.findAll(pageable);
        
        assertThat(states.isFirst(), equalTo(false));
        assertThat(states.isLast(), equalTo(false));
        assertThat(states.hasNext(), equalTo(true));
        assertThat(states.hasPrevious(), equalTo(true));
        assertThat(states.hasContent(), equalTo(true));

        assertThat(states.getTotalElements(), equalTo(50L));
        assertThat(states.getTotalPages(), equalTo(10));
        
        assertThat(states.getContent().toString(), equalTo("[Hawaii, Idaho, Illinois, Indiana, Iowa]"));
    }
    
    //------------------------------------------------- paging to list
    
    @Test
    public void testPagingToList() {
        int page = 4;
        int pageSize = 4;
        
        Pageable pageable = new PageRequest(page, pageSize);
        List<State> states = stateRepo.findByCapitalSinceGreaterThan(1800, pageable);
        
        assertThat(states.toString(), equalTo("[Maine, Michigan, Minnesota, Mississippi]"));
    }
    
    //------------------------------------------------- sorting all
    
    @Test
    public void testSortingAll() {

        Sort sort = new Sort(Direction.ASC, "abbreviation");
        Iterable<State> states = stateRepo.findAll(sort);
        
        StringBuffer abbreviations = new StringBuffer();
        for (State state : states) {
            abbreviations.append(state.getAbbreviation() + ",");
        }
        
        assertThat(abbreviations.toString(), equalTo("AK,AL,AR,AZ,CA,CO,CT,DE,FL,GA,HI,IA,ID,IL,IN,KS,KY,LA,MA," +
                                                 "MD,ME,MI,MN,MO,MS,MT,NC,ND,NE,NH,NJ,NM,NV,NY,OH,OK,OR,PA," +
                                                 "RI,SC,SD,TN,TX,UT,VA,VT,WA,WI,WV,WY,"));
    }
    
    //------------------------------------------------- sorting some
    
    @Test
    public void testSortingSome() {
        
        Sort sort = new Sort(Direction.DESC, "dateOfStatehood");
        Iterable<State> states = stateRepo.findByDateOfStatehoodGreaterThan(1900, sort);
        
        StringBuffer abbreviations = new StringBuffer();
        for (State state : states) {
            abbreviations.append(state.getDateOfStatehood() + ",");
        }
        
        assertThat(abbreviations.toString(), equalTo("1959,1959,1912,1912,1907,"));
    }
    
    //------------------------------------------------- paging and sorting
    
    @Test
    public void testPagingAndSorting() {
        int page = 3;
        int pageSize = 6;
        
        Pageable pageable = new PageRequest(page, pageSize, Direction.ASC, "capitalSince");
        Page<State> states = stateRepo.findAll(pageable);
        
        assertThat(states.getContent().toString(), equalTo("[Missouri, Tennessee, Maine, Illinois, Wisconsin, Texas]"));
    }

}

MongoRepository

Finally, MongoRepository is a MongoDB specific extension of PagingAndSortingRepository.

OceanRepository.java
package io.lishman.springdata.repository;

import io.lishman.springdata.domain.Ocean;
import org.springframework.data.mongodb.repository.MongoRepository;

import java.math.BigInteger;

public interface OceanRepository extends MongoRepository<Ocean, BigInteger> {
 
}

MongoRepository adds some extra methods that return data to a List rather than Iterable.

OceanRepositoryTest.java
package io.lishman.springdata.repository;

import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertThat;

import java.util.List;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import io.lishman.springdata.TestData;
import io.lishman.springdata.config.MongoConfig;
import io.lishman.springdata.domain.Ocean;

@ContextConfiguration(classes={MongoConfig.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class OceanRepositoryTest {

    @Autowired private OceanRepository oceanRepo;
    
    @Rule public ExpectedException exception = ExpectedException.none();  
    
    @Before
    public void reset() {
        TestData.oceans();
    }

    //------------------------------------------------- find all to list
    
    @Test
    public void testFindAllToList() {
        List<Ocean> oceans = oceanRepo.findAll();
        assertThat(oceans.toString(), equalTo("[Arctic, Atlantic, Indian, Pacific, Southern]"));
    }
    
    //------------------------------------------------- sort all to list
    
    @Test
    public void testSortingAllToList() {
        Sort sortDescending = new Sort(Direction.DESC, "area");
        List<Ocean> oceans = oceanRepo.findAll(sortDescending);
        assertThat(oceans.toString(), equalTo("[Pacific, Atlantic, Indian, Southern, Arctic]"));
    }
    
    //------------------------------------------------- duplicate
    
    @Test
    public void testDuplicateNameCannotBeInserted() {
        exception.expect(DuplicateKeyException.class);
        
        Ocean arctic = new Ocean("Arctic", 123456);
        oceanRepo.save(arctic);
    }
}