Create a REST API with Spring Boot

Why Spring Boot?

Spring Boot is a framework that bootstraps a Java web application without the XML configuration headaches that come with a typical Spring application.   The framework adopts an opinionated approach to configuration by making decisions based on the use cases that fit the majority of modern web apps.  In other words, it follows the philosophy of convention over configuration.

With Spring Boot, we can create an application that bundles all dependencies and it’s servlet container so it doesn’t even require a traditional WAR deployment.    We’ll have a fat executable JAR that can be run as a standard Linux service.

SeatingNow

Let’s imagine we’re developing a restaurant reservation application called SeatingNow to compete with OpenTable.   Users can create an account, search for restaurants, book a reservation, and receive notifications via SMS or e-mail.

The application consists of the following microservices:

  • Account
  • Search
  • Restaurant
  • Reservation
  • Search
  • Messaging

Each of these microservices has a REST API and its own MySQL database.  The services will be accessed by desktop and mobile users via an API gateway.

In this article, we’ll focus on building the API for the Restaurant microservice.

REST Endpoints

Our application will expose the following endpoints:

  • GET /v1/reservations - Retrieves all reservations
  • GET /v1/reservations/{id} -Retrieves a specific reservation based on ID
  • POST /v1/reservations - Creates a new reservation
  • PUT /v1/reservations/{id} - Updates an existing reservation
  • DELETE /v1/reservations/{id} - Removes a specific reservation based on ID

Technology Stack

We’ll be using the following technologies for the application:

  • Spring Tool Suite (STS) 3.83
  • Spring Boot 1.5.1
  • Spring Data JPA 1.11
  • Spring MVC 4.3.6
  • MySQL 5.7
  • Maven 3.3.9

Getting Started

There are a few different ways that we can begin development:

We’ll use the Spring Tool Suite for this project.  STS is based on Eclipse and customized for developing Spring applications.  It comes with built-in Tomcat server, validation of config files among other handy features that makes life easier.

Installing Spring Tool Suite

Spring Tool Suite comes packaged as a zip file and does not use an install wizard or store anything in the Windows registry.   So we can just unzip the file and put it anywhere.

On first launch, it will ask us where we want our workspace to live.   This is the directory that holds all of our projects and settings.

Creating the Project

Let’s launch Spring Tool Suite and select File->New -> Spring Starter project

We can configure it as a Maven project and enter the Group, Artifact, and Package as below.  Click Next.

The next screen will ask to select our dependencies.  These selections will get populated into Maven’s pom.xml file.

For our project, we want the following dependencies:

  • Core -> Dev Tools
    • This dependency makes development easier for us by adding some neat features like automatic Tomcat restarts after each file change.  Not required but useful to have.
  • SQL -> JPA, MySQL
    • These dependencies will allow our app to communicate with a MySQL/MariaDB database.   JPA is the Java Persistence framework and will do the heavy lifting on the querying side so we don’t have to write boilerplate SQL statements for routine operations.
  • Web -> Web
    • This dependency allows us to create a REST API based on Spring MVC

 

Click Finish to create the project.

Maven pom.xml

Our project has now been created so let’s look at the Maven configuration file.  Double-click on the file in Package Explorer and go to the tab named „pom.xml“ in the main window pane.

 

Parent project

We’ve got a parent project named spring-boot-start-parent

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>1.5.1.RELEASE</version>
</parent>

This configuration block declares that our project is a child of the parent project and thus inherits a host of default Maven configurations.

Dependencies

Now let’s check out the dependencies section:

<dependencies> 
  <dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-data-jpa</artifactId> 
  </dependency> 
  
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  
  <dependency> 
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <scope>runtime</scope>
  </dependency>
  
  <dependency> 
    <groupId>mysql</groupId> 
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope> 
  </dependency> 
  
  <dependency> 
    <groupId>org.springframework.boot</groupId>     
    <artifactId>spring-boot-starter-test</artifactId> 
    <scope>test</scope> 
  </dependency>
</dependencies>

These artifacts (and their own dependencies) have all been downloaded automatically by Maven and the JARs can be viewed by expanding the Maven dependencies under Package Explorer:

Build

In order to make our final JAR executable, we’ll need to add an element named executable into this section:

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

Application Class

The Application class containing the main method is created for us automatically by the Project wizard.

package com.codebyamir.springboot;

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

@SpringBootApplication
public class ReservationApp {

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

}

Notice that the class is annotated with @SpringBootApplication.  This annotation is shorthand for three annotations:  @Configuration, @EnableAutoConfiguration and @ComponentScan.

What does this do for us?

  • @Configuration tags the class as a source of bean definitions for the application context.
  • @EnableAutoConfiguration tells Spring Boot to start adding beans based on class path settings, other beans, and various property settings.
  • @ComponentScan tells Spring to look for other components, configurations, and services in the the com.codebyamir.springboot package.

Notice that there was no XML configuration required.

Our main method delegates to Spring Boot’s SpringApplication class by calling run.  SpringApplication will bootstrap our application, starting Spring which will in turn start the embedded Tomcat server.

We need to pass SeatingNow.class as an argument so SpringApplication knows the primary component.

The static run method performs a number of important setup tasks:

  • Sets the default configuration
  • Starts Spring application context
  • Performs classpath scan
  • Starts Tomcat server

If we run the project as a Java application (Right click project -> Run As -> Java Application), we see a bunch of output in the console with some fancy ASCII art:

 .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.1.RELEASE)
2017-03-11 08:29:25.096  INFO 5484 --- [           main] com.codebyamir.springboot.SeatingNow   : Starting ReservationApp on SURFACEBOOK with PID 5484 (C:\Users\amirb\Documents\workspace-sts-3.8.3.RELEASE\seatingnow-api\target\classes started by amirb in C:\Users\amirb\Documents\workspace-sts-3.8.3.RELEASE\seatingnow-api)
2017-03-11 08:29:25.100  INFO 5484 --- [           main] com.codebyamir.springboot.SeatingNow   : No active profile set, falling back to default profiles: default
2017-03-11 08:29:25.188  INFO 5484 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5427c60c: startup date [Tue Feb 21 22:29:25 EST 2017]; root of context hierarchy
2017-03-11 08:29:26.478  INFO 5484 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration' of type [class org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-03-11 08:29:26.562  INFO 5484 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'validator' of type [class org.springframework.validation.beanvalidation.LocalValidatorFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-03-11 08:29:26.958  INFO 5484 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-03-11 08:29:26.978  INFO 5484 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2017-03-11 08:29:26.980  INFO 5484 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.11
2017-03-11 08:29:27.152  INFO 5484 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-03-11 08:29:27.153  INFO 5484 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1969 ms
2017-03-11 08:29:27.381  INFO 5484 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2017-03-11 08:29:27.386  INFO 5484 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-03-11 08:29:27.387  INFO 5484 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-03-11 08:29:27.387  INFO 5484 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-03-11 08:29:27.387  INFO 5484 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2017-03-11 08:29:27.845  INFO 5484 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5427c60c: startup date [Tue Feb 21 22:29:25 EST 2017]; root of context hierarchy
2017-03-11 08:29:27.950  INFO 5484 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.map<java.lang.string, java.lang.object="">> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-03-11 08:29:27.951  INFO 5484 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-03-11 08:29:27.982  INFO 5484 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-03-11 08:29:27.982  INFO 5484 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-03-11 08:29:28.067  INFO 5484 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-03-11 08:29:28.215  INFO 5484 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-03-11 08:29:28.275  INFO 5484 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-03-11 08:29:28.280  INFO 5484 --- [           main] com.codebyamir.springboot.ReservationApp   : Started ReservationApp in 3.576 seconds (JVM running for 3.95)</java.util.map<java.lang.string,>

We’ve started Tomcat server on localhost with its default port 8080.   Let’s check it out in our browser:

We get an error page from the Tomcat server.

What’s happening here?  There is no mapping for „/“ so it tries to go to „/error“ but there’s no mapping for that either so it returns a 404.

This is normal so no worries.  The server is running, but we haven’t defined any API endpoints yet.

Set server.error.whitelabel.enabled=false in application.properties to switch the whitelabel error page off and restore the default error page from Tomcat.

Populate the Database

One of the tenets of microservice architecture is one database per service.  So let’s create and initialize the database with some sample data:

CREATE DATABASE seatingnow_reservation;

USE seatingnow_reservation;

CREATE TABLE reservation (
`id` int(1) NOT NULL AUTO_INCREMENT,
`user_id` int(1) NOT NULL,
`party_size` tinyint(1) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`restaurant_id` int(1) NOT NULL, 
PRIMARY KEY (`id`)
) ENGINE=InnoDB CHARSET=utf8;

INSERT INTO reservation VALUES 
(NULL,100,2,NOW(),800), 
(NULL,101,3,NOW() + INTERVAL 1 DAY,800), 
(NULL,102,5,NOW() + INTERVAL 2 DAY,800)

Here we are creating 3 reservations that correspond to users 100, 101, and 102.

  • Amir (user_id=100) has a reservation for 2 at restaurant XYZ (restaurant_id=800) on 3-11-2017 at 6pm
  • Beth (user_id=101) has a reservation for 3 at restaurant XYZ (restaurant_id=800) on 3-12-2017 at 6pm
  • Joe (user_id=102) has a reservation for 5 at restaurant XYZ (restaurant_id=800) on 3-13-2017 at 6pm

The columns user_id and restaurant_id would typically be foreign key references to other table columns named user.id and restaurant.id but we are simplifying things here a bit.

Entity Class

In order to model a reservation, we need to create a class called Reservation which is annotated with @Entity marking it as a JPA entity.

We’ll store the reservation id, name of the person who made the reservation, the reservation date/time, and the size of the party.

Since our class name is in title case (Reservation) and our table name is in lower case (reservation), we’ll need to let Spring know about this by using @Table(name=“reservation“) before our class declaration.

We tell Spring the private key of our table by using the @Id annotation.   The @Columnannotations are for other columns we want to use.   If any of the database column names differ from our instance variables, then we must explicitly specify the database column name as part of the annotation – @Column(name=“user_id“) 

light_bulb (2).png

Avoid camelcase column names in your database because Hibernate converts these into snake case. 

package com.codebyamir.springboot.reservation;

import java.time.LocalDateTime;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

// Model class 

@Entity
@Table(name="reservation")
public class Reservation {
	
	@Id
	private Long id;
	
	@Column
	private LocalDateTime dt;

	
	@Column(name="user_id")
	private Long userId;
	
	@Column(name="restaurant_id")
	private Long restaurantId;
	
	public Long getRestaurantId() {
		return restaurantId;
	}

	public void setRestaurantId(Long restaurantId) {
		this.restaurantId = restaurantId;
	}

	// Hibernate will convert camel case column names to snake case!!!
	// Don't use camelcase columns in DB
	@Column(name="party_size")
	private int partySize;
	
	
	
	public Reservation() {}
	
	public Reservation(Long id,  Long userId, int partySize) {
		this.id = id;
	
		this.userId = userId;
		this.partySize = partySize;
	}
		
	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public LocalDateTime getDt() {
		return dt;
	}

	public void setDt(LocalDateTime dt) {
		this.dt = dt;
	}

	public Long getUserId() {
		return userId;
	}

	public void setUserId(Long userId) {
		this.userId = userId;
	}

	public int getPartySize() {
		return partySize;
	}

	public void setPartySize(int partySize) {
		this.partySize = partySize;
	}

}

Repository Interface

In a typical Java application, we would expect to write a class that implements ReservationRepository.  But since we’re using Spring Data JPA, we don’t have to worry about that.   JPA will create an implementation on the fly during run-time.

All we have to do is extend the CrudRepository interface like below:

package com.codebyamir.springboot.reservation;

import org.springframework.data.repository.CrudRepository;

public interface ReservationRepository extends CrudRepository<Reservation,String> {

}

We specify the generic parameters for the entity and ID that we are working with (Reservation and String).   We will inherit several methods for saving, deleting, and finding Reservation entities.

We may also define our own methods here if we wish like findByDate() and findByLastName().   For this example, we do not need to define any additional methods.

Service Class

We now create our service class named ReservationService and annotate it with @Service.

Service classes contain the business logic and call methods in the repository layer.

package com.codebyamir.springboot.reservation;

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

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

@Service	
public class ReservationService {

	@Autowired
	private ReservationRepository reservationRepository;
	
	// Retrieve all rows from table and populate list with objects
	public List getAllReservations() {
		
		List reservations = new ArrayList<>();
		reservationRepository.findAll().forEach(reservations::add);
		
		return reservations;
	}
	
	// Retrieves one row from table based on given id
	public Reservation getReservation(Long id) {
		return reservationRepository.findOne(id);
	
	}
	
	// Inserts row into table 
	public void addReservation(Reservation reservation) {
		reservationRepository.save(reservation);
	}
	
	// Updates row in table
	public void updateReservation(Long id, Reservation reservation) {
		reservationRepository.save(reservation);
	}
	
	// Removes row from table
	public void deleteReservation(Long id) {
		reservationRepository.delete(id);
	}
}

Controller Class

In order to handle HTTP requests, we must add a controller.   This will be a Java class named ReservationController with a @RestController annotation applied to it.

Inside this class, we define methods that handle the actions for each URI by using the @RequestMapping annotation.   The controller class creates an instance of class ReservationService to perform its work thanks to the @Autowired annotation.

package com.codebyamir.springboot.reservation;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/v1")
public class ReservationController {
	
	@Autowired
	private ReservationService reservationService;
	
	// ------------ Retrieve all reservations ------------
	@RequestMapping(value = "/reservations", method = RequestMethod.GET)
	public List getAllReservations() {
		
		return reservationService.getAllReservations();
		
	}
	
	// ------------ Retrieve a reservation ------------
	@RequestMapping(value = "/reservations/{id}", method = RequestMethod.GET)
	public Reservation getReservation(@PathVariable String id) {
		return reservationService.getReservation(id);
	}
	
	// ------------ Create a reservation ------------
	@RequestMapping(value = "/reservations", method = RequestMethod.POST)
	public void addReservation(@RequestBody Reservation reservation) {
		reservationService.addReservation(reservation);
		
	}
	
	// ------------ Update a reservation ------------
	@RequestMapping(value = "/reservations/{id}", method = RequestMethod.PUT)
	public void updateReservation(@RequestBody Reservation reservation,@PathVariable String id) {
		reservationService.updateReservation(id, reservation);
	}
	
	// ------------ Delete a reservation ------------
	@RequestMapping(value = "/reservations/{id}", method = RequestMethod.DELETE)
	public void deleteReservation(@PathVariable String id) {
		reservationService.deleteReservation(id);
	}
}

JPA Converter Class

JPA does not know how to serialize LocalDateTime objects which poses a problem for us since our entity class has one.   The solution to this problem is to create a helper class that instructs JPA on conversion:

package com.codebyamir.springboot.reservation;

import java.sql.Timestamp;
import java.time.LocalDateTime;

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class LocalDateTimeAttributeConverter implements AttributeConverter {
	
    @Override
    public Timestamp convertToDatabaseColumn(LocalDateTime locDateTime) {
    	return (locDateTime == null ? null : Timestamp.valueOf(locDateTime));
    }

    @Override
    public LocalDateTime convertToEntityAttribute(Timestamp sqlTimestamp) {
    	return (sqlTimestamp == null ? null : sqlTimestamp.toLocalDateTime());
    }
}

Configuration with application.properties

In this project, we will use an application.properties file in /src/main/resources.   Spring Boot reads this file on startup to determine how to connect to our database.

server.address=127.0.0.1
spring.datasource.url=jdbc:mysql://localhost:3306/seatingnow?autoReconnect=true&useSSL=false
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

We could have also used O/S environment variables or an external application.properties file to specify these values.   All the configuration options can be found here.

Start the Application

Let’s restart the application and see how it looks.   The console output will tell us if there are any issues connecting to the database.

Our output below looks normal so let’s move forward and test our API.

09:39:45.496 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
09:39:45.500 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-starter/target/classes/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot/target/classes/, /spring-boot-actuator/target/classes/, /spring-boot-devtools/target/classes/]
09:39:45.500 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/C:/Users/amirb/Documents/workspace-sts-3.8.3.RELEASE/course-api/target/classes/]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.1.RELEASE)

2017-03-11 09:39:45.936  INFO 24732 --- [  restartedMain] c.codebyamir.springboot.ReservationApp   : Starting ReservationApp on SURFACEBOOK with PID 24732 (C:\Users\amirb\Documents\workspace-sts-3.8.3.RELEASE\course-api\target\classes started by amirb in C:\Users\amirb\Documents\workspace-sts-3.8.3.RELEASE\course-api)
2017-03-11 09:39:45.937  INFO 24732 --- [  restartedMain] c.codebyamir.springboot.ReservationApp   : No active profile set, falling back to default profiles: default
2017-03-11 09:39:46.013  INFO 24732 --- [  restartedMain] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5c6c80b1: startup date [Sat Mar 11 09:39:46 EST 2017]; root of context hierarchy
2017-03-11 09:39:48.173  INFO 24732 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration' of type [class org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-03-11 09:39:48.261  INFO 24732 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'validator' of type [class org.springframework.validation.beanvalidation.LocalValidatorFactoryBean] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-03-11 09:39:48.342  INFO 24732 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$925fe313] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2017-03-11 09:39:49.127  INFO 24732 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2017-03-11 09:39:49.147  INFO 24732 --- [  restartedMain] o.apache.catalina.core.StandardService   : Starting service Tomcat
2017-03-11 09:39:49.149  INFO 24732 --- [  restartedMain] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.5.11
2017-03-11 09:39:49.290  INFO 24732 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2017-03-11 09:39:49.291  INFO 24732 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 3283 ms
2017-03-11 09:39:49.470  INFO 24732 --- [ost-startStop-1] o.s.b.w.servlet.ServletRegistrationBean  : Mapping servlet: 'dispatcherServlet' to [/]
2017-03-11 09:39:49.475  INFO 24732 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'characterEncodingFilter' to: [/*]
2017-03-11 09:39:49.475  INFO 24732 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2017-03-11 09:39:49.475  INFO 24732 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'httpPutFormContentFilter' to: [/*]
2017-03-11 09:39:49.475  INFO 24732 --- [ost-startStop-1] o.s.b.w.servlet.FilterRegistrationBean   : Mapping filter: 'requestContextFilter' to: [/*]
2017-03-11 09:39:50.223  INFO 24732 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2017-03-11 09:39:50.242  INFO 24732 --- [  restartedMain] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
	name: default
	...]
2017-03-11 09:39:50.337  INFO 24732 --- [  restartedMain] org.hibernate.Version                    : HHH000412: Hibernate Core {5.0.11.Final}
2017-03-11 09:39:50.339  INFO 24732 --- [  restartedMain] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2017-03-11 09:39:50.341  INFO 24732 --- [  restartedMain] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2017-03-11 09:39:50.395  INFO 24732 --- [  restartedMain] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
2017-03-11 09:39:50.536  INFO 24732 --- [  restartedMain] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
2017-03-11 09:39:51.442  INFO 24732 --- [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2017-03-11 09:39:52.580  INFO 24732 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5c6c80b1: startup date [Sat Mar 11 09:39:46 EST 2017]; root of context hierarchy
2017-03-11 09:39:52.794  INFO 24732 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/v1/reservations/{id}],methods=[GET]}" onto public com.codebyamir.springboot.reservation.Reservation com.codebyamir.springboot.reservation.ReservationController.getReservation(java.lang.Long)
2017-03-11 09:39:52.795  INFO 24732 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/v1/reservations],methods=[POST]}" onto public void com.codebyamir.springboot.reservation.ReservationController.addReservation(com.codebyamir.springboot.reservation.Reservation)
2017-03-11 09:39:52.796  INFO 24732 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/v1/reservations/{id}],methods=[DELETE]}" onto public void com.codebyamir.springboot.reservation.ReservationController.deleteReservation(java.lang.Long)
2017-03-11 09:39:52.796  INFO 24732 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/v1/reservations/{id}],methods=[PUT]}" onto public void com.codebyamir.springboot.reservation.ReservationController.updateReservation(com.codebyamir.springboot.reservation.Reservation,java.lang.Long)
2017-03-11 09:39:52.797  INFO 24732 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/v1/reservations],methods=[GET]}" onto public java.util.List com.codebyamir.springboot.reservation.ReservationController.getAllReservations()
2017-03-11 09:39:52.802  INFO 24732 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-03-11 09:39:52.802  INFO 24732 --- [  restartedMain] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-03-11 09:39:52.852  INFO 24732 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-03-11 09:39:52.853  INFO 24732 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-03-11 09:39:52.946  INFO 24732 --- [  restartedMain] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2017-03-11 09:39:53.447  INFO 24732 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-03-11 09:39:53.547  INFO 24732 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-03-11 09:39:53.704  INFO 24732 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-03-11 09:39:53.726  INFO 24732 --- [  restartedMain] c.codebyamir.springboot.ReservationApp   : Started ReservationApp in 8.205 seconds (JVM running for 8.823)

Test the API

We have a few options for testing the API.  Some popular utilities are curl (command-line) and Postman (GUI).  We can also use the browser for any GET requests.

Retrieve all reservations [GET /v1/reservations]

curl -X GET -H "Content-Type: application/json" http://localhost:8080/v1/reservations

[
  {
    "id": 1,
    "dt": {
      "dayOfMonth": 12,
      "dayOfWeek": "SUNDAY",
      "dayOfYear": 71,
      "month": "MARCH",
      "monthValue": 3,
      "year": 2017,
      "hour": 21,
      "minute": 1,
      "nano": 0,
      "second": 11,
      "chronology": {
        "id": "ISO",
        "calendarType": "iso8601"
      }
    },
    "userId": 100,
    "restaurantId": 800,
    "partySize": 2
  },
  {
    "id": 2,
    "dt": {
      "dayOfMonth": 13,
      "dayOfWeek": "MONDAY",
      "dayOfYear": 72,
      "month": "MARCH",
      "monthValue": 3,
      "year": 2017,
      "hour": 21,
      "minute": 1,
      "nano": 0,
      "second": 11,
      "chronology": {
        "id": "ISO",
        "calendarType": "iso8601"
      }
    },
    "userId": 101,
    "restaurantId": 800,
    "partySize": 3
  },
  {
    "id": 3,
    "dt": {
      "dayOfMonth": 14,
      "dayOfWeek": "TUESDAY",
      "dayOfYear": 73,
      "month": "MARCH",
      "monthValue": 3,
      "year": 2017,
      "hour": 21,
      "minute": 1,
      "nano": 0,
      "second": 11,
      "chronology": {
        "id": "ISO",
        "calendarType": "iso8601"
      }
    },
    "userId": 102,
    "restaurantId": 800,
    "partySize": 5
  }
]

 

Delete a reservation [DELETE /v1/reservations/2]

curl -X DELETE -H "Content-Type: application/json" -H "Cache-Control: no-cache""http://localhost:8080/v1/reservations/2"

 

Run the Executable JAR as a Linux Service

The executable jar is named reservation-microservice-0.0.1-SNAPSHOT.jar.

We can run it as a Linux System V init service like so:

# ln -s /home/ec2-user/reservation-microservice-0.0.1-SNAPSHOT.jar /etc/init.d/springApp
# /etc/init.d/springApp
Usage: /etc/init.d/springApp {start|stop|force-stop|restart|force-reload|status|run}

The script supports the standard service startstoprestart and status commands.

  • Writes a PID file in /var/run/springApp/springApp.pid
  • Writes console logs to /var/log/springApp.log
<!–  https://www.codebyamir.com/blog/create-rest-api-with-spring-boot –>

Comments

comments