Building a RESTful Web Service with Spring Boot using an H2 in-memory database and also an external MySQL database

4

In a previous article, I talked about an environment, I prepared on my Windows laptop, with a guest Operating System, Docker and Minikube available within an Oracle VirtualBox appliance.

In this article, I will create two versions of a RESTful Web Service Spring Boot application that, later on (in another article), I will be running in Minikube.
The Spring Boot application I create in this article is a book service. With this service you can add, update, delete and retrieve books from a catalog.
The application uses an H2 in-memory database but is also prepared for using an external MySQL database. For demo purposes I created a 1.0 and 2.0 version of the application.
The latter has some additional fields representing a book.

In a next article I will be describing how these applications will be used in Minikube, together with an external “Dockerized” MySQL database.

Minikube

Minikube is a tool that makes it easy to run Kubernetes locally. Minikube runs a single-node Kubernetes cluster for users looking to try out Kubernetes or develop with it day-to-day.
[https://kubernetes.io/docs/setup/minikube/]

Spring Boot

Spring Boot makes it easy to create stand-alone, production-grade Spring based Applications that you can “just run”.
[https://spring.io/projects/spring-boot]

Spring Initializr

To bootstrap my application, I used Spring Initializr.

I left most as default and filled in:

  • Group: nl.amis.demo.services
  • Artifiact: books_service
  • Dependencies: Web

If you switch to the full version, the following is shown:

Remark:
Because the things you fill in, are being placed in a maven pom.xml file, I had a look at the Guide to naming conventions on groupId, artifactId, and version.
[https://maven.apache.org/guides/mini/guide-naming-conventions.html]

Then I clicked the “Generate Project” button, which downloaded a books_service.zip to my laptop.

Next, I created a subdirectory named env on my Windows laptop. In this directory I created an applications subdirectory and extracted the zip file in this directory.

Pom.xml

The pom.xml that is created, has the following content:

 
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>nl.amis.demo.services</groupId>
    <artifactId>books_service</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>books_service</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

Because I selected Web as a technology for the project, the following were added as a dependency:

In the pom.xml, I changed the version from:

<version>0.0.1-SNAPSHOT</version>

to

<version>1.0.0-SNAPSHOT</version>

Importing the project into IntelliJ IDEA

Within IntelliJ IDEA, I imported the project and selected directory: C:\My\AMIS\env\applications\books_service. Next, I chose “Import project from external model: Maven” and kept the rest as default.

BooksServiceApplication.java

Spring Initizalizr has generated an application class named BooksServiceApplication.java. It contains a method called main, which is the entry point of the program.

package nl.amis.demo.services.books_service;

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

@SpringBootApplication
public class BooksServiceApplication {

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

}

The BooksService, will be a catalog of books. For now, an in-memory database is used to add, update, delete and retrieve books. Later on, also an external MySQL database is used.

In my application I used a layered architecture. This means that I organized the project structure into to following main categories:

  • Presentation
      Contains all of the classes responsible for presenting the UI to the end-user or sending the response back to the client.
  • Application
      Contains all the logic that is required by the application to meet its functional requirements. The application layer mostly consists of services orchestrating the domain objects.
  • Domain
      Represents the underlying domain, mostly consisting of domain entities and business rules.
  • Infrastructure (also known as the persistence layer)
      Contains all the classes responsible for doing the technical stuff, like persisting the data in the database.

Each of the layers contains objects related to the particular functionality it provides.
[https://dzone.com/articles/layered-architecture-is-good]

Book.java

To model the book representation, I created a resource representation class. Providing a plain old java object with fields, constructors, and accessors.
Therefor I added a new package to ‘nl.amis.demo.services.books_service’ and named it ‘domain’.
Next, I added a new Java class to the domain package and named it Book.java.

package nl.amis.demo.services.books_service.domain;

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class Book {

    @Id
    private String id = "";
    private String title = "";
    private String author = "";
    private String type = "";
    private double price = 0;
    private int numOfPages = 0;
    private String language = "";
    private String isbn13 = "";


    public Book() {
    }

    public Book(String id, String title, String author, String type, double price, int numOfPages, String language, String isbn13) {
        this.id = id;
        this.title = title;
        this.author = author;
        this.type = type;
        this.price = price;
        this.numOfPages = numOfPages;
        this.language = language;
        this.isbn13 = isbn13;
    }

    public String getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public String getAuthor() {
        return author;
    }

    public String getType() {
        return type;
    }

    public double getPrice() { return price; }

    public int getNumOfPages() {
        return numOfPages;
    }

    public String getLanguage() {
        return language;
    }

    public String getIsbn13() {
        return isbn13;
    }
}

The Book class is annotated with @Entity, indicating that it is a JPA entity. For lack of a @Table annotation, it is assumed that this entity will be mapped to a table named Book.
The Book’s id property is annotated with @Id so that JPA will recognize it as the object’s ID.
[https://spring.io/guides/gs/accessing-data-jpa/]

Spring Data JPA focuses on using JPA to store data in a relational database. Its most compelling feature is the ability to create repository implementations automatically, at runtime, from a repository interface.
[https://spring.io/guides/gs/accessing-data-jpa/]

BookRepository.java

By using a Spring Data repository you get a lot of (CRUD)functionality with a minimum of effort.

The goal of the Spring Data repository abstraction is to significantly reduce the amount of boilerplate code required to implement data access layers for various persistence stores.
[https://docs.spring.io/spring-data/jpa/docs/current/reference/html/]

See below a list of the methods that become available when using a CrudRepository.

I added a new package to ‘nl.amis.demo.services.books_service’ and named it ‘infrastructure.persistence’.
Next, I added a new Java class to the persistence package and named it BookRepository.java.

 
package nl.amis.demo.services.books_service.infrastructure.persistence;

import nl.amis.demo.services.books_service.domain.Book;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface BookRepository extends CrudRepository {
}

BookService.java

I added a new package to ‘nl.amis.demo.services.books_service’ and named it ‘application’.
Next, I added a new Java class to the application package and named it BookService.java.
This class is, more or less, a wrapper class, exposing some of the methods of the CrudRepository.

package nl.amis.demo.services.books_service.application;

import nl.amis.demo.services.books_service.domain.Book;
import nl.amis.demo.services.books_service.infrastructure.persistence.BookRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class BookService {

    @Autowired
    private BookRepository bookRepository;

    public List getAllBooks() {
        List books = new ArrayList<>();
        bookRepository.findAll().forEach(books::add);
        return books;
    }

    public Book getBook(String id) {
        return bookRepository.findById(id).orElseGet(Book::new);
    }

    public void addBook(Book whiskey) {
        bookRepository.save(whiskey);
    }

    public void updateBook(String id, Book whiskey) {
        bookRepository.save(whiskey);
    }

    public void deleteBook(String id) {
        bookRepository.deleteById(id);
    }

}

Changes made to pom.xml

Because I used the CrudRepository, I added the following dependency in the pom.xml:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Because I used the H2 in-memory database, I added the following dependency in the pom.xml:

<dependency>
   <groupId>com.h2database</groupId>
   <artifactId>h2</artifactId>
   <scope>runtime</scope>
</dependency>

Because I also want to use the MySQL database, I added the following dependency in the pom.xml:

<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <scope>runtime</scope>
</dependency>

Remark:
The same could be achieved in Spring Initializr, by selecting the following Dependencies:

BookController.java

In Spring’s approach to building RESTful web services, HTTP requests are handled by a controller. These components are easily identified by the @RestController annotation.
I added a new package to ‘nl.amis.demo.services.books_service’ and named it ‘presentation’.
Next, I added a new Java class to the presentation package and named it BookController.java.

package nl.amis.demo.services.books_service.presentation;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookController {

    @GetMapping("/books")
    public List getAllBooks() {
        return bookservice.getAllbooks();
    }

    @GetMapping("/books/{id}")
    public Book getBook(@PathVariable String bookId){
        return bookservice.getBook(bookId);
    }

    @PostMapping("/books")
    public void addBook(@RequestBody Book book) {
        bookservice.addBook(book);
    }

    @PutMapping("/books/{id}")
    public void updateBook(@PathVariable String bookId, @RequestBody Book book) {
        bookservice.updateBook(bookId, book);
    }

    @DeleteMapping("/books/{id}")
    public void deleteBook(@PathVariable String bookId) {
        bookservice.deleteBook(bookId);
    }
}

Id is a so-called path variable and needs to be mapped onto the corresponding method parameter, which is bookId in this case. For the mapping you use the annotation @PathVariable.

The @GetMapping annotation is for mapping HTTP GET requests onto specific handler methods.
Specifically, @GetMapping is a composed annotation that acts as a shortcut for @RequestMapping(method = RequestMethod.GET).
[https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/GetMapping.html]

The @PostMapping annotation is for mapping HTTP POST requests onto specific handler methods.
Specifically, @PostMapping is a composed annotation that acts as a shortcut for @RequestMapping(method = RequestMethod.POST).
[https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/PostMapping.html]

The @PutMapping annotation is for mapping HTTP PUT requests onto specific handler methods.
Specifically, @PutMapping is a composed annotation that acts as a shortcut for @RequestMapping(method = RequestMethod.PUT).
[https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/PutMapping.html]

The @DeleteMapping annotation for mapping HTTP DELETE requests onto specific handler methods.
Specifically, @DeleteMapping is a composed annotation that acts as a shortcut for @RequestMapping(method = RequestMethod.DELETE).
[https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/DeleteMapping.html]

The @RequestBody annotation indicates a method parameter should be bound to the body of the web request. The body of the request is passed through an HttpMessageConverter to resolve the method argument depending on the content type of the request.
[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestBody.html]

HTTP Methods

For your convenience, below I added an overview of the HTTP Methods:

HTTP MethodDescription
GETGET is used to request data from a specified resource
POSTPOST is used to send data to a server to create/update a resource
PUTPUT is used to send data to a server to create/update a resource
HEADHEAD is almost identical to GET, but without the response body
DELETEThe DELETE method deletes the specified resource
PATCHPATCH requests are to make partial update on a resource
OPTIONSThe OPTIONS method describes the communication options for the target resource

The difference between POST and PUT is that PUT requests are idempotent. That is, calling the same PUT request multiple times will always produce the same result. In contrast, calling a POST request repeatedly have side effects of creating the same resource multiple times.
[https://www.w3schools.com/tags/ref_httpmethods.asp]

Running the BooksServiceApplication on port 8080

Within IntelliJ IDEA, I chose the BooksServiceApplication | Run BooksServiceApplication main(). The response from running the application is:


[ main] n.a.d.s.b.BooksServiceApplication : Starting BooksServiceApplication on DESKTOP-E5EV84J with PID 12632 …
[ main] n.a.d.s.b.BooksServiceApplication : No active profile set, falling back to default profiles: default
[ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
[ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 62ms. Found 1 repository interfaces.
[ main] trationDelegate$BeanPostProcessorChecker : Bean ‘org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration’ of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$bddbfc4d] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
[ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
[ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.16]
[ main] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [C:\My\App\Oracle\Java\jdk1.8.0_172\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\My\App\Tools\TortoiseSVN\bin;C:\My\App\Tools\HashiCorp\Vagrant\bin;C:\WINDOWS\System32\OpenSSH\;C:\Users\marc_l\AppData\Local\Microsoft\WindowsApps; C:\My\App\Oracle\Java\jdk1.8.0_172\bin;;.]
[ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
[ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1913 ms
[ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 – Starting…
[ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 – Start completed.
[ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [ name: default …]
[ main] org.hibernate.Version : HHH000412: Hibernate Core {5.3.7.Final}
[ main] org.hibernate.cfg.Environment : HHH000206: hibernate.properties not found
[ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.0.4.Final}
[ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
[ main] o.h.t.schema.internal.SchemaCreatorImpl : HHH000476: Executing import script ‘org.hibernate.tool.schema.internal.exec.ScriptSourceInputNonExistentImpl@93501be’
[ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit ‘default’
[ main] o.s.s.concurrent.ThreadPoolTaskExecutor : Initializing ExecutorService ‘applicationTaskExecutor’
[ main] aWebConfiguration$JpaWebMvcConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
[ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ”
[ main] n.a.d.s.b.BooksServiceApplication : Started BooksServiceApplication in 4.702 seconds (JVM running for 5.196)
[nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet ‘dispatcherServlet’
[nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet ‘dispatcherServlet’
[nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 19 ms
[nio-8080-exec-2] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactory

As you can see, Tomcat is started by default on port 8080 (http).

Example filling

As an example filling for the book catalog, in this article (and the next one), I will be using some of the data provided in the following table:

TitlePublishDateAuthorTypePriceNumOfPagesPublisherLanguageISBN-13
The Threat: How the FBI Protects America in the Age of Terror and TrumpFebruary 19, 2019Andrew G. McCabeHardcover$17.99288St. Martin’s PressEnglish978-1250207579
BecomingNovember 13, 2018Michelle ObamaHardcover$17.88448Crown Publishing Group; First Edition editionEnglish978-1524763138
Five Presidents: My Extraordinary Journey with Eisenhower, Kennedy, Johnson, Nixon, and FordMay 2, 2017Clint Hill, Lisa McCubbinPaperback$11.09464Gallery Books; Reprint editionEnglish978-1476794143
Where the Crawdads SingAugust 14, 2018Delia OwensHardcover$16.20384G.P. Putnam’s Sons; First Edition, First Printing editionEnglish978-0735219090

[https://www.amazon.com/best-sellers-books-Amazon/zgbs/books]

Postman

From Postman I invoked a request named “PostBook1Request” (with method “POST” and URL “http://locahost:8080/books”) and a response with “Status 200 OK” was shown:

Next, from Postman I invoked a request named “PostBook2Request” (with method “POST” and URL “http://locahost:8080/books”) and a response with “Status 200 OK” was shown:

Then I wanted to check if the books were added to the catalog.
From Postman I invoked a request named “GetAllBooksRequest” (with method “GET” and URL “http://locahost:8080/books”) and a response with “Status 200 OK” was shown:

As you can see, both books that I added, were returned from the catalog.

So now let’s make some changes to the application code.

Running the BooksServiceApplication on port 9090

I opened the file application.properties, which already existed in the project folder, and added the following line:

server.port=9090

Within IntelliJ IDEA, I chose the BooksServiceApplication | Run BooksServiceApplication main(). The response from running the application is:


[ main] n.a.d.s.b.BooksServiceApplication : Starting BooksServiceApplication on DESKTOP-E5EV84J with PID 12632 …

[ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9090 (http) with context path ”

[ main] n.a.d.s.b.BooksServiceApplication : Started BooksServiceApplication in 5.687 seconds (JVM running for 6.324)

Run/Debug Configurations

Within IntelliJ IDEA, via Run | Edit Configurations… , you can edit the Run/Debug Configurations.

In the Main class field, specify the class that contains the main() method.
In the VM options field, type optional VM arguments, for example the heap size, garbage collection options, file encoding, etc.
In the Program arguments field, type optional list of arguments that should be passed to the main() method.
In the Environment variables field, create variables and specify their values.
[https://www.jetbrains.com/help/idea/setting-configuration-options.html]

Profiles

In Spring you can have different profiles. I created a development profile and a testing profile. In the development profile I want to quickly test with an H2 in-memory database, while in the testing profile I want to connect to an external MySQL database.
You can set up different application-{profile}.properties files and set the environment variable spring.profiles.active equal to the activated profile. The activated profile will overwrite the properties of the default application.properties file.

I changed the content of application.properties to:

 
logging.level.root=INFO
server.port=9090

Next, I added a new application-development.properties:

logging.level.root=INFO
server.port=9090
nl.amis.environment=development

Next, I added a new application-testing.properties:

logging.level.root=INFO
server.port=9091
nl.amis.environment=testing
spring.datasource.url=jdbc:mysql://localhost:3306/test?allowPublicKeyRetrieval=true&useSSL=false
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.hibernate.ddl-auto=create

Adding logging to BooksServiceApplication.java

In order, to be able to see what VM options, Program arguments and Environment variables are set, I added logging to BooksServiceApplication.java.

package nl.amis.demo.services.books_service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.core.env.Environment;

@SpringBootApplication
public class BooksServiceApplication implements CommandLineRunner {

    private static final Logger logger = LoggerFactory.getLogger(BooksServiceApplication.class);

    @Autowired
    private Environment environment;

    @Override
    public void run(String... args) throws Exception {
        logger.info("\n----Begin logging BooksServiceApplication----");

        logger.info("----System Properties from VM Arguments----");
        logger.info("server.port: " + System.getProperty("server.port"));
        logger.info("----Program Arguments----");
        for (String arg : args) {
            logger.info(arg);
        }

        if (environment != null) {
            getActiveProfiles();
            logger.info("----Environment Properties----");
            logger.info("server.port: " + environment.getProperty("server.port"));
            logger.info("nl.amis.environment: " + environment.getProperty("nl.amis.environment"));
            logger.info("spring.datasource.url: " + environment.getProperty("spring.datasource.url"));
            logger.info("spring.datasource.username: " + environment.getProperty("spring.datasource.username"));
            logger.info("spring.datasource.password: " + environment.getProperty("spring.datasource.password"));
            logger.info("spring.jpa.database-platform: " + environment.getProperty("spring.jpa.database-platform"));
            logger.info("spring.jpa.hibernate.ddl-auto: " + environment.getProperty("spring.jpa.hibernate.ddl-auto"));
        }

        logger.info("----End logging BooksServiceApplication----");
    }

    private void getActiveProfiles() {
        for (final String profileName : environment.getActiveProfiles()) {
            logger.info("Currently active profile - " + profileName);
        }
    }

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

}

Within IntelliJ IDEA, I chose the BooksServiceApplication | Run BooksServiceApplication main(). The response from running the application is:



[ main] n.a.d.s.b.BooksServiceApplication : Started BooksServiceApplication in 4.025 seconds (JVM running for 4.396)
[ main] n.a.d.s.b.BooksServiceApplication : —-Begin logging BooksServiceApplication—-
[ main] n.a.d.s.b.BooksServiceApplication : —-System Properties from VM Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: null
[ main] n.a.d.s.b.BooksServiceApplication : —-Program Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : —-Environment Properties—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: 9090
[ main] n.a.d.s.b.BooksServiceApplication : nl.amis.environment: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.url: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.username: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.password: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.database-platform: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.hibernate.ddl-auto: null
[ main] n.a.d.s.b.BooksServiceApplication : —-End logging BooksServiceApplication—-

To test the logging, next I added some program arguments (space separated).

Within IntelliJ IDEA, I chose the BooksServiceApplication | Run BooksServiceApplication main(). The response from running the application is:



[ main] n.a.d.s.b.BooksServiceApplication : —-Begin logging BooksServiceApplication—-
[ main] n.a.d.s.b.BooksServiceApplication : —-System Properties from VM Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: null
[ main] n.a.d.s.b.BooksServiceApplication : —-Program Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : booksServiceApplication.arg1=123
[ main] n.a.d.s.b.BooksServiceApplication : booksServiceApplication.arg2=456
[ main] n.a.d.s.b.BooksServiceApplication : —-Environment Properties—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: 9090
[ main] n.a.d.s.b.BooksServiceApplication : nl.amis.environment: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.url: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.username: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.password: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.database-platform: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.hibernate.ddl-auto: null
[ main] n.a.d.s.b.BooksServiceApplication : —-End logging BooksServiceApplication—-

To test the logging even further, next I added an environment variable.

Within IntelliJ IDEA, I chose the BooksServiceApplication | Run BooksServiceApplication main(). The response from running the application is:



[ main] n.a.d.s.b.BooksServiceApplication : —-Begin logging BooksServiceApplication—-
[ main] n.a.d.s.b.BooksServiceApplication : —-System Properties from VM Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: null
[ main] n.a.d.s.b.BooksServiceApplication : —-Program Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : booksServiceApplication.arg1=123
[ main] n.a.d.s.b.BooksServiceApplication : booksServiceApplication.arg2=456
[ main] n.a.d.s.b.BooksServiceApplication : —-Environment Properties—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: 7070
[ main] n.a.d.s.b.BooksServiceApplication : nl.amis.environment: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.url: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.username: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.password: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.database-platform: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.hibernate.ddl-auto: null
[ main] n.a.d.s.b.BooksServiceApplication : —-End logging BooksServiceApplication—-

As you can see by using an environment variable, you can also change the port that the BooksServiceApplication is running on.

To test the logging even further, next I added an environment variable to set the profile to developement.

Within IntelliJ IDEA, I chose the BooksServiceApplication | Run BooksServiceApplication main(). The response from running the application is:


[ main] n.a.d.s.b.BooksServiceApplication : —-Begin logging BooksServiceApplication—-
[ main] n.a.d.s.b.BooksServiceApplication : —-System Properties from VM Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: null
[ main] n.a.d.s.b.BooksServiceApplication : —-Program Arguments—-
[ main] n.a.d.s.b.BooksServiceApplication : booksServiceApplication.arg1=123
[ main] n.a.d.s.b.BooksServiceApplication : booksServiceApplication.arg2=456
[ main] n.a.d.s.b.BooksServiceApplication : Currently active profile – development
[ main] n.a.d.s.b.BooksServiceApplication : —-Environment Properties—-
[ main] n.a.d.s.b.BooksServiceApplication : server.port: 9090
[ main] n.a.d.s.b.BooksServiceApplication : nl.amis.environment: development
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.url: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.username: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.datasource.password: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.database-platform: null
[ main] n.a.d.s.b.BooksServiceApplication : spring.jpa.hibernate.ddl-auto: null
[ main] n.a.d.s.b.BooksServiceApplication : —-End logging BooksServiceApplication—-

To test the logging even further, next I added an environment variable to set the profile to testing.

Within IntelliJ IDEA, I chose the BooksServiceApplication | Run BooksServiceApplication main(). The response from running the application is:



[ main] n.a.d.s.b.BooksServiceApplication : Starting BooksServiceApplication on DESKTOP-E5EV84J with PID 15668 (C:\My\AMIS\env\applications\books_service\target\classes started by marc_l in C:\My\AMIS\env\applications\books_service)
[ main] n.a.d.s.b.BooksServiceApplication : The following profiles are active: testing
[ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
[ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 54ms. Found 1 repository interfaces.
[ main] trationDelegate$BeanPostProcessorChecker : Bean ‘org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration’ of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$33daa5db] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
[ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 9091 (http)
[ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
[ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.16]
[ main] o.a.catalina.core.AprLifecycleListener : The APR based Apache Tomcat Native library which allows optimal performance in production environments was not found on the java.library.path: [C:\My\App\Oracle\Java\jdk1.8.0_172\bin;C:\WINDOWS\Sun\Java\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0\;C:\My\App\Tools\TortoiseSVN\bin;C:\My\App\Tools\HashiCorp\Vagrant\bin;C:\WINDOWS\System32\OpenSSH\;C:\Users\marc_l\AppData\Local\Microsoft\WindowsApps; C:\My\App\Oracle\Java\jdk1.8.0_172\bin;;.]
[ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
[ main] o.s.web.context.ContextLoader : Root WebApplicationContext: initialization completed in 1850 ms
[ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 – Starting…
[ main] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 – Exception during pool initialization.

com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.

at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:174) ~[mysql-connector-java-8.0.15.jar:8.0.15]


As expected this results in an error, because the MySQL database is not yet up and running. This will be addressed when we are going to use Minikube (see my next article).

Creating an Executable Jar

From IntelliJ, via Maven Projects | package | Run ‘books_service [package]’, I created an Executable Jar.

This creates a jar file:

…\ applications\books_service\target\books_service-1.0.0-SNAPSHOT.jar

With the following output:


[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] — maven-jar-plugin:3.1.1:jar (default-jar) @ books_service —
[INFO] Building jar: C:\My\AMIS\env\applications\books_service\target\books_service-1.0.0-SNAPSHOT.jar
[INFO]
[INFO] — spring-boot-maven-plugin:2.1.3.RELEASE:repackage (repackage) @ books_service —
[INFO] Replacing main artifact with repackaged archive
[INFO] ————————————————————————
[INFO] BUILD SUCCESS
[INFO] ————————————————————————
[INFO] Total time: 19.188 s
[INFO] Finished at: 2019-02-24T14:52:26+01:00
[INFO] Final Memory: 38M/415M
[INFO] ————————————————————————


Process finished with exit code 0

Creating another version of the BooksServiceApplication

For demo purposes I also wanted another version of the BooksServiceApplication with some code changes.
To keep it simple I only added some extra fields to Book.java.

package nl.amis.demo.services.books_service.domain;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.Date;

@Entity
public class Book {

    @Id
    private String id = "";
    private String title = "";
    private Date publishDate = null;
    private String author = "";
    private String type = "";
    private double price = 0;
    private int numOfPages = 0;
    private String publisher = "";
    private String language = "";
    private String isbn13 = "";


    public Book() {
    }

    public Book(String id, String title, Date publishDate, String author, String type, double price, int numOfPages, String publisher, String language, String isbn13) {
        this.id = id;
        this.title = title;
        this.publishDate = publishDate;
        this.author = author;
        this.type = type;
        this.price = price;
        this.numOfPages = numOfPages;
        this.publisher = publisher;
        this.language = language;
        this.isbn13 = isbn13;
    }

    public String getId() {
        return id;
    }

    public String getTitle() {
        return title;
    }

    public Date getPublishDate() {
        return publishDate;
    }

    public String getAuthor() {
        return author;
    }

    public String getType() {
        return type;
    }

    public double getPrice() {
        return price;
    }

    public int getNumOfPages() {
        return numOfPages;
    }

    public String getPublisher() {
        return publisher;
    }

    public String getLanguage() {
        return language;
    }

    public String getIsbn13() {
        return isbn13;
    }
}

In the pom.xml, I changed the version from:

<version>1.0.0-SNAPSHOT</version>

to

<version>2.0.0-SNAPSHOT</version>

Within IntelliJ IDEA I removed the program arguments and environment variables in the Run/Debug Configurations, so that the default application.properties file was going to be used.

From IntelliJ IDEA, via Maven Projects | package | Run ‘books_service [package]’, I created an Executable Jar.

This creates a jar file:

…\ applications\books_service\target\books_service-2.0.0-SNAPSHOT.jar

With the following output:


[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] — maven-jar-plugin:3.1.1:jar (default-jar) @ books_service —
[INFO] Building jar: C:\My\AMIS\env\applications\books_service\target\books_service-2.0.0-SNAPSHOT.jar
[INFO]
[INFO] — spring-boot-maven-plugin:2.1.3.RELEASE:repackage (repackage) @ books_service —
[INFO] Replacing main artifact with repackaged archive
[INFO] ————————————————————————
[INFO] BUILD SUCCESS
[INFO] ————————————————————————
[INFO] Total time: 8.922 s
[INFO] Finished at: 2019-02-24T15:17:57+01:00
[INFO] Final Memory: 38M/592M
[INFO] ————————————————————————


Process finished with exit code 0

Postman

From Postman I invoked a request named “PostBook1Request” (with method “POST” and URL “http://locahost:9090/books”) and a response with “Status 200 OK” was shown:

Next, from Postman I invoked a request named “PostBook2Request” (with method “POST” and URL “http://locahost:9090/books”) and a response with “Status 200 OK” was shown:

Then I wanted to check if the books were added to the catalog.

From Postman I invoked a request named “GetAllBooksRequest” (with method “GET” and URL “http://locahost:9090/books”) and a response with “Status 200 OK” was shown:

As you can see, both books that I added, were returned from the catalog.

With these final checks I conclude this article.
Version 1.0 and 2.0 of the BooksServiceApplication executable jar are created and ready to be used in Minikube.
But more about that, you can read in my next article.

About Author

Marc, active in IT (and with Oracle) since 1995, is a Principal Oracle SOA Consultant with focus on Oracle Cloud, Oracle Service Bus, Oracle SOA Suite, Oracle Database (SQL & PL/SQL) and Java, Docker, Kubernetes, Minikube and Helm. He's Oracle SOA Suite 12c Certified Implementation Specialist. Over the past 20 years he has worked for several customers in the Netherlands. Marc likes to share his knowledge through publications, blog’s and presentations.

4 Comments

    • Marc Lameriks on

      Hello Rodrigo,

      Please send me an email (at: marc.lameriks@amis.nl) and I will provide you with the source code of the RESTful Web Service Spring Boot application belonging to my article.
      Your currently provided email address is rejected.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.