{"id":862,"date":"2022-02-20T13:18:42","date_gmt":"2022-02-20T09:48:42","guid":{"rendered":"https:\/\/m-shaeri.ir\/blog\/?p=862"},"modified":"2023-03-25T22:48:08","modified_gmt":"2023-03-25T19:18:08","slug":"restful-spring-boot-application-swagger-mysql-docker-a-real-world-example","status":"publish","type":"post","link":"https:\/\/mshaeri.com\/blog\/restful-spring-boot-application-swagger-mysql-docker-a-real-world-example\/","title":{"rendered":"Spring boot Restful API + Swagger + MySQL + Docker A Real World Example"},"content":{"rendered":"\n<p>In this article, we will develop a sample Spring Boot&nbsp;application in IntelliJ IDEA 2021 using Maven. It exposes numbers of RESTful APIs compatible with Open API specifications and It uses Swagger to demonstrate APIs usage documentation in a graphical user interface. I write unit test and integration test for application using H2 in-memory database. And finally I compile, test and run the application in a docker container using docker compose. It is a real world example that covers all parts of an application development including coding, testing, and dockerizing. Let&#8217;s dive into the practice.<\/p>\n\n\n\n<p>We want to build  an application to perform CRUD jobs with rest API. We name it  NoteBook Manager. NoteBook Manager is a tiny application that performs CRUD jobs on NoteBook Entity via Rest Webservices. It exposes 5 endpoint as following list :<\/p>\n\n\n\n<ul><li>GET \/api\/notebooks (get a list of notebooks)<\/li><li>POST \/api\/notebooks (create a notebook)<\/li><li>GET \/api\/notebooks\/1 (get one notebook from the list)<\/li><li>PATCH \/api\/notebooks\/1 (update the price of a single notebook)<\/li><li>DELETE \/api\/notebooks\/1 (delete a single notebook)<\/li><\/ul>\n\n\n\n<p>The project code is already pushed to <a href=\"https:\/\/github.com\/birddevelper\/NoteBookManager\" data-type=\"URL\" data-id=\"https:\/\/github.com\/birddevelper\/NoteBookManager\">GitHub repository<\/a>.&nbsp;<br>This tutorial consists of 7 main sections  :<\/p>\n\n\n\n<ul><li><a href=\"#projectcreation\" data-type=\"internal\" data-id=\"#projectcreation\">Project creation<\/a><\/li><li><a href=\"#openAPI-Dependecy\" data-type=\"internal\" data-id=\"#openAPI-Dependecy\">Open API related dependencies<\/a><\/li><li><a href=\"#entity-repository\" data-type=\"internal\" data-id=\"#entity-repository\">Entity and Repository<\/a><\/li><li><a href=\"#service-controller\" data-type=\"internal\" data-id=\"#service-controller\">Service and Controller<\/a><\/li><li><a href=\"#JPA-configuration-and-preload-data\" data-type=\"internal\" data-id=\"#JPA-configuration-and-preload-data\">JPA configuration and preload data<\/a><\/li><li><a href=\"#test-with-h2-database\" data-type=\"internal\" data-id=\"#test-with-h2-database\">Test endpoints with H2 database<\/a><\/li><li><a href=\"#Creating-Docker-image-and-container\" data-type=\"internal\" data-id=\"#Creating-Docker-image-and-container\">Creating Docker image and container<\/a><\/li><\/ul>\n\n\n\n<h3 class=\"is-style-default has-vivid-cyan-blue-color has-text-color wp-block-heading\" id=\"projectcreation\">Project Creation<\/h3>\n\n\n\n<p>Open New Project window in IntelliJ by going to File -&gt; New -&gt; New Project , and choose Spring Initializr from the left side bar, then put your new project parameters like below image :<\/p>\n\n\n\n<div class=\"wp-block-image is-style-default\"><figure class=\"aligncenter size-full is-resized\"><a href=\"https:\/\/m-shaeri.ir\/blog\/wp-content\/uploads\/2022\/02\/image-4.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/m-shaeri.ir\/blog\/wp-content\/uploads\/2022\/02\/image-4.png\" alt=\"\" class=\"wp-image-873\" width=\"762\" height=\"591\" srcset=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/02\/image-4.png 1016w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/02\/image-4-300x233.png 300w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/02\/image-4-768x596.png 768w\" sizes=\"(max-width: 762px) 100vw, 762px\" \/><\/a><figcaption>IntelliJ New Project Window<\/figcaption><\/figure><\/div>\n\n\n\n<p>As you can see in the above image, I am going to develop this project on Java 8. Click on next button to navigate to  dependencies selection step, there we choose the dependencies we want to use in our project. <\/p>\n\n\n\n<div class=\"wp-block-image is-style-default\"><figure class=\"aligncenter size-full is-resized\"><a href=\"https:\/\/m-shaeri.ir\/blog\/wp-content\/uploads\/2022\/02\/image-5.png\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/m-shaeri.ir\/blog\/wp-content\/uploads\/2022\/02\/image-5.png\" alt=\"\" class=\"wp-image-875\" width=\"767\" height=\"595\" srcset=\"https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/02\/image-5.png 1023w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/02\/image-5-300x233.png 300w, https:\/\/mshaeri.com\/blog\/wp-content\/uploads\/2022\/02\/image-5-768x595.png 768w\" sizes=\"(max-width: 767px) 100vw, 767px\" \/><\/a><figcaption>Select dependencies you need in your project<\/figcaption><\/figure><\/div>\n\n\n\n<p>In the list at the right bottom of the window you can see that I imported the <strong>Lombok<\/strong>, <strong>Spring Boot DevTools<\/strong>, <strong>Spring Web<\/strong>, <strong>Spring HATEOAS<\/strong>, <strong>Spring Data JPA<\/strong>, <strong>H2 Database<\/strong>, and <strong>MySQL Driver <\/strong>into the project dependencies. Let&#8217;s what is the purpose of each of imported library :<\/p>\n\n\n\n<ul><li><strong>Lombok<\/strong> : It is a Java annotation library which helps to reduce boilerplate code in projects. Put simply,  annotations provided in this library, add codes to our project in compile time, for example entities <strong>getter<\/strong> and <strong>setter<\/strong> methods are kind of boilerplate codes that we have to write in every classes. This library undertake this job for us in a very simple way.<\/li><li><strong>Spring Boot DevTools <\/strong>: This library provides fast application restart, live reload and other configuration to enhance development process. <\/li><li><strong>Spring Web<\/strong> : It&nbsp;contains common web specific utilities for Servlet , Portlet environments, and MVC. It uses Apache Tomcat as the default embedded container.<\/li><li><strong>Spring HATEOAS<\/strong>: It&#8217;s an extra level upon REST and it present more information around a REST API to in the API itself, in order to better understanding of the API. For example, when we list all Books, it helps us to put API link to see more details of each book in the API response. (HATEOAS is one of Restful architecture rules to be followed)<\/li><li><strong>Spring Data JPA<\/strong> :  We use it to access and persist data between Java class and database. Its main goals is to reduce the effort of working with database along with separating the database layer from application so that if we change the database, it doesn&#8217;t affect the application. <\/li><li><strong>H2 Database<\/strong> : It is a relational in-memory database that is generally used in unit testing.<\/li><li><strong>MySQL Driver<\/strong> : It allows us to communicate with MySQL database in Spring boot application, we need it here because we want to access data in MySQL DB by Spring Data JPA.<\/li><\/ul>\n\n\n\n<h2 class=\"is-style-default has-vivid-cyan-blue-color has-text-color wp-block-heading\" id=\"openAPI-Dependecy\"> <span style=\"font-size: 1.125rem;\">OpenAPI Related Dependencies<\/span><\/h2>\n\n\n\n<p><strong>OpenAPI<\/strong> is&nbsp;an API description format&nbsp;to describe and visualize RESTful APIs . It allows developers to describe entire API including  available endpoints and methods on each endpoints, operation&#8217;s parameters and their input-output format. <strong>Swagger<\/strong>&nbsp;is a set of tools built based on the OpenAPI specification that facilitate the process of  designing, building, documenting and consuming REST APIs. Here we use <strong>springdoc-openapi-ui<\/strong> library to generate API documentation automatically at runtime. It also contains Swagger-ui in itself, so that our application&#8217;s endpoint can be easily accessed and understood within a single \/swagger-ui.html page . So we add this library to Maven dependency like below :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"xml\" class=\"language-xml\">&lt;dependencies&gt;\n&lt;!-- Other dependencies--&gt;\n&lt;dependency&gt;\n        &lt;groupId&gt;org.springdoc&lt;\/groupId&gt;\n        &lt;artifactId&gt;springdoc-openapi-ui&lt;\/artifactId&gt;\n        &lt;version&gt;1.6.4&lt;\/version&gt;\n    &lt;\/dependency&gt;\n&lt;\/dependencies&gt;<\/code><\/pre>\n\n\n\n<h2 class=\"is-style-default has-vivid-cyan-blue-color has-text-color wp-block-heading\" id=\"entity-repository\">Entity And Repository <\/h2>\n\n\n\n<p>The NoteBook entity class  : <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package bdp.sample.notebookmanager.entities;\n\nimport org.hibernate.annotations.UpdateTimestamp;\nimport org.springframework.hateoas.RepresentationModel;\n\nimport javax.persistence.*;\nimport java.sql.Timestamp;\nimport java.util.Objects;\n\n@Entity\npublic class NoteBook extends RepresentationModel&lt;NoteBook&gt; {\n    private Integer ID;\n    private String name;\n    private double currentPrice;\n    private Timestamp lastUpdate;\n\n    public NoteBook(){\n\n    }\n\n    public NoteBook(Integer ID, String name, double currentPrice){\n        this.ID = ID;\n        this.name = name;\n        this.currentPrice = currentPrice;\n    }\n    public NoteBook(String name, double currentPrice){\n        this.name = name;\n        this.currentPrice = currentPrice;\n    }\n\n    @Id\n    @Column(name = \"id\")\n    @GeneratedValue(strategy = GenerationType.<em>IDENTITY<\/em>) \/\/ id generate by the database native approach\n    public Integer getID() {\n        return ID;\n    }\n\n    public void setID(Integer ID) {\n        this.ID = ID;\n    }\n\n    @Basic\n    @Column(name = \"name\", unique = true)\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    @Basic\n    @Column(name = \"currentPrice\")\n    public double getCurrentPrice() {\n        return currentPrice;\n    }\n\n    public void setCurrentPrice(double currentPrice) {\n        this.currentPrice = currentPrice;\n    }\n\n    @Basic\n    @Column(name = \"lastUpdate\")\n    @UpdateTimestamp\n\n    public Timestamp getLastUpdate() {\n        return lastUpdate;\n    }\n\n    public void setLastUpdate(Timestamp lastUpdate) {\n        this.lastUpdate = lastUpdate;\n    }\n\n    @Override\n    public boolean equals(Object o) {\n        if (this == o) return true;\n        if (o == null || getClass() != o.getClass()) return false;\n        NoteBook notebook = (NoteBook) o;\n        return ID == notebook.ID &amp;&amp; currentPrice == notebook.currentPrice &amp;&amp; Objects.<em>equals<\/em>(name, notebook.name) &amp;&amp; Objects.<em>equals<\/em>(lastUpdate, notebook.lastUpdate);\n    }\n\n    @Override\n    public int hashCode() {\n        return Objects.<em>hash<\/em>(ID, name, currentPrice, lastUpdate);\n    }\n}\n<\/code><\/pre>\n\n\n\n<p>In the  NoteBookRepository interface, I added a custom findAll function that accepts Pageable to add pagination support to our webservice which we will code in next sections:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package bdp.sample.notebookmanager.repositories;\n\nimport org.springframework.data.domain.Page;\nimport org.springframework.data.domain.Pageable;\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.stereotype.Repository;\nimport bdp.sample.notebookmanager.entities.NoteBook;\n\n@Repository\npublic interface NoteBookRepository extends JpaRepository&lt;NoteBook,Integer&gt; {\n\n    public Page&lt;NoteBook&gt; findAll(Pageable page);\n}\n<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"is-style-default has-vivid-cyan-blue-color has-text-color wp-block-heading\" id=\"service-controller\">Service And Controller<\/h2>\n\n\n\n<p>The important note about the service class is the @AutoWired annotation which injects NotebookRepository bean into the service class. All other codes and methods are commented and explained well :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package bdp.sample.notebookmanager.services;\n\n\nimport bdp.sample.notebookmanager.repositories.NoteBookRepository;\nimport org.springframework.beans.factory.annotation.Autowired;\n\n\nimport org.springframework.data.domain.PageRequest;\nimport org.springframework.data.domain.Sort;\nimport org.springframework.stereotype.Service;\nimport bdp.sample.notebookmanager.entities.NoteBook;\n\nimport java.util.List;\nimport java.util.Optional;\n\n@Service\npublic class NoteBookService {\n\n    @Autowired\n    NoteBookRepository noteBookRepository;\n\n    \/\/This method gets page index and page size and returns records from database accordingly\n    public List&lt;NoteBook&gt; listnotebooks(int page, int size){\n        return noteBookRepository.findAll(PageRequest.<em>of<\/em>(page, size, Sort.<em>by<\/em>(\"ID\").ascending())).toList();\n    }\n\n    \/\/ This method gets an Id and returns corresponding notebook if it exists, otherwise it returns null\n    public NoteBook getnotebookById(int notebookId){\n        Optional&lt;NoteBook&gt; notebook = noteBookRepository.findById(notebookId);\n        if (notebook.isPresent())\n            return notebook.get();\n        \/\/ returns null if the given Id doesn't exist\n        return null;\n    }\n\n    \/\/ This method creates given notebook object in the database and returns it with its Id\n    public NoteBook createnotebook(NoteBook notebook){\n\n        return noteBookRepository.save(notebook);\n    }\n\n    \/\/This method gets a notebook object along with its Id, updates the name and currentPrice and returns it back\n    \/\/if the notebook Id doesn't exist, it returns null\n    public NoteBook updatenotebook(int notebookId, NoteBook changednotebook){\n        Optional&lt;NoteBook&gt; notebook = noteBookRepository.findById(notebookId);\n        \/\/Check if the notebook exists\n        if (notebook.isPresent()) {\n            NoteBook tempnotebook= notebook.get();\n            tempnotebook.setName(changednotebook.getName());\n            tempnotebook.setCurrentPrice(changednotebook.getCurrentPrice());\n            \/\/ Save and return updated notebook object\n            return noteBookRepository.save(tempnotebook);\n        }\n\n        \/\/ returns null if the given Id doesn't exist\n        return null;\n    }\n\n    \/\/This method gets a notebook Id and delete the corresponding notebook record in databse\n    \/\/if the notebook Id doesn't exist, it returns null\n    public boolean deletenotebook(int notebookId){\n        Optional&lt;NoteBook&gt; notebook = noteBookRepository.findById(notebookId);\n        \/\/Check if the notebook exists\n        if (notebook.isPresent()) {\n            noteBookRepository.delete(notebook.get());\n            return true;\n        }\n\n        \/\/ returns null if the given Id doesn't exist\n        return false;\n    }\n\n}\n<\/code><\/pre>\n\n\n\n<p>  In the Controller class I Autowired  EntityLinks  from HATEOAS library to add self descriptive links to endpoints&#8217; responses (Why? because being self descriptive, i.e. having link to guide client to fetch more details is one mandatory RESTful rules). To do that, we need to add <strong><em><span class=\"has-inline-color has-vivid-red-color\">@EnableHypermediaSupport(type = EnableHypermediaSupport.HypermediaType.HAL)<\/span><\/em><\/strong> annotation to the main application class . If you remember we extended the NoteBook entity class from <span class=\"has-inline-color has-vivid-cyan-blue-color\">RepresentationModel<\/span> in earlier section , we did it because it allows us to add HATEOAS related fields to the notebook records in output.<\/p>\n\n\n\n<p>Moreover, as you can see in the code, we used  <strong>springdoc-openapi<\/strong> annotations to describe each endpoint in OpenAPI format. Annotations are : <\/p>\n\n\n\n<ul><li>@<span class=\"has-inline-color has-vivid-green-cyan-color\">Operation<\/span>(<span class=\"has-inline-color has-vivid-cyan-blue-color\">summary<\/span> = <span class=\"has-inline-color has-vivid-red-color\">&#8220;description&#8221;<\/span> ) : With this annotation we describe the endpoint and its operation.<\/li><li>@<span class=\"has-inline-color has-vivid-green-cyan-color\">ApiResponses<\/span>(<span class=\"has-inline-color has-vivid-cyan-blue-color\">value =<\/span>{&#8230;}) : A method level annotation to define one or more responses of an endpoint.  <\/li><li>@<span class=\"has-inline-color has-vivid-green-cyan-color\">ApiResponse<\/span>(<span class=\"has-inline-color has-vivid-cyan-blue-color\">responseCode<\/span> = <span class=\"has-inline-color has-vivid-red-color\">&#8220;200&#8221;<\/span>, <span class=\"has-inline-color has-vivid-cyan-blue-color\">description<\/span> = <span class=\"has-inline-color has-vivid-red-color\">&#8220;Successful Operation&#8221;<\/span>,&nbsp; <span class=\"has-inline-color has-vivid-cyan-blue-color\">content<\/span> = { <span class=\"has-inline-color has-vivid-green-cyan-color\">@Content<\/span>(<span class=\"has-inline-color has-vivid-cyan-blue-color\">mediaType<\/span> = <span class=\"has-inline-color has-vivid-red-color\">&#8220;application\/json&#8221;<\/span>) }) : This annotation describe one possible response of an endpoint including its status code, content types, and its description.<\/li><\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package bdp.sample.notebookmanager.controller;\n\n\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.media.Content;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport io.swagger.v3.oas.annotations.responses.ApiResponse;\nimport io.swagger.v3.oas.annotations.responses.ApiResponses;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.dao.DataIntegrityViolationException;\nimport org.springframework.hateoas.*;\nimport org.springframework.hateoas.server.EntityLinks;\nimport org.springframework.hateoas.server.ExposesResourceFor;\nimport org.springframework.hateoas.server.mvc.WebMvcLinkBuilder;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.http.ResponseEntity;\nimport org.springframework.web.bind.MissingServletRequestParameterException;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;\nimport bdp.sample.notebookmanager.entities.NoteBook;\nimport bdp.sample.notebookmanager.services.NoteBookService;\n\nimport java.security.InvalidParameterException;\nimport java.util.List;\n\n@RestController\n@ExposesResourceFor(NoteBook.class)\n@RequestMapping(\"\/api\/notebooks\")\npublic class NoteBookController {\n\n&nbsp; &nbsp; @Autowired\n&nbsp; &nbsp; private EntityLinks entityLinks;\n\n&nbsp; &nbsp; @Autowired\n&nbsp; &nbsp; private NoteBookService noteBookService;\n\n\n&nbsp; &nbsp; \/\/ GET \/api\/notebooks (get a list of notebooks)\n&nbsp; &nbsp; @Operation(summary = \"Get list of notebooks (Ordered by Id in ascending order), you must specify page number and page size\")\n&nbsp; &nbsp; @ApiResponses(value = {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"200\", description = \"Successful Operation\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = { @Content(mediaType = \"application\/json\") }),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"400\", description = \"Invalid parameters supplied\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"500\", description = \"Internal Error\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content)})\n&nbsp; &nbsp; @GetMapping(produces = { \"application\/json\" })\n&nbsp; &nbsp; public ResponseEntity&lt;CollectionModel&lt;NoteBook&gt;&gt; getnotebooks(@Parameter(description = \"Page index (start from 0)\") @RequestParam(value = \"page\") Integer page, @Parameter(description = \"Number of records per page\") @RequestParam(value = \"pageSize\") Integer pageSize) {\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Retrieve requested portion of notebooks from database\n&nbsp; &nbsp; &nbsp; &nbsp; List&lt;NoteBook&gt; notebookList = noteBookService.listnotebooks(page,pageSize);\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Add self Link to each record as url for retrieving the record\n&nbsp; &nbsp; &nbsp; &nbsp; for (NoteBook notebook : notebookList) {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Link recordSelfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(NoteBookController.class)\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .getnotebookById(notebook.getID())).withSelfRel();\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; notebook.add(recordSelfLink);\n&nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; &nbsp; CollectionModel&lt;NoteBook&gt; resources = CollectionModel.of(notebookList);\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ selfLink to api according to HATEOS\n&nbsp; &nbsp; &nbsp; &nbsp; Link selfLink = entityLinks.linkToCollectionResource(NoteBook.class);\n&nbsp; &nbsp; &nbsp; &nbsp; resources.add(selfLink);\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Send data to client as a response\n&nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(EntityModel.of(resources),HttpStatus.OK);\n&nbsp; &nbsp; }\n\n\n&nbsp; &nbsp; \/\/GET \/api\/notebooks\/1 (get one notebook from the list)\n&nbsp; &nbsp; @Operation(summary = \"Get a notebook by its id\")\n&nbsp; &nbsp; @ApiResponses(value = {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"200\", description = \"Found the notebook\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = { @Content(mediaType = \"application\/json\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; schema = @Schema(implementation = NoteBook.class)) }),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"400\", description = \"Invalid id supplied\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"404\", description = \"notebook not found\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"500\", description = \"Internal Error\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content) })\n&nbsp; &nbsp; @GetMapping(value = \"\/{notebookId}\", produces = { \"application\/json\" })\n&nbsp; &nbsp; public ResponseEntity&lt;EntityModel&lt;NoteBook&gt;&gt; getnotebookById(@Parameter(description = \"id of notebook to be retrieved\") &nbsp;@PathVariable int notebookId){\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ selfLink to api according to HATEOS\n&nbsp; &nbsp; &nbsp; &nbsp; Link selfLink = entityLinks.linkToItemResource(NoteBook.class, notebookId);\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Retrieve requested notebook from database\n&nbsp; &nbsp; &nbsp; &nbsp; NoteBook notebook = noteBookService.getnotebookById(notebookId);\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/Check whether the notebook exist or not\n&nbsp; &nbsp; &nbsp; &nbsp; if(notebook!=null) {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; EntityModel&lt;NoteBook&gt; resource = EntityModel.of(notebook);\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; resource.add(selfLink);\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ Send data to client as a response\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(EntityModel.of(resource),HttpStatus.OK);\n&nbsp; &nbsp; &nbsp; &nbsp; }\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ If no notebook is found send 404 status code as response\n&nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(null, HttpStatus.NOT_FOUND);\n&nbsp; &nbsp; }\n\n\n&nbsp; &nbsp; \/\/ POST \/api\/notebooks (create a notebook)\n&nbsp; &nbsp; @Operation(summary = \"Create a new notebook\")\n&nbsp; &nbsp; @ApiResponses(value = {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"201\", description = \"notebook created\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = { @Content(mediaType = \"application\/json\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; schema = @Schema(implementation = NoteBook.class)) }),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"400\", description = \"Invalid id supplied\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"404\", description = \"notebook not found\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"500\", description = \"Internal Error\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"409\", description = \"Duplicate notebook name\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content) })\n&nbsp; &nbsp; @PostMapping\n&nbsp; &nbsp; public ResponseEntity&lt;EntityModel&lt;NoteBook&gt;&gt; createnotebook(@RequestBody NoteBook notebook){\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Create and Save notebook in database\n&nbsp; &nbsp; &nbsp; &nbsp; NoteBook storednotebook = noteBookService.createnotebook(notebook);\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Check whether the notebook is saved or not\n&nbsp; &nbsp; &nbsp; &nbsp; if(notebook!=null) {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ selfLink to api that retrieves the notebook according to HATEOS\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Link recordSelfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(NoteBookController.class)\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .getnotebookById(notebook.getID())).withSelfRel();\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; notebook.add(recordSelfLink);\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ Send created notebook with 201 status code to client\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(EntityModel.of(storednotebook), HttpStatus.CREATED);\n&nbsp; &nbsp; &nbsp; &nbsp; }\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ If no notebook is saved send 304 status code\n&nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(null, HttpStatus.NOT_MODIFIED);\n&nbsp; &nbsp; }\n\n\n&nbsp; &nbsp; \/\/PATCH \/api\/notebooks\/1 (update the price of a single notebook)\n&nbsp; &nbsp; @Operation(summary = \"Update a notebook by its id\")\n&nbsp; &nbsp; @ApiResponses(value = {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"202\", description = \"notebook updated\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = { @Content(mediaType = \"application\/json\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; schema = @Schema(implementation = NoteBook.class)) }),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"400\", description = \"Invalid id supplied\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"404\", description = \"notebook not found\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"500\", description = \"Internal Error\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"409\", description = \"Duplicate notebook name\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content) })\n&nbsp; &nbsp; @PatchMapping(value = \"\/{notebookId}\", produces = { \"application\/json\" })\n&nbsp; &nbsp; public ResponseEntity&lt;EntityModel&lt;NoteBook&gt;&gt; updatenotebook(@Parameter(description = \"id of notebook to be updated\") &nbsp;@PathVariable int notebookId, @Parameter(description = \"notebook updated information\") &nbsp;@RequestBody NoteBook notebook){\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Update notebook in the database\n&nbsp; &nbsp; &nbsp; &nbsp; NoteBook storednotebook = noteBookService.updatenotebook(notebookId,notebook);\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Check whether the notebook exist and is updated or not\n&nbsp; &nbsp; &nbsp; &nbsp; if(storednotebook!=null) {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ selfLink to api that retrieves the notebook according to HATEOS\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Link recordSelfLink = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(NoteBookController.class)\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .getnotebookById(notebook.getID())).withSelfRel();\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; notebook.add(recordSelfLink);\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ Send updated notebook with 202 status code to client\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(EntityModel.of(storednotebook), HttpStatus.ACCEPTED);\n&nbsp; &nbsp; &nbsp; &nbsp; }\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ If no notebook is found send 404 status code as response\n&nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(null, HttpStatus.NOT_FOUND);\n&nbsp; &nbsp; }\n\n&nbsp; &nbsp; \/\/ DELETE\/api\/notebooks\/1 (delete a single notebook)\n&nbsp; &nbsp; @Operation(summary = \"Delete a notebook by its id\")\n&nbsp; &nbsp; @ApiResponses(value = {\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"202\", description = \"notebook deleted\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"400\", description = \"Invalid id supplied\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"404\", description = \"notebook not found\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content),\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; @ApiResponse(responseCode = \"500\", description = \"Internal Error\",\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; content = @Content) })\n&nbsp; &nbsp; @DeleteMapping(value = \"\/{notebookId}\")\n&nbsp; &nbsp; public ResponseEntity&lt;EntityModel&lt;NoteBook&gt;&gt; deletenotebook(@Parameter(description = \"id of notebook to be deleted\") &nbsp;@PathVariable int notebookId){\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Delete the notebook by its Id and check for the result\n&nbsp; &nbsp; &nbsp; &nbsp; if(noteBookService.deletenotebook(notebookId))\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ Sent empty response with 202 status code\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(null, HttpStatus.ACCEPTED);\n&nbsp; &nbsp; &nbsp; &nbsp; else\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; \/\/ If no notebook is found send 404 status code as response\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(null, HttpStatus.NOT_FOUND);\n&nbsp; &nbsp; }\n\n\n\n\n&nbsp; &nbsp; &nbsp; &nbsp; \/\/ Handling exceptions related to user input\n&nbsp; &nbsp; &nbsp; &nbsp; @ExceptionHandler({ MissingServletRequestParameterException.class, InvalidParameterException.class, MethodArgumentTypeMismatchException.class })\n&nbsp; &nbsp; &nbsp; &nbsp; public ResponseEntity&lt;String&gt; handleUserInputException() {\n\n&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(\"Bad Request\", HttpStatus.BAD_REQUEST);\n&nbsp; &nbsp; &nbsp; &nbsp; }\n\n&nbsp; &nbsp; \/\/ Handling runtime exception\n&nbsp; &nbsp; @ExceptionHandler({ RuntimeException.class })\n&nbsp; &nbsp; public ResponseEntity&lt;String&gt; handleRuntimeException() {\n\n&nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(null, HttpStatus.INTERNAL_SERVER_ERROR);\n&nbsp; &nbsp; }\n\n\n\n&nbsp; &nbsp; @ExceptionHandler({ DataIntegrityViolationException.class })\n&nbsp; &nbsp; public ResponseEntity&lt;String&gt; handleDuplicateException() {\n\n&nbsp; &nbsp; &nbsp; &nbsp; return new ResponseEntity(null, HttpStatus.CONFLICT);\n&nbsp; &nbsp; }\n}\n\n<\/code><\/pre>\n\n\n\n<h2 class=\"is-style-default has-vivid-cyan-blue-color has-text-color wp-block-heading\" id=\"JPA-configuration-and-preload-data\"> JPA configuration and preload data<\/h2>\n\n\n\n<p>It is a common approach in unit tests and integration tests to work with H2 database instead of real database of production environment. We declare two <strong>application.properties<\/strong>, one in the <strong>main\/Resources<\/strong> and another one in the <strong>test\/Resources<\/strong>. In the <strong>application.properties<\/strong> file of main package we define the real database specifications and in the other one we define the H2 database specifications  :<\/p>\n\n\n\n<p>Main application.properties, configured to use MySQL database :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">spring.jpa.hibernate.ddl-auto=update\njdbc.driverClassName=com.mysql.cj.jdbc.Driver\nspring.datasource.url=jdbc:mysql:\/\/localhost:3306\/notebook_manager?useSSL=false\nspring.jpa.database-platform = org.hibernate.dialect.MySQL5InnoDBDialect\nspring.datasource.username=root\nspring.datasource.password=<\/code><\/pre>\n\n\n\n<p> Test application.properties, configured to use H2 database : <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">jdbc.driverClassName=org.h2.Driver\nspring.datasource.url=jdbc:h2:mem:myDb;DB_CLOSE_DELAY=-1\nspring.jpa.database-platform=org.hibernate.dialect.H2Dialect\nspring.jpa.hibernate.ddl-auto=create<\/code><\/pre>\n\n\n\n<p>Now that the database configuration is done, we want  to preload a number of notebook records in the database on application startup. So, we add a configuration class with a <strong>CommandLineRunner<\/strong> bean  to our project, the <strong>CommandLineRunner<\/strong> run just after application startup. But, we don&#8217;t need these preload data in the test time. How to prevent  <strong>CommandLineRunner<\/strong> execution in unit test? There exist several way to prevent <strong>CommandLineRunner<\/strong> to be executed at startup. The easiest way that we can prevent it from running is by annotating the class with<em>&nbsp;<\/em><strong><em>@Profile<\/em>(&#8220;!test&#8221;)<\/strong> which means it will not be wired into contexts annotated with <strong><em>@ActiveProfiles(&#8220;test&#8221;)<\/em><\/strong>. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package bdp.sample.notebookmanager.configs;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework.boot.CommandLineRunner;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport bdp.sample.notebookmanager.entities.NoteBook;\nimport bdp.sample.notebookmanager.repositories.NoteBookRepository;\nimport org.springframework.context.annotation.Profile;\n\n\n@Configuration\n@Profile(\"!test\")\nclass InitiateDatabaseRecords  {\n\n    private static final Logger <em>log <\/em>= LoggerFactory.<em>getLogger<\/em>(InitiateDatabaseRecords.class);\n\n\n   @Bean\n    CommandLineRunner initDatabase(NoteBookRepository repository) {\n        if(repository.findAll().size()&lt;1) {\n            return args -&gt; {\n                try {\n                    \/\/ insert initial records into DB\n                    repository.save(new NoteBook(\"Asus Vivo Book S\", 211.9));\n                    repository.save(new NoteBook(\"HP Inspiron\", 299.9));\n                    repository.save(new NoteBook(\"Dell Magic\", 300));\n                    repository.save(new NoteBook(\"Apple MacBook\", 709.9));\n                    repository.save(new NoteBook(\"LG D230\", 299.9));\n                    repository.save(new NoteBook(\"Acer P700\", 199.9));\n\n                    <em>log<\/em>.info(\"Initial records inserted into database.\" );\n                } catch (Exception e) {\n                    <em>log<\/em>.info(\"Error in initiating records into database.\" );\n                }\n            };\n        }\n        else\n            return args -&gt; {\n                <em>log<\/em>.info(\"Records already initiated \");\n            };\n    }\n\n\n\n\n}<\/code><\/pre>\n\n\n\n<h2 class=\"is-style-default has-vivid-cyan-blue-color has-text-color wp-block-heading\" id=\"test-with-h2-database\"> Test endpoints with H2 database<\/h2>\n\n\n\n<p>We use @SpringBootTest to import main application context automatically, and <strong>MockMVC<\/strong> is autowired to help us in simulating endpoints call. As we see, the test class is annotated with <strong><em>@ActiveProfiles(\u201ctest\u201d)<\/em><\/strong> to prevent  <strong>CommandLineRunner<\/strong> to be executed, however the <strong><em>@ActiveProfiles(\u201ctest\u201d)<\/em><\/strong> functionality is not limited to this purpose. The test will automatically read JPA database configuration from <strong>test\/Resources\/application.properties<\/strong>, and we already set its parameters to work with H2 database.  <strong>@Sql <\/strong>annotation with <strong> executionPhase = Sql.ExecutionPhase.<em>BEFORE_TEST_METHOD<\/em><\/strong>  parameter reads the  <strong>beforeTestRun.sql<\/strong> file and execute it before each test,  the file is located in Resources folder. The <strong>beforeTestRun.sql<\/strong> file Contains script to create the table and insert initial test records. And the second <strong>@Sql<\/strong> with  <strong>executionPhase = <\/strong> <strong>Sql.ExecutionPhase.AFTER_TEST_METHOD<\/strong> executes the given sql file after each test, the script within the beforeTestRun.sql contains script to drop the note_book table. For deleting the table after each test we could use  <strong>@DirtiesContext(classMode = DirtiesContext.ClassMode.<em>BEFORE_EACH_TEST_METHOD<\/em>) <\/strong>annotation reset the database and revert any changes made by other tests before starting each test, But the big difference is that it restarts the application as well.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"java\" class=\"language-java\">package bdp.sample.notebookmanager.controller;\n\nimport bdp.sample.notebookmanager.entities.NoteBook;\nimport bdp.sample.notebookmanager.repositories.NoteBookRepository;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.test.autoconfigure.web.client.AutoConfigureWebClient;\nimport org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.test.annotation.DirtiesContext;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.jdbc.Sql;\nimport org.springframework.test.web.servlet.MockMvc;\n\nimport javax.annotation.Resource;\nimport javax.sql.DataSource;\n\nimport java.sql.SQLException;\n\nimport static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;\nimport static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;\n\n\n@SpringBootTest\n@ActiveProfiles(\"test\")\n@AutoConfigureMockMvc\n@AutoConfigureWebClient\n@Sql(executionPhase = Sql.ExecutionPhase.<em>BEFORE_TEST_METHOD<\/em>, scripts = \"classpath:beforeTestRun.sql\")\n@Sql(executionPhase = Sql.ExecutionPhase.<em>AFTER_TEST_METHOD<\/em>, scripts = \"classpath:afterTestRun.sql\")\n\/\/@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)\nclass NoteBookControllerTest {\n\n\n    @Autowired\n    MockMvc mockMvc;\n\n    @Autowired\n    DataSource dataSource;\n\n\n\n\n\n    @Test\n    void getListOfnotebooksWithConfigurablePageSize() throws Exception {\n        \/\/ First page with page size of 2\n        mockMvc.perform(<em>get<\/em>(\"\/api\/notebooks?page=0&amp;pageSize=2\")).andExpect(<em>status<\/em>().isOk())\n                .andExpect(<em>content<\/em>().contentType(\"application\/json\")).andExpect(<em>jsonPath<\/em>(\"$._embedded.noteBookList[0].id\").value(\"1\"))\n                .andExpect(<em>jsonPath<\/em>(\"$._embedded.noteBookList[1].id\").value(\"2\"));\n\n        \/\/ Second page with page size of 3\n        mockMvc.perform(<em>get<\/em>(\"\/api\/notebooks?page=1&amp;pageSize=3\")).andExpect(<em>status<\/em>().isOk())\n                .andExpect(<em>content<\/em>().contentType(\"application\/json\"))\n                .andExpect(<em>jsonPath<\/em>(\"$._embedded.noteBookList[0].id\").value(\"4\"))\n                .andExpect(<em>jsonPath<\/em>(\"$._embedded.noteBookList[1].id\").value(\"5\"));\n\n    }\n\n    @Test\n    void getSingleExistingnotebookById() throws Exception {\n\n        \/\/ Get notebook with id=2\n        mockMvc.perform(<em>get<\/em>(\"\/api\/notebooks\/2\")).andExpect(<em>status<\/em>().isOk())\n                .andExpect(<em>content<\/em>().contentType(\"application\/json\"))\n                .andExpect(<em>jsonPath<\/em>(\"$.id\").value(\"2\"))\n                .andExpect(<em>jsonPath<\/em>(\"$.name\").value(\"HP Inspiron\"));\n\n    }\n\n\n    @Test\n    void getSingleNotExistingnotebookById() throws Exception {\n\n        \/\/ Get notebook with id=22\n        mockMvc.perform(<em>get<\/em>(\"\/api\/notebooks\/22\"))\n                .andExpect(<em>status<\/em>().isNotFound());\n\n    }\n\n    @Test\n    void getSinglenotebookByInvalidnotebookId() throws Exception {\n\n        \/\/ Get notebook with id=i\n        mockMvc.perform(<em>get<\/em>(\"\/api\/notebooks\/i\"))\n                .andExpect(<em>status<\/em>().isBadRequest());\n\n        \/\/ Get notebook with id=*\n        mockMvc.perform(<em>get<\/em>(\"\/api\/notebooks\/*\"))\n                .andExpect(<em>status<\/em>().isBadRequest());\n\n    }\n\n    @Test\n    void updatenotebookWithExistingnotebookId() throws Exception{\n\n        NoteBook notebook = new NoteBook(2,\"HP FitBook\",305.99);\n        ObjectMapper objectMapper = new ObjectMapper();\n        String json = objectMapper.writeValueAsString(notebook);\n        \/\/ Update notebook with id = 2\n        mockMvc.perform(<em>patch<\/em>(\"\/api\/notebooks\/2\").content(json).characterEncoding(\"utf-8\")\n                        .contentType(\"application\/json\"))\n                .andExpect(<em>status<\/em>().isAccepted())\n                .andExpect(<em>jsonPath<\/em>(\"$.name\").value(\"HP FitBook\"))\n                .andExpect(<em>jsonPath<\/em>(\"$.currentPrice\").value(305.99));\n    }\n\n\n    @Test\n    void updatenotebookWithNotExistingnotebookId() throws Exception{\n\n        NoteBook notebook = new NoteBook(77,\"HP FitBook\",305.99);\n        ObjectMapper objectMapper = new ObjectMapper();\n        String json = objectMapper.writeValueAsString(notebook);\n        \/\/ Update notebook with id = 77 which is not existed\n        mockMvc.perform(<em>patch<\/em>(\"\/api\/notebooks\/77\").content(json).characterEncoding(\"utf-8\")\n                        .contentType(\"application\/json\"))\n                .andExpect(<em>status<\/em>().isNotFound());\n\n    }\n\n\n    @Test\n    void createnotebook() throws Exception{\n\n        NoteBook notebook = new NoteBook(\"HP FitBook\",305.99);\n        ObjectMapper objectMapper = new ObjectMapper();\n        String json = objectMapper.writeValueAsString(notebook);\n\n        \/\/ Create new notebook\n        mockMvc.perform(<em>post<\/em>(\"\/api\/notebooks\").content(json).characterEncoding(\"utf-8\")\n                .contentType(\"application\/json\"))\n                .andExpect(<em>status<\/em>().isCreated());\n\n    }\n\n    @Test\n    void createNoteBookWithDuplicateName() throws Exception{\n\n        NoteBook notebook = new NoteBook(\"Dell B3\",305.99);\n        ObjectMapper objectMapper = new ObjectMapper();\n        String json = objectMapper.writeValueAsString(notebook);\n\n        \/\/ Create new notebook with duplicate name\n        mockMvc.perform(<em>post<\/em>(\"\/api\/notebooks\").content(json).characterEncoding(\"utf-8\")\n                        .contentType(\"application\/json\"))\n                        .andExpect(<em>status<\/em>().isConflict());\n\n    }\n\n    @Test\n    void deleteNoteBookWithExistingNoteBookId() throws Exception{\n\n        \/\/ Delete notebook with id = 2\n        mockMvc.perform(<em>delete<\/em>(\"\/api\/notebooks\/2\"))\n                .andExpect(<em>status<\/em>().isAccepted());\n    }\n\n\n}<\/code><\/pre>\n\n\n\n<h2 class=\"is-style-default has-vivid-cyan-blue-color has-text-color wp-block-heading\" id=\"Creating-Docker-image-and-container\"> Creating Docker image and container <\/h2>\n\n\n\n<p>To Dockerize our project we need to build two docker container, one for our application and another for MySQL. We make the image from our application by <strong>DockerFile<\/strong>, and to build the MySQL container and integrate it to the container build from our application docker image we need to use <strong>docker-compose.yml<\/strong> file. We put both of these files into application root path. In the docker file we first clone a java8 image and copy our application source files to it, then after changing the <strong>chmod<\/strong> of <strong>mvnw<\/strong> file, we compile, test and build  the output jar file with <strong>package<\/strong> command of maven, then we configure the container to run the jar file on startup with <strong>ENTRYPOINT<\/strong> command. The DockerFile :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"docker\" class=\"language-docker\">FROM openjdk:8-jdk\nEXPOSE 8090\nWORKDIR \/app\n\n# Copy maven executable to the image\nCOPY mvnw .\nCOPY .mvn .mvn\n\n# Copy the pom.xml file\nCOPY pom.xml .\n\n# Copy the project source\nCOPY .\/src .\/src\nCOPY .\/pom.xml .\/pom.xml\n\nRUN chmod 755 \/app\/mvnw\n\nRUN .\/mvnw dependency:go-offline -B\n\nRUN .\/mvnw package \n#RUN ls -al\nENTRYPOINT [\"java\",\"-jar\",\"target\/NoteBookManagerRest-0.0.1-SNAPSHOT.jar\"]<\/code><\/pre>\n\n\n\n<p>In the<strong> docker-compose.yml <\/strong>file, we first clone the mysql image from dockerhub or local host and start it, then we make our application image by referencing to the DockerFile that already defined, but we wait until the mysql container to be started and fully operational before we create\/start our application container.  we do that by <strong>depends_on<\/strong> and <strong>healthcheck<\/strong>.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"docker\" class=\"language-docker\">version: '2.1'\n\nservices:\n    <em>#service 1: mysql database image creation\n    <\/em>db:\n      image: mysql:5.6.17\n      container_name: mysql-db  \n      environment:\n        - MYSQL_ROOT_PASSWORD=spring\n      ports:\n        - \"3306:3306\"\n      restart: always\n      healthcheck:\n          test: [\"CMD\", \"mysqladmin\" ,\"ping\", \"-h\", \"localhost\"]\n          timeout: 20s\n          retries: 10\n\n    \n    <em># service 2: NoteBook Manager app image creation\n    <\/em>notebookervice:\n      image: notebook-service\n      container_name: notebook-service-app     <em># name of container created from image\n      <\/em>build:\n        context: .                          <em># path to docker file\n        <\/em>dockerfile: Dockerfile              <em># docker file name\n      <\/em>ports:\n        - \"8090:8080\"                       <em># stock service port\n      <\/em>restart: always\n\n      depends_on:                           <em>#define dependencies of this app\n         <\/em>db :                              <em>#dependency name (which is defined with this name 'db' in this file earlier)\n           <\/em>condition: service_healthy\n      environment:\n        SPRING_DATASOURCE_URL: jdbc:mysql:\/\/mysql-db:3306\/notebook_manager?createDatabaseIfNotExist=true&amp;useSSL=false\n        SPRING_DATASOURCE_USERNAME: root\n        SPRING_DATASOURCE_PASSWORD: spring<\/code><\/pre>\n\n\n\n<p>Finally, execcute following command in the application root path to build your docker containers :<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code lang=\"bash\" class=\"language-bash\">docker-compose up<\/code><\/pre>\n\n\n\n<p>Thanks to SwaggerUI, you can see endpoints documentation in a graphical user interface and try out their functionality and see the response. After running the containers, you can access the application links with :<\/p>\n\n\n\n<ul><li>Application Rest services :&nbsp;<a href=\"http:\/\/localhost:8090\/api\/notebooks\">http:\/\/localhost:8090\/api\/notebooks<\/a><\/li><li>SwaggerUI :&nbsp;<a href=\"http:\/\/localhost:8090\/swagger-ui.html\">http:\/\/localhost:8090\/swagger-ui.html<\/a><\/li><\/ul>\n\n\n\n<p>Happy coding!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, we will develop a sample Spring Boot&nbsp;application in IntelliJ IDEA 2021 using Maven. It exposes numbers of RESTful APIs compatible with Open &hellip; <\/p>\n","protected":false},"author":1,"featured_media":930,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,124,69,123,125,35,70,122],"tags":[115,120,117,9,64,114,113,118,119,63,71,112,121,116],"_links":{"self":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/862"}],"collection":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/comments?post=862"}],"version-history":[{"count":0,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/posts\/862\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/media\/930"}],"wp:attachment":[{"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/media?parent=862"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/categories?post=862"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mshaeri.com\/blog\/wp-json\/wp\/v2\/tags?post=862"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}