Spring Boot Exercise
Create the Basic Application
- Navigate to https://start.spring.io
- Create a Maven project with Java and the latest version of Spring Boot (2.1.1 at the time of writing)
- Specify group:
microservice.workshop
- Specify artifact:
springboot-demo
- For dependencies, add the following:
- Web
- Actuator
- JPA
- H2
- Generate the project (causes a download)
- Unzip the downloaded file somewhere convenient
- Add the new project to your IDE workspace
- Eclipse: File->Import->Existing Maven Project
- IntelliJ: File->New->Module From Existing Sources…
- VS Code: File->Add Folder to Workspace
- Rename
application.properties
insrc/main/resources
toapplication.yml
Configure The Info Actuator
- Open
application.yml
insrc/main.resources
-
Add this value
info: app: name: Person Service management: endpoint: health: show-details: always
Configure Swagger
-
Open
pom.xml
, add the following dependencies:<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>
-
Create a class
SwaggerConfiguration
in themicoservice.workshop.springbootdemo
package. Add the following:package microservice.workshop.springbootdemo; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.view.RedirectView; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; @Configuration @EnableSwagger2 @Controller public class SwaggerConfiguration { @RequestMapping("/") public RedirectView redirectToSwagger() { return new RedirectView("swagger-ui.html"); } @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class)) .build(); } }
This configuration does three important things:
- It enables Swagger
- It redirects the root URL to the Swagger UI. I find this convenient, but YMMV
- It tells Springfox that we only want to use Swagger for REST controllers. Without this there will be Swagger documentation for the redirect controller, as well as the basic Spring error controller and we usually don’t want this.
Create a Person Repository
- Create a package
microservice.workshop.springbootdemo.model
- Create a class in the new package called
Person
-
Set the content of
Person
to the following:package microservice.workshop.springbootdemo.model; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String firstName; private String lastName; // getters/setters }
- Create a package
microservice.workshop.springbootdemo.data
- Create an interface in the new package called
PersonRepository
-
Set the content of
PersonRepository
to the following:package microservice.workshop.springbootdemo.data; import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; import microservice.workshop.springbootdemo.model.Person; public interface PersonRepository extends JpaRepository<Person, Integer> { List<Person> findByLastName(String lastName); }
-
Create a file called
import.sql
insrc/main/resources
. Set the contents to the following:insert into person(first_name, last_name) values('Fred', 'Flintstone'); insert into person(first_name, last_name) values('Wilma', 'Flintstone'); insert into person(first_name, last_name) values('Barney', 'Rubble'); insert into person(first_name, last_name) values('Betty', 'Rubble');
Create a REST Controller
- Create a package
microservice.workshop.springbootdemo.http
- Create a class in the new package called
PersonController
-
Set the content of
PersonController
to the following:package microservice.workshop.springbootdemo.http; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import microservice.workshop.springbootdemo.data.PersonRepository; import microservice.workshop.springbootdemo.model.Person; @RestController @RequestMapping("/person") public class PersonController { @Autowired private PersonRepository personRepository; @GetMapping public ResponseEntity<List<Person>> findAll() { return ResponseEntity.ok(personRepository.findAll()); } @GetMapping("/{id}") public ResponseEntity<Person> findById(@PathVariable("id") Integer id) { return ResponseEntity.of(personRepository.findById(id)); } @GetMapping("/search") public ResponseEntity<List<Person>> search(@RequestParam("lastName") String lastName) { return ResponseEntity.ok(personRepository.findByLastName(lastName)); } }
Unit Tests
- Make a new package
microservice.workshop.springbootdemo.http
in thesrc/test/java
tree - Create a class in the new package called
PersonControllerTest
-
Set the content of
PersonControllerTest
to the following:package microservice.workshop.springbootdemo.http; import static org.hamcrest.Matchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.web.context.WebApplicationContext; @RunWith(SpringRunner.class) @SpringBootTest public class PersonControllerTest { private MockMvc mockMvc; @Autowired private WebApplicationContext webApplicationContext; @Before public void setup() { this.mockMvc = webAppContextSetup(webApplicationContext).build(); } @Test public void testFindAll() throws Exception { mockMvc.perform(get("/person")) .andExpect(status().is(HttpStatus.OK.value())) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$", hasSize(4))); } @Test public void testFindOne() throws Exception { mockMvc.perform(get("/person/1")) .andExpect(status().is(HttpStatus.OK.value())) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$.firstName", is("Fred"))) .andExpect(jsonPath("$.lastName", is("Flintstone"))); } @Test public void testFindNone() throws Exception { mockMvc.perform(get("/person/22")) .andExpect(status().is(HttpStatus.NOT_FOUND.value())); } @Test public void testSearch() throws Exception { mockMvc.perform(get("/person/search?lastName=Rubble")) .andExpect(status().is(HttpStatus.OK.value())) .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) .andExpect(jsonPath("$", hasSize(2))); } }
Testing
- Run the unit tests:
- (Windows Command Prompt)
mvnw clean test
- (Windows Powershell)
.\mvnw clean test
- (Mac/Linux)
./mvnw clean test
- Or your IDE’s method of running tests
- (Windows Command Prompt)
- Start the application:
- (Windows Command Prompt)
mvnw spring-boot:run
- (Windows Powershell)
.\mvnw spring-boot:run
- (Mac/Linux)
./mvnw spring-boot:run
- Or your IDE’s method of running the main application class
- (Windows Command Prompt)
- Test Swagger http://localhost:8080
- Test the acuator health endpoint http://localhost:8080/actuator/health
- Test the acuator info endpoint http://localhost:8080/actuator/info