SpringData for MongoDB

Since there are numbers of tutorial out there to teach you how you can get simple MongoDB application up and running with spring data, so I’m not going to talk about it here, instead try to summarize Spring Data for MongoDB (v1.7) after reading it 🙂

Generally there are 2 way to perform tasks against MongoDB using SDMB (Spring Data MonggoDB) describe below:

1. MongoTemplate

Code snipped below show how easy we can insert, find and drop a collection in MongoDB using MongoTemplate.

@Document
public class Shop {

    @Id
    private BigInteger id;
    private String shopName;
    private String address;

    public Shop() {}

    public Shop(String shopName, String address) {
        this.shopName = shopName;
        this.address = address;
    }

    public BigInteger getId() {
        return id;
    }

    public void setShopName(String shopName) {
        this.shopName = shopName;
    }

    public String getShopName() {
        return shopName;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getAddress() {
        return address;
    }
}
@Configuration
@EnableMongoRepositories({"com.dicksonkho.repositories"})
public class MongoConfig extends AbstractMongoConfiguration {

    @Override
    protected String getDatabaseName() {
        return "shopdb";
    }

    @Bean
    @Override
    public Mongo mongo() throws Exception {
        return new MongoClient("localhost");
    }

    @Override
    protected String getMappingBasePackage() {
        return "com.dicksonkho.domain";
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MongoConfig.class})
public class TemplateTest {

    @Autowired
    MongoOperations mongoOperation;

    @Test
    public void testCRUDRecordWithTemplate() {
        Shop shop = new Shop("muimui", "Taming Jaya");
        mongoOperation.save(shop);
        Assert.assertNotNull(shop.getId());

        Shop insertedShop = mongoOperation.findOne(new Query(where("shopName").is("muimui")), Shop.class);
        Assert.assertEquals(shop.getId(), insertedShop.getId());

        insertedShop.setShopName("newmui");
        mongoOperation.save(insertedShop);
        Shop updatedShop = mongoOperation.findOne(new Query(where("shopName").is("newmui")), Shop.class);
        Assert.assertEquals(shop.getId(), updatedShop.getId());

        mongoOperation.dropCollection("shop");
    }
}

NOTE : MongoTemplate implements MongoOperations

  • When we insert new Post, MappingMongoConverter (default for MongoTemplate) will do the class / object type mapping, and save into MongoDB.
  • There are numbers of methods provided in MongoTemplate to deal with MongoDB, such as upsert, count, findAndModify etc.
  • It supported geospatial, GeoJson, and full text search queries.
  • It support map reduce operation to perform batch processing and data aggregation.
  • It provides methods for managing indexed and collection.
  • We can execute MongoDB driver’s DB.command using executeCommand and any exception will translate into Spring’s DataAccessException hierarchy.

2. Repository

There is a core marker interface for Spring Data repository abstraction called Repository, which then inherited by few sub-interface sort in hierarchical order as below:

Repository < CrudRepository < PagingAndSortingRepository < MongoRepository

Defining a query to manipulate data just a matter of declaring query method on repository interface.
Refer sample interface below, we will have a method for querying shop by shop name, querying shop name which start with some regex pattern, etc.

public interface ShopRepository extends Repository<Shop, BigInteger> {

    Shop save(Shop shop);

    Shop findByShopName(String shopName);

    Shop findOne(BigInteger id);

    Shop findByShopNameStartingWith(String shopName);

    List deleteByShopName(String shopName);
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MongoConfig.class})
public class RepositoryTest {

    @Autowired
    private ShopRepository repository;

    @Test
    public void testCRUDRecordWithRepository() {
        Shop shop = new Shop("muimui", "Taming Jaya");
        repository.save(shop);
        Assert.assertNotNull(shop.getId());

        Shop shopByShopName = repository.findByShopName("muimui");
        Assert.assertEquals(shop.getId(), shopByShopName.getId());

        Shop shopByNameStartWith = repository.findByShopNameStartingWith("mui");
        Assert.assertEquals(shop.getId(), shopByNameStartWith.getId());
        List & lt;Shop & gt;
        deletedShopList = repository.deleteByShopName("muimui");
        Assert.assertEquals(1, deletedShopList.size()); // 1 record deleted

        Shop recheckTheShop = repository.findByShopName("muimui");
        Assert.assertNull(recheckTheShop);
    }
}

NOTE: Sample above extends the most basic Repository interface, but we can extends other interface like CrudRepository, PagingAndSortingRepository based on our need.

  • There are 3 types of query lookup strategies being use by Repository infrastructure:
    CREATE, USE_DECLARE_QUERY and CREATE_IF_NOT_FOUND (default)
  • There are numbers of method convention are available such as: findByXXXLessThan, findByYYYBefore, findByLocationNear, etc.
  • We can also create MongoDB JSON based query method to override the query method as sample below:
    @Query("{ 'shopName' : ?0 }")
    List<Person> findByShopName(String shopName);
    
  • Result of query can be process incrementally using Java8 Stream as return type.
  • Support integration with QueryDSL to provide type-safe query.
  • Support full text search.

Spring Data extensions (Web Support)

@Configuration
@EnableWebMvc
@EnableSpringDataWebSupport
@EnableMongoRepositories({"com.dicksonkho.repositories"})
@ComponentScan(basePackages = {"com.dicksonkho.controller"})
public class MongoWebConfig extends AbstractMongoConfiguration {

    @Override
    protected String getDatabaseName() {
        return "shopdb";
    }

    @Bean
    @Override
    public Mongo mongo() throws Exception {
        return new MongoClient("localhost");
    }
}

As you may notice, these configuration was exactly same as MongoConfig except 2 additional annotation (EnableWebMvc & EnableSpringDataWebSupport) which to enable spring data web support.

@Controller
@RequestMapping("/shop")
public class ShopController {

    @ResponseBody
    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public Shop showUserForm(@PathVariable("id") Shop shop) {
        return shop;
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {MongoWebConfig.class})
@WebAppConfiguration
public class SpringDataWebSupportTests {

    private MockMvc mockMvc;
    ObjectMapper mapper = new ObjectMapper();

    @Autowired
    protected WebApplicationContext webApplicationContext;

    @Autowired
    private ShopRepository repository;

    @Test
    public void getShopWithWebSupport() throws Exception {
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();

        Shop shop = new Shop("muimui", "Taming Jaya");
        repository.save(shop);

        MvcResult result = mockMvc.perform(get("/shop/" + shop.getId())).andReturn();
        Shop shopResponse = mapper.readValue(result.getResponse().getContentAsString(), Shop.class);
        Assert.assertEquals("Taming Jaya", shopResponse.getAddress());

        repository.deleteByShopName("muimui");
    }
}

See, we just need to pass in id as request parameter, and we get back the object without any additional coding. What it does was internally convert the path variable and access the instance through findOne() on repository instance.

Besides that, spring data web support able to resolve request parameter (page, size and sort) to Pageable instance which then allow us to use it to query list of object with pagination and sortable functionality (refer snippet below).

@RequestMappingpublic String showUsers(Pageable pageable) {
    ...
}

Lifecycle Events

There are few lifecycle event allow us to intercept an object before/after certain action take place.

public class BeforeConvertListener extends AbstractMongoEventListener {

    @Override
    public void onBeforeConvert(Shop s) {
        //change the state of object, or whatever
    }
}
public class BeforeSaveListener extends AbstractMongoEventListener {

    @Override
    public void onBeforeSave(Shop s, DBObject dbo) {
        // change the state of object, or whatever
    }
}

Reference : Spring Data MongoDB – Reference Documentation

Source code available @ github