📜 ⬆️ ⬇️

Introduction to Spring Boot with Spring Data Mongo

Hello. We are glad to congratulate students on their professional holiday and to report that in February we start the course “Developer on the Spring Framework” ! This will be the subject of today's publication.

Justice League is in danger, and only Alfred can save everyone - with a new management system with Spring Boot, Spring Data and MongoDB.

For the League of Justice dark times came when the awesome Darkseid decided to enslave humanity. Batman and Wonder Woman are looking for members of the League, and all they lack is one important point - a proper management system for members of the Justice League.



There is not enough time to create a bulky project from scratch, so Batman passes this difficult task to his dear Alfred (after all, Robin is too unpredictable), who recalls something called Spring Boot that will help not to waste time on solving small nuances of project setting and quickly go to writing application code.
So our dear Alfred is starting to use Spring Boot to quickly create a management system for members of the Justice League. At a minimum, its back-end parts, since Batman works directly with the REST API.

There are many convenient ways to configure Spring Boot applications. In this article, we will focus on the traditional method of downloading a package (Spring CLI) and its settings from scratch on Ubuntu. Spring also supports packing a project online using their tool . Download the latest stable version here . In this article I use version 1.3.0.M1.

After unpacking the downloaded archive, first of all we will configure the following parameters in the profile:

SPRING_BOOT_HOME=<extracted path>/spring-1.3.0.M1 PATH=$SPRING_BOOT_HOME/bin:$PATH 

Then add the following to the bashrc file:

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

This will add autocompletion to the command line when working with spring-cli to create Spring Boot applications. Remember that you need to “source” the profile and “bashrc” files to confirm the changes.

This article uses the following technology stack:


Start by creating an application project template by running the following command. Please note that the sample project can be downloaded from my GitHub repository 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 MongoDB embedded.
By default, spring-cli creates a project called “Demo”. Therefore, we need to rename the corresponding generated application class. If you used the source from my GitHub repository mentioned above, you can skip this step.
When using Spring Boot, launching an application is as simple as launching a JAR file created by a project. Which just calls the application class annotated by @SpringBootApplication to load Spring. Let's see what it looks like:

 package com.justiceleague.justiceleaguemodule; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Основное приложение spring boot, которое запустит веб-контейнер и подключит все * необходимые компоненты * * @author dinuka * */ @SpringBootApplication public class JusticeLeagueManagementApplication { public static void main(String[] args) { SpringApplication.run(JusticeLeagueManagementApplication.class, args); } } 

Then we go to the domain classes, where Spring Data along with MongoDB is used to determine the level of data. Domain class looks like this:

 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; /** * В этом классе содержатся подробности о членах Лиги Справедливости, которые * будут храниться в 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; } } 

Spring Data is intuitive, especially if you have experience in JPA / Hibernate. Annotations are very similar. The only new thing - abstract

 @Document 

which means the name of the collection in the Mongo database. There is also an index defined for superhero names, as many queries will concern searches by name.

Spring Data introduced the functionality of a simple definition of the repository, which supports normal CRUD operations and some read operations from the box. Thus, in our application we use the capabilities of Spring Data repositories, as well as the class of the repository 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 > { /** * Этот метод извлекает подробности об участнике лиги справедливости, связанным с . * переданным именем. * * @param superHeroName * имя участника лиги справедливости для поиска и извлечения. * @return возвращает инстанс {@link JusticeLeagueMemberDetail} с подробностями * об участнике. */ @Query("{ 'name' : {$regex: ?0, $options: 'i' }}") JusticeLeagueMemberDetail findBySuperHeroName(final String superHeroName); } 

Standard save operations are built into Spring by the proxy through the runtime, so you just need to define the domain class in the repository.

As you can see, only one method is defined. With the @Query annotation @Query we are looking for a superhero using regular expressions. The “i” option means that you need to ignore the case when trying to find a match in MongoDB.

Then, we proceed to the implementation of the logic of storage of new members of the Justice League through the service level.



 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; /** * Этот класс сервиса реализует {@link JusticeLeagueMemberService} для обеспечения * функциональности, необходимой системе лиги справедливости * * @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 simple - if the participant already exists, then we give an error. Otherwise, add a member. Note that the already implemented Spring Data repository insert method is used here, which we defined earlier.

Finally, Alfred is ready to show the new functionality he developed through the REST API using Spring REST, so that Batman starts sending out the details via HTTP — he is constantly on the go:

 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; /** * Этот класс открывает REST API системе. * * @author dinuka * */ @RestController @RequestMapping("/justiceleague") public class JusticeLeagueManagementController { @Autowired private JusticeLeagueMemberService memberService; /** * Этот метод будет использоваться для добавления новых участников лиги справедливости в систему * * @param justiceLeagueMember * участник лиги для добавления. * @return an instance of {@link ResponseDTO} который уведомит об успешности * добавления нового члена. */ @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; } } 

Batman is not enough, so we provide functionality in the form of a JSON payload, although Alfred is old-fashioned and would prefer XML.

Our old friend Alfred is a TDD minion (test-driven development, translated as “development through testing”), therefore he wants to test the functionality. And here we look at the integration tests written by Alfred to make sure that the initial version of the Justice League management system is correct and predictable. Please note that here we only show REST API tests. Alfred has embraced a larger volume, which can be found in the GitHub repository.

 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; /** * В этом классе будет функциональность, необходимая для запуска интеграционных тестов, * чтобы не реализовывать одно и то же несколько раз в индивидуальных классах. * * @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); } /** * Здесь мы настраиваем встроенный инстанс mongodb для запуска с нашими * интеграционными тестами. * * @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; /** * Этот класс протестирует работу уровня REST-контроллера, встроенного * {@link JusticeLeagueManagementController} * * @author dinuka * */ public class JusticeLeagueManagementControllerTest extends BaseIntegrationTest { /** * Этот метод протестирует успешность добавления участника лиги справедливости при * передаче корректных данных. * * @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)); } /** * Этот метод проверит, будет ли получен ответ об ошибке * при попытке добавить члена, который уже существует в системе. * * @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)); } /** * Этот метод проверит, будет ли получена валидная ошибка клиента, * если не будут переданы необходимые данные с полезной нагрузкой JSON запроса. * В нашем случае - имя супергероя. * * @throws Exception */ @Test public void testAddJusticeLeagueMemberWhenNameNotPassedIn() throws Exception { // Здесь передается пустое имя супергероя, чтобы проверить // начинается ли обработка ошибки валидации. 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()); } } 

Here, perhaps, that's all. With the help of Spring Boot, Alfred managed in the shortest possible time to make a minimally functioning Justice League management system with the REST API. Over time, we will expand the functionality of this application and see how Alfred will find an approach to deploying an application through Docker on an Amazon AWS instance managed by Kubernetes. We are waiting for exciting times, so get connected!

Traditionally, we are waiting for your comments and invite you to an open webinar , which will be held on February 6 by Ph.D. in Physics and Mathematics - Yury Dvorzhetsky .

Source: https://habr.com/ru/post/437410/