Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

An Intro to Spring Boot With Spring Data Mongo

DZone's Guide to

An Intro to Spring Boot With Spring Data Mongo

The Justice League is in trouble, and only Alfred can save the day—with a new management system built with Spring Boot, Spring Data, and MongoDB.

· Java Zone ·
Free Resource

Download Microservices for Java Developers: A hands-on introduction to frameworks and containers. Brought to you in partnership with Red Hat.

Dark times are ahead for the Justice League with the formidable Darkseid coming over to conquer human kind. Batman, with the help of Wonder Woman, is on a quest to get the league together with one critical aspect missing — a proper Justice League member management system.

As time is not on their side, they do not want to go through the cumbersome process of setting up a project from scratch with all the things they need. Batman hands over this daunting task of building a rapid system to his beloved trusted Alfred (as Robin is so unpredictable), who tells Batman that he recalls coming across something called Spring Boot, which helps set up everything you need so you can get to writing code for your application rather than being bogged down with the minor nuances of setting up the configuration for your project.

Let's get onto it with our beloved Alfred, who will utilize Spring Boot to build a Justice League member management system in no time. Well, at least the back-end part for now, since Batman likes dealing directly with the REST APIs.

There are many convenient ways of setting up a Spring Boot application. For this article, we will focus on the traditional way of downloading the package (Spring CLI) and setting it up from scratch on Ubuntu. Spring also supports getting a project packaged online via their tool. You can download the latest stable release from here. For this post, I am using the 1.3.0.M1 release.

After extracting your downloaded archive, first off, set the following parameters on your profile;

SPRING_BOOT_HOME=<extracted path>/spring-1.3.0.M1

PATH=$SPRING_BOOT_HOME/bin:$PATH


Afterward, in your "bashrc" file, include the following;

. <extracted-path>/spring-1.3.0.M1/shell-completion/bash/spring


That last execution does give you auto completion on the command line when you are dealing with spring-cli to create your Spring Boot applications. Please remember to "source" both the profile and the "bashrc" files for the changes to take effect.

Our technology stack used in this article will be as follows;:

  • Spring REST
  • Spring Data
  • MongoDB

So let's start off by creating the template project for the application by issuing the following command. Note that the sample project can be downloaded from my GitHub repository found here.

spring init -dweb,data-mongodb,flapdoodle-mongo  --groupId com.justiceleague --artifactId justiceleaguemodule --build maven justiceleaguesystem


This will generate a maven project with Spring MVC and Spring Data with an embedded MongoDB. 

By default, spring-cli creates a project with the name set as "Demo". So we will need to rename the respective application class generated. If you checked out the source from my GitHub repository mentioned above, then this will already be done.

With Spring Boot, running the application is as easy as running the JAR file created by the project, which essentially calls the application class annotated with @SpringBootApplication that boots up Spring. Let us see how that looks:

package com.justiceleague.justiceleaguemodule;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * The main spring boot application which will start up a web container and wire
 * up all the required beans.
 * 
 * @author dinuka
 *
 */
@SpringBootApplication
public class JusticeLeagueManagementApplication {

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


We then move onto our domain classes, where we use Spring Data along with MongoDB to define our data layer. The domain class is as follows:

package com.justiceleague.justiceleaguemodule.domain;

import org.bson.types.ObjectId;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

/**
 * This class holds the details that will be stored about the justice league
 * members on MongoDB.
 * 
 * @author dinuka
 *
 */
@Document(collection = "justiceLeagueMembers")
public class JusticeLeagueMemberDetail {

    @Id
    private ObjectId id;

    @Indexed
    private String name;

    private String superPower;

    private String location;

    public JusticeLeagueMemberDetail(String name, String superPower, String location) {
        this.name = name;
        this.superPower = superPower;
        this.location = location;
    }

    public String getId() {
        return id.toString();
    }

    public void setId(String id) {
        this.id = new ObjectId(id);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSuperPower() {
        return superPower;
    }

    public void setSuperPower(String superPower) {
        this.superPower = superPower;
    }

    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }

}


As we are using Spring Data, it is fairly intuitive, especially if you are coming from a JPA/Hibernate background. The annotations are very similar. The only new thing would be the @Document annotation, which denotes the name of the collection in our Mongo database. We also have an index defined for the name of the superhero, since more queries will revolve around searching by name.

With Spring Data came the functionality of easily defining your repositories that support the usual CRUD operations and some read operations straight out of the box without you having to write them. So we utilize the power of Spring Data repositories in our application as well and the repository class as follows:

package com.justiceleague.justiceleaguemodule.dao;

import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.data.mongodb.repository.Query;

import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail;

public interface JusticeLeagueRepository extends MongoRepository < JusticeLeagueMemberDetail, String > {

    /**
     * This method will retrieve the justice league member details pertaining to
     * the name passed in.
     * 
     * @param superHeroName
     *            the name of the justice league member to search and retrieve.
     * @return an instance of {@link JusticeLeagueMemberDetail} with the member
     *         details.
     */
    @Query("{ 'name' : {$regex: ?0, $options: 'i' }}")
    JusticeLeagueMemberDetail findBySuperHeroName(final String superHeroName);
}


The usual saving operations are implemented by Spring at runtime through the use of proxies, and we just have to define our domain class in our repository.

As you can see, we have only one method defined. With the @Query annotation, we are trying to find a super hero with the use of regular expressions. The option "i" denotes that we should ignore case when trying to find a match in MongoDB.

Next up,  we move onto implementing our logic to storing the new Justice League members through our service layer.

package com.justiceleague.justiceleaguemodule.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.justiceleague.justiceleaguemodule.constants.MessageConstants.ErrorMessages;
import com.justiceleague.justiceleaguemodule.dao.JusticeLeagueRepository;
import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail;
import com.justiceleague.justiceleaguemodule.exception.JusticeLeagueManagementException;
import com.justiceleague.justiceleaguemodule.service.JusticeLeagueMemberService;
import com.justiceleague.justiceleaguemodule.web.dto.JusticeLeagueMemberDTO;
import com.justiceleague.justiceleaguemodule.web.transformer.DTOToDomainTransformer;

/**
 * This service class implements the {@link JusticeLeagueMemberService} to
 * provide the functionality required for the justice league system.
 * 
 * @author dinuka
 *
 */
@Service
public class JusticeLeagueMemberServiceImpl implements JusticeLeagueMemberService {

    @Autowired
    private JusticeLeagueRepository justiceLeagueRepo;

    /**
     * {@inheritDoc}
     */
    public void addMember(JusticeLeagueMemberDTO justiceLeagueMember) {
        JusticeLeagueMemberDetail dbMember = justiceLeagueRepo.findBySuperHeroName(justiceLeagueMember.getName());

        if (dbMember != null) {
            throw new JusticeLeagueManagementException(ErrorMessages.MEMBER_ALREDY_EXISTS);
        }
        JusticeLeagueMemberDetail memberToPersist = DTOToDomainTransformer.transform(justiceLeagueMember);
        justiceLeagueRepo.insert(memberToPersist);
    }

}


Again quite trivial, if the member already exists, we throw out an error. Otherwise, we add the member. Here you can see we are using the already-implemented insert method of the Spring Data repository we just defined before.

Finally, Alfred is ready to expose the new functionality he just developed via a REST API using Spring REST so that Batman can start sending in the details over HTTP — as he is always traveling:

package com.justiceleague.justiceleaguemodule.web.rest.controller;

import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.justiceleague.justiceleaguemodule.constants.MessageConstants;
import com.justiceleague.justiceleaguemodule.service.JusticeLeagueMemberService;
import com.justiceleague.justiceleaguemodule.web.dto.JusticeLeagueMemberDTO;
import com.justiceleague.justiceleaguemodule.web.dto.ResponseDTO;

/**
 * This class exposes the REST API for the system.
 * 
 * @author dinuka
 *
 */
@RestController
@RequestMapping("/justiceleague")
public class JusticeLeagueManagementController {

    @Autowired
    private JusticeLeagueMemberService memberService;

    /**
     * This method will be used to add justice league members to the system.
     * 
     * @param justiceLeagueMember
     *            the justice league member to add.
     * @return an instance of {@link ResponseDTO} which will notify whether
     *         adding the member was successful.
     */
    @ResponseBody
    @ResponseStatus(value = HttpStatus.CREATED)
    @RequestMapping(method = RequestMethod.POST, path = "/addMember", produces = {
        MediaType.APPLICATION_JSON_VALUE
    }, consumes = {
        MediaType.APPLICATION_JSON_VALUE
    })
    public ResponseDTO addJusticeLeagueMember(@Valid @RequestBody JusticeLeagueMemberDTO justiceLeagueMember) {
        ResponseDTO responseDTO = new ResponseDTO(ResponseDTO.Status.SUCCESS,
            MessageConstants.MEMBER_ADDED_SUCCESSFULLY);
        try {
            memberService.addMember(justiceLeagueMember);
        } catch (Exception e) {
            responseDTO.setStatus(ResponseDTO.Status.FAIL);
            responseDTO.setMessage(e.getMessage());
        }
        return responseDTO;
    }
}


We expose our functionality as a JSON payload as Batman just cannot get enough of it, although Alfred is a bit old school and prefers XML sometimes.

The old guy Alfred still wants to test out his functionality, as TDD is just his style. So finally we look at the integration tests written up by Alfred to make sure the initial version of the Justice League management system is working as expected. Note that we are only showing the REST API tests here, although Alfred has actually covered more, which you can check out on the GitHub repo.

package com.justiceleague.justiceleaguemodule.test.util;

import java.io.IOException;
import java.net.UnknownHostException;

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail;

import de.flapdoodle.embed.mongo.MongodExecutable;
import de.flapdoodle.embed.mongo.MongodStarter;
import de.flapdoodle.embed.mongo.config.IMongodConfig;
import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
import de.flapdoodle.embed.mongo.config.Net;
import de.flapdoodle.embed.mongo.distribution.Version;

/**
 * This class will have functionality required when running integration tests so
 * that invidivual classes do not need to implement the same functionality.
 * 
 * @author dinuka
 *
 */
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public abstract class BaseIntegrationTest {

    @Autowired
    protected MockMvc mockMvc;

    protected ObjectMapper mapper;

    private static MongodExecutable mongodExecutable;

    @Autowired
    protected MongoTemplate mongoTemplate;

    @Before
    public void setUp() {
        mapper = new ObjectMapper();
    }

    @After
    public void after() {
        mongoTemplate.dropCollection(JusticeLeagueMemberDetail.class);
    }

    /**
     * Here we are setting up an embedded mongodb instance to run with our
     * integration tests.
     * 
     * @throws UnknownHostException
     * @throws IOException
     */
    @BeforeClass
    public static void beforeClass() throws UnknownHostException, IOException {

        MongodStarter starter = MongodStarter.getDefaultInstance();

        IMongodConfig mongoConfig = new MongodConfigBuilder().version(Version.Main.PRODUCTION)
            .net(new Net(27017, false)).build();

        mongodExecutable = starter.prepare(mongoConfig);

        try {
            mongodExecutable.start();
        } catch (Exception e) {
            closeMongoExecutable();
        }
    }

    @AfterClass
    public static void afterClass() {
        closeMongoExecutable();
    }

    private static void closeMongoExecutable() {
        if (mongodExecutable != null) {
            mongodExecutable.stop();
        }
    }

}


package com.justiceleague.justiceleaguemodule.web.rest.controller;

import org.hamcrest.beans.SamePropertyValuesAs;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import com.justiceleague.justiceleaguemodule.constants.MessageConstants;
import com.justiceleague.justiceleaguemodule.constants.MessageConstants.ErrorMessages;
import com.justiceleague.justiceleaguemodule.domain.JusticeLeagueMemberDetail;
import com.justiceleague.justiceleaguemodule.test.util.BaseIntegrationTest;
import com.justiceleague.justiceleaguemodule.web.dto.JusticeLeagueMemberDTO;
import com.justiceleague.justiceleaguemodule.web.dto.ResponseDTO;
import com.justiceleague.justiceleaguemodule.web.dto.ResponseDTO.Status;

/**
 * This class will test out the REST controller layer implemented by
 * {@link JusticeLeagueManagementController}
 * 
 * @author dinuka
 *
 */
public class JusticeLeagueManagementControllerTest extends BaseIntegrationTest {

    /**
     * This method will test if the justice league member is added successfully
     * when valid details are passed in.
     * 
     * @throws Exception
     */
    @Test
    public void testAddJusticeLeagueMember() throws Exception {

        JusticeLeagueMemberDTO flash = new JusticeLeagueMemberDTO("Barry Allen", "super speed", "Central City");
        String jsonContent = mapper.writeValueAsString(flash);
        String response = mockMvc
            .perform(MockMvcRequestBuilders.post("/justiceleague/addMember").accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON).content(jsonContent))
            .andExpect(MockMvcResultMatchers.status().isCreated()).andReturn().getResponse().getContentAsString();

        ResponseDTO expected = new ResponseDTO(Status.SUCCESS, MessageConstants.MEMBER_ADDED_SUCCESSFULLY);
        ResponseDTO receivedResponse = mapper.readValue(response, ResponseDTO.class);

        Assert.assertThat(receivedResponse, SamePropertyValuesAs.samePropertyValuesAs(expected));

    }

    /**
     * This method will test if an appropriate failure response is given when
     * the member being added already exists within the system.
     * 
     * @throws Exception
     */
    @Test
    public void testAddJusticeLeagueMemberWhenMemberAlreadyExists() throws Exception {
        JusticeLeagueMemberDetail flashDetail = new JusticeLeagueMemberDetail("Barry Allen", "super speed",
            "Central City");
        mongoTemplate.save(flashDetail);

        JusticeLeagueMemberDTO flash = new JusticeLeagueMemberDTO("Barry Allen", "super speed", "Central City");
        String jsonContent = mapper.writeValueAsString(flash);
        String response = mockMvc
            .perform(MockMvcRequestBuilders.post("/justiceleague/addMember").accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON).content(jsonContent))
            .andExpect(MockMvcResultMatchers.status().isCreated()).andReturn().getResponse().getContentAsString();

        ResponseDTO expected = new ResponseDTO(Status.FAIL, ErrorMessages.MEMBER_ALREDY_EXISTS);
        ResponseDTO receivedResponse = mapper.readValue(response, ResponseDTO.class);
        Assert.assertThat(receivedResponse, SamePropertyValuesAs.samePropertyValuesAs(expected));
    }

    /**
     * This method will test if a valid client error is given if the data
     * required are not passed within the JSON request payload which in this
     * case is the super hero name.
     * 
     * @throws Exception
     */
    @Test
    public void testAddJusticeLeagueMemberWhenNameNotPassedIn() throws Exception {
        // The superhero name is passed in as null here to see whether the
        // validation error handling kicks in.
        JusticeLeagueMemberDTO flash = new JusticeLeagueMemberDTO(null, "super speed", "Central City");
        String jsonContent = mapper.writeValueAsString(flash);
        mockMvc.perform(MockMvcRequestBuilders.post("/justiceleague/addMember").accept(MediaType.APPLICATION_JSON)
                .contentType(MediaType.APPLICATION_JSON).content(jsonContent))
            .andExpect(MockMvcResultMatchers.status().is4xxClientError());

    }

}


And that is about it. With the power of Spring Boot, Alfred was able to get a bare minimum Justice League management system with a REST API exposed in no time. We will build upon this application in time and see how Alfred comes up with getting this application deployed via Docker to an Amazon AWS instance managed by Kubernetes. Exciting times are ahead, so tune in.

Download Building Reactive Microservices in Java: Asynchronous and Event-Based Application Design. Brought to you in partnership with Red Hat

Topics:
java ,spring boot ,mongodb ,management system ,tutorial

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}