Before You Begin
Purpose
This tutorial shows you how to develop and package a REST service using Spring Data REST, Java Persistence API (JPA) and Apache Tomcat embedded so that it can be deployed on Oracle Application Container Cloud Service.
Time to Complete
60 minutes
Background
Writing a REST-based micro service that accesses a data model typically involves writing the Data Access Objects (DAO) to access the data repository and writing the REST methods independently. This often means that you are responsible for writing all of the SQL queries or JPQL. The Spring Data REST framework provides you with the ability to create data repository implementations automatically at runtime, from a data repository interface, and expose these data repositories over HTTP as a REST service.
Spring Data REST takes the features of Spring HATEOAS and Spring Data JPA and combines them automatically.
- Spring Hypermedia as the Engine of Application State (HATEOAS) provides some APIs to help create REST representations that follow the HATEOAS principle when working with Spring and especially Spring MVC.
- Spring Data JPA makes it easy to implement a data access layer of an application because it reduces the amount of boilerplate code required to implement data access layers.
The purpose of the data access layer is to access
the database and retrieve/persist the entities and
the main implementation class is the repository,
which is basically a DAO. In a classic Spring
application this would be implemented with the @Repository
annotation. However, Spring Data JPA provides a
simpler approach to implement repositories by using
interfaces only and a naming convention for
querying. The framework supports custom queries for
queries that aren't obvious, and also support
extending the repositories with custom code.
The Spring Data JPA module implements the Spring Data Commons repository abstraction that provides two basic interfaces:
- The
CrudRepository
interface provides methods for CRUD operations. It enables you to create, read, update, and delete records without defining your own methods. TheCrudRepository
interface extends fromRepository
interface which is a empty interface that can accept a domain class and a serialable primary key as type parameters. - The
PagingAndSortingRepository
interface extends fromCrudRepository
interface and provides additional methods to retrieve entities using pagination and sorting.
Additionally, Spring Data JPA provides one more
repository interface: theJpaRepository
interface extends from the PagingAndSortingRepository
interface and add more functionality that is
specific to JPA, such as flushing the persistence
context and deleting records in a batch .
The JpaRepository
interface ties your
repositories to the JPA persistence technology. If
you do not need to use the extra methods provided by
it, then it is recommended that you use the CrudRepository
interface or thePagingAndSortingRepository
interface depending on whether or not you need
sorting and paging.
Scenario
You will build an application that lets you create and retrieve Book objects stored in a memory-based database using Spring Data REST.
Context
For this tutorial, you use Maven to compile, resolve dependencies, and package the application. To persist data you use an embedded in-memory database (H2) .
Embedded databases are useful during the development phase of a project because of their lightweight nature. Embedded database are practical for a simple tutorial like this one because you don't need to configure a database or a datasource in order to test your application. Spring supports H2 and other embedded databases.
What Do You Need?
- Java Development Kit 8 (JDK 8)
- Maven 3.0+
- cURL 7.0+ (cURL is installed by default on most UNIX and Linux distributions. To install cURL on a Windows 64-bit machine, click here).
- A text editor or integrated development environment IDE.
- book-service.zip This is the Maven project with source code. Download it if you want to view the completed project.
Creating a Maven Project
In this section, you create a basic Maven project from an archetype.
The first time you execute a Maven command, Maven will need to download all the plugins and related dependencies to fulfill the command. From a clean installation of Maven, this can take quite a while depending on your internet connection. If you create other project, Maven will now have what it needs, so it won't need to download anything new and will be able to execute the command more quickly.
-
Open a command-line window or a terminal in Linux.
-
Go to the directory where you want to store the project..
-
Generate the project:
mvn archetype:generate -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeGroupId=org.apache.maven.archetypes -DinteractiveMode=false -DgroupId=com.example.spring.rest
-Dpackage=
-DartifactId=book-service -DarchetypeVersion=1.1com.example.spring.rest
Note: You can change the groupId
,
package
, and artifactId
of this command for your particular project.
This command creates a standard Maven structure.
After it's finished, the book-service
project directory is created in your current
location.
Note: You can open the project in Netbeans (from the File menu, select Open Project) or in Eclipse (from the File menu, select Import, then Maven, and then Existing Maven Project), but you must configure Maven in your IDE.
Configuring the pom.xml File
In this section, you configure the maven script (pom.xml
)
to resolve dependencies, compile, and package your
web service.
-
Open the
pom.xml
file located in the project root directory and set the parent pom:<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.3.RELEASE</version> </parent>
-
Add the following dependencies in the
<dependencies>
tags.<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-rest</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> </dependency>
Spring Boot makes it easy to create standalone Spring applications
-
Configure the plugin:
spring-boot-maven-plugin
.<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
The Spring Boot Maven plugin lets you package an application in a standalone JAR file, with a full Tomcat server embedded.
The
main
class that you want to launch can either be specified using a configuration option, or by adding aMain-Class
attribute to the manifest in the usual way. If you don’t specify a main class then the plugin will search for a class with apublic static void main(String[] args)
method. In this case, you must have only one class with amain
method. -
Add the Spring repositories:
<repositories> <repository> <id>spring-releases</id> <url>https://repo.spring.io/libs-release</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-releases</id> <url>https://repo.spring.io/libs-release</url> </pluginRepository> </pluginRepositories>
Creating a Domain Object and a Repository
In this section, you create a domain object to represent a book and a repository for this entity.
-
Create the class
Book
in thecom.example.spring.rest
package. -
Copy and paste the following code:
/* Copyright © 2015 Oracle and/or its affiliates. All rights reserved. */ package com.example.spring.rest; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; @Entity public class Book { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String title; private String author; private String isbn; private int published; private String genre; public void setId(long id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; } public String getIsbn() { return isbn; } public void setIsbn(String isbn) { this.isbn = isbn; } public int getPublished() { return published; } public void setPublished(int published) { this.published = published; } public String getGenre() { return genre; } public void setGenre(String genre) { this.genre = genre; } }
The
Book
class is a simple POJO with fields, setters, and getters that represents the Book table in the database.-
@Entity
declares the class as a persistent POJO class. -
@Id
declares the identifier property of this entity. -
@GeneratedValue
specifies that a value will be automatically generated for theid
field.
-
-
Create the interface
BookRepository.java
in thecom.example.spring.rest
package. -
Copy and paste the following code:
/* Copyright © 2015 Oracle and/or its affiliates. All rights reserved. */ package com.example.spring.rest; import java.util.List; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; import org.springframework.data.rest.core.annotation.RestResource; public interface BookRepository extends PagingAndSortingRepository<Book, Long> { public Book findByIsbn(@Param("isbn") String isbn); @RestResource(path="getBooksByAuthor") public List<Book> findByAuthorIgnoreCaseOrderByTitleAsc(@Param("author") String author); @Query("SELECT b FROM Book b WHERE b.published BETWEEN :startYear AND :endYear ORDER BY b.published") public List<Book> getBooksBetweenYears(@Param("startYear")int startYear, @Param("endYear")int endYear); public List<Book> findByTitleContaining(@Param("title") String title); public int countByAuthor(@Param("author") String author); }
At runtime, Spring Data REST creates an implementation of the
BookRepository
interface automatically. It inspects all query methods defined in it and derives a query for each of them. By default, Spring Data JPA automatically parses the method name and creates a query from it. The query is implemented using the JPA criteria API. For example thefindByIsbn(…)
method is logically equivalent to the JPQL query:select b from Book b where b.isbn = :isbn
. The parser that analyzes the method name supports a large set of keywords such asAnd
,Or
,GreaterThan
,LessThan
,Like
,IsNull
,Not
and so on. You can also addOrderBy
clauses. Spring Data JPA also lets you manually define the query to be executed by a repository method using the@Query
annotation.Spring Data REST exposes a collection resource named after the uncapitalized, plural version of the domain class (for example, Book = /books), that the exported repository is handling. Both the name of the resource and the path can be customized using the
@RepositoryRestResource
annotation on the repository interface.
Running the Web Service
Making the Application Executable
You can package this service as a traditional WAR
file for deployment to an external application
server; however, for this tutorial, you will create
a standalone application. You package all
dependencies in a single, executable JAR file,
driven by a Java main()
method. You
use Spring’s support for embedding the Tomcat
servlet container as the HTTP runtime, instead of
deploying to an external instance.
- Open the
App
class. - Annotate the class with the
@SpringBootApplication
annotation. - Add the line
SpringApplication.run(App.class);
into themain
method. -
Add the missing imports:
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
-
Review the code. Your code should look like the following:
/* Copyright © 2015 Oracle and/or its affiliates. All rights reserved. */ package com.example.spring.rest; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class App { public static void main( String[] args ){ SpringApplication.run(App.class); } }
Building an Executable JAR
- Open a command-line (Windows) or a terminal window (Linux).
- Go to the root directory of the project (
book-service
), where thepom.xml
file is located. - Clean and compile the application:
mvn clean compile
- Package the application:
mvn package
- Look in the
target
directory. You should see a file with the following or with a similar name:book-service-1.0-SNAPSHOT.jar
- Change to the
target
directory. - Execute the JAR file:
java -jar book-service-1.0-SNAPSHOT.jar
You can also run the application using the mvn
spring-boot:run
command.
c:\examples\book-service\target>java -jar book-service-1.0-SNAPSHOT.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.2.3.RELEASE)
2015-07-30 15:21:06.881 INFO 5704 --- [ main] com.example.spring.rest.App : Starting App v1.0
-SNApplication Container CloudHOT on LUPERALT-LAP with PID 5704 (C:\examples\book-service\target\book-service-1.0-SNApplication Container CloudHOT.jar started by lup
eralt in c:\examples\book-service\target)
2015-07-30 15:21:07.012 INFO 5704 --- [ main] ationConfigEmbeddedWebApplicationContext : Refreshing org.sp
ringframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5aca1991: startup date [Thu Jul 30
15:21:07 CDT 2015]; root of context hierarchy
2015-07-30 15:21:08.556 INFO 5704 --- [ main] o.s.b.f.s.DefaultListableBeanFactory : Overriding bean d
efinition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false
; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.
autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver;
initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconf
igure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; a
bstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanN
ame=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodN
ame=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/spri
ngframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2015-07-30 15:21:10.478 INFO 5704 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springf
ramework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transacti
on.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$b155264e] is not eligible for getting p
rocessed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-07-30 15:21:10.552 INFO 5704 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'transaction
AttributeSource' of type [class org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] is n
ot eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-07-30 15:21:10.588 INFO 5704 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'transaction
Interceptor' of type [class org.springframework.transaction.interceptor.TransactionInterceptor] is not eligible for g
etting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-07-30 15:21:10.605 INFO 5704 --- [ main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springf
ramework.transaction.config.internalTransactionAdvisor' of type [class org.springframework.transaction.interceptor.Be
anFactoryTransactionAttributeSourceAdvisor] is not eligible for getting processed by all BeanPostProcessors (for exam
ple: not eligible for auto-proxying)
2015-07-30 15:21:11.865 INFO 5704 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialize
d with port(s): 8080 (http)
2015-07-30 15:21:12.222 INFO 5704 --- [ main] o.apache.catalina.core.StandardService : Starting service
Tomcat
2015-07-30 15:21:12.227 INFO 5704 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet
Engine: Apache Tomcat/8.0.20
2015-07-30 15:21:12.445 INFO 5704 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spri
ng embedded WebApplicationContext
2015-07-30 15:21:12.446 INFO 5704 --- [ost-startStop-1] o.s.web.context.ContextLoader : Root WebApplicati
onContext: initialization completed in 5443 ms
2015-07-30 15:21:14.875 INFO 5704 --- [ost-startStop-1] j.LocalContainerEntityManagerFactoryBean : Building JPA cont
ainer EntityManagerFactory for persistence unit 'default'
2015-07-30 15:21:14.912 INFO 5704 --- [ost-startStop-1] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Proces
sing PersistenceUnitInfo [
name: default
...]
2015-07-30 15:21:15.224 INFO 5704 --- [ost-startStop-1] org.hibernate.Version : HHH000412: Hibern
ate Core {4.3.8.Final}
2015-07-30 15:21:15.232 INFO 5704 --- [ost-startStop-1] org.hibernate.cfg.Environment : HHH000206: hibern
ate.properties not found
2015-07-30 15:21:15.235 INFO 5704 --- [ost-startStop-1] org.hibernate.cfg.Environment : HHH000021: Byteco
de provider name : javassist
2015-07-30 15:21:16.771 INFO 5704 --- [ost-startStop-1] o.hibernate.annotations.common.Version : HCANN000001: Hibe
rnate Commons Annotations {4.0.5.Final}
2015-07-30 15:21:16.935 INFO 5704 --- [ost-startStop-1] org.hibernate.dialect.Dialect : HHH000400: Using
dialect: org.hibernate.dialect.H2Dialect
2015-07-30 15:21:17.132 INFO 5704 --- [ost-startStop-1] o.h.h.i.ast.ASTQueryTranslatorFactory : HHH000397: Using
ASTQueryTranslatorFactory
2015-07-30 15:21:17.674 INFO 5704 --- [ost-startStop-1] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Runnin
g hbm2ddl schema export
2015-07-30 15:21:17.698 INFO 5704 --- [ost-startStop-1] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema
export complete
2015-07-30 15:21:19.044 INFO 5704 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean : Mapping servlet:
'dispatcherServlet' to [/]
2015-07-30 15:21:19.054 INFO 5704 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: '
characterEncodingFilter' to: [/*]
2015-07-30 15:21:19.056 INFO 5704 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean : Mapping filter: '
hiddenHttpMethodFilter' to: [/*]
2015-07-30 15:21:19.226 INFO 5704 --- [ main] o.s.b.f.config.PropertiesFactoryBean : Loading propertie
s file from class path resource [rest-default-messages.properties]
2015-07-30 15:21:19.715 INFO 5704 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @Cont
rollerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5aca1991: start
up date [Thu Jul 30 15:21:07 CDT 2015]; root of context hierarchy
2015-07-30 15:21:19.893 INFO 5704 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]
,methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEnt
ity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorControlle
r.error(javax.servlet.http.HttpServletRequest)
2015-07-30 15:21:19.902 INFO 5704 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]
,methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.ser
vlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpSe
rvletRequest)
2015-07-30 15:21:19.982 INFO 5704 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [
/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-07-30 15:21:19.986 INFO 5704 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [
/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-07-30 15:21:20.063 INFO 5704 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [
/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-07-30 15:21:20.463 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerAdapter : Looking for @Cont
rollerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@5aca1991: start
up date [Thu Jul 30 15:21:07 CDT 2015]; root of context hierarchy
2015-07-30 15:21:20.496 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}/{property}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.spring
framework.http.ResponseEntity<org.springframework.hateoas.ResourceSupport> org.springframework.data.rest.webmvc.Repos
itoryPropertyReferenceController.followPropertyReference(org.springframework.data.rest.webmvc.RootResourceInformation
,java.io.Serializable,java.lang.String,org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler) throws
java.lang.Exception
2015-07-30 15:21:20.502 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}/{property}/{propertyId}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto publ
ic org.springframework.http.ResponseEntity<org.springframework.hateoas.ResourceSupport> org.springframework.data.rest
.webmvc.RepositoryPropertyReferenceController.followPropertyReference(org.springframework.data.rest.webmvc.RootResour
ceInformation,java.io.Serializable,java.lang.String,java.lang.String,org.springframework.data.rest.webmvc.PersistentE
ntityResourceAssembler) throws java.lang.Exception
2015-07-30 15:21:20.503 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}/{property}],methods=[DELETE],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.spr
ingframework.http.ResponseEntity<? extends org.springframework.hateoas.ResourceSupport> org.springframework.data.rest
.webmvc.RepositoryPropertyReferenceController.deletePropertyReference(org.springframework.data.rest.webmvc.RootResour
ceInformation,java.io.Serializable,java.lang.String) throws java.lang.Exception
2015-07-30 15:21:20.505 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}/{property}],methods=[GET],params=[],headers=[],consumes=[],produces=[application/x-spring-data-compact+js
on || text/uri-list],custom=[]}" onto public org.springframework.http.ResponseEntity<org.springframework.hateoas.Reso
urceSupport> org.springframework.data.rest.webmvc.RepositoryPropertyReferenceController.followPropertyReferenceCompac
t(org.springframework.data.rest.webmvc.RootResourceInformation,java.io.Serializable,java.lang.String,org.springframew
ork.data.rest.webmvc.PersistentEntityResourceAssembler) throws java.lang.Exception
2015-07-30 15:21:20.507 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}/{property}],methods=[PATCH || PUT],params=[],headers=[],consumes=[application/json || application/x-sprin
g-data-compact+json || text/uri-list],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<? e
xtends org.springframework.hateoas.ResourceSupport> org.springframework.data.rest.webmvc.RepositoryPropertyReferenceC
ontroller.createPropertyReference(org.springframework.data.rest.webmvc.RootResourceInformation,org.springframework.ht
tp.HttpMethod,org.springframework.hateoas.Resources<java.lang.Object>,java.io.Serializable,java.lang.String) throws j
ava.lang.Exception
2015-07-30 15:21:20.508 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}/{property}/{propertyId}],methods=[DELETE],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto p
ublic org.springframework.http.ResponseEntity<org.springframework.hateoas.ResourceSupport> org.springframework.data.r
est.webmvc.RepositoryPropertyReferenceController.deletePropertyReferenceId(org.springframework.data.rest.webmvc.RootR
esourceInformation,java.io.Serializable,java.lang.String,java.lang.String) throws java.lang.Exception
2015-07-30 15:21:20.510 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/schema],methods=[GET],params=[],headers=[],consumes=[],produces=[application/schema+json],custom=[]}" onto pub
lic org.springframework.http.HttpEntity<org.springframework.data.rest.webmvc.json.JsonSchema> org.springframework.dat
a.rest.webmvc.RepositorySchemaController.schema(org.springframework.data.rest.webmvc.RootResourceInformation)
2015-07-30 15:21:20.512 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}],methods=[PUT],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.h
ttp.ResponseEntity<? extends org.springframework.hateoas.ResourceSupport> org.springframework.data.rest.webmvc.Reposi
toryEntityController.putItemResource(org.springframework.data.rest.webmvc.RootResourceInformation,org.springframework
.data.rest.webmvc.PersistentEntityResource,java.io.Serializable,org.springframework.data.rest.webmvc.PersistentEntity
ResourceAssembler) throws org.springframework.web.HttpRequestMethodNotSupportedException
2015-07-30 15:21:20.513 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.h
ttp.ResponseEntity<org.springframework.hateoas.Resource<?>> org.springframework.data.rest.webmvc.RepositoryEntityCont
roller.getItemResource(org.springframework.data.rest.webmvc.RootResourceInformation,java.io.Serializable,org.springfr
amework.data.rest.webmvc.PersistentEntityResourceAssembler) throws org.springframework.web.HttpRequestMethodNotSuppor
tedException
2015-07-30 15:21:20.514 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}],methods=[OPTIONS],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.ht
tp.ResponseEntity<?> org.springframework.data.rest.webmvc.RepositoryEntityController.optionsForCollectionResource(org
.springframework.data.rest.webmvc.RootResourceInformation)
2015-07-30 15:21:20.515 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}],methods=[HEAD],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.
ResponseEntity<?> org.springframework.data.rest.webmvc.RepositoryEntityController.headCollectionResource(org.springfr
amework.data.rest.webmvc.RootResourceInformation) throws org.springframework.web.HttpRequestMethodNotSupportedExcepti
on
2015-07-30 15:21:20.516 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.hateoa
s.Resources<?> org.springframework.data.rest.webmvc.RepositoryEntityController.getCollectionResource(org.springframew
ork.data.rest.webmvc.RootResourceInformation,org.springframework.data.rest.webmvc.support.DefaultedPageable,org.sprin
gframework.data.domain.Sort,org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler) throws org.spring
framework.data.rest.webmvc.ResourceNotFoundException,org.springframework.web.HttpRequestMethodNotSupportedException
2015-07-30 15:21:20.517 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}],methods=[GET],params=[],headers=[],consumes=[],produces=[application/x-spring-data-compact+json || text/uri-l
ist],custom=[]}" onto public org.springframework.hateoas.Resources<?> org.springframework.data.rest.webmvc.Repository
EntityController.getCollectionResourceCompact(org.springframework.data.rest.webmvc.RootResourceInformation,org.spring
framework.data.rest.webmvc.support.DefaultedPageable,org.springframework.data.domain.Sort,org.springframework.data.re
st.webmvc.PersistentEntityResourceAssembler) throws org.springframework.data.rest.webmvc.ResourceNotFoundException,or
g.springframework.web.HttpRequestMethodNotSupportedException
2015-07-30 15:21:20.519 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}],methods=[POST],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.
ResponseEntity<org.springframework.hateoas.ResourceSupport> org.springframework.data.rest.webmvc.RepositoryEntityCont
roller.postCollectionResource(org.springframework.data.rest.webmvc.RootResourceInformation,org.springframework.data.r
est.webmvc.PersistentEntityResource,org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler) throws or
g.springframework.web.HttpRequestMethodNotSupportedException
2015-07-30 15:21:20.520 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}],methods=[OPTIONS],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframewo
rk.http.ResponseEntity<?> org.springframework.data.rest.webmvc.RepositoryEntityController.optionsForItemResource(org.
springframework.data.rest.webmvc.RootResourceInformation)
2015-07-30 15:21:20.521 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}],methods=[HEAD],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.
http.ResponseEntity<?> org.springframework.data.rest.webmvc.RepositoryEntityController.headForItemResource(org.spring
framework.data.rest.webmvc.RootResourceInformation,java.io.Serializable) throws org.springframework.web.HttpRequestMe
thodNotSupportedException
2015-07-30 15:21:20.522 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}],methods=[PATCH],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework
.http.ResponseEntity<org.springframework.hateoas.ResourceSupport> org.springframework.data.rest.webmvc.RepositoryEnti
tyController.patchItemResource(org.springframework.data.rest.webmvc.RootResourceInformation,org.springframework.data.
rest.webmvc.PersistentEntityResource,java.io.Serializable,org.springframework.data.rest.webmvc.PersistentEntityResour
ceAssembler) throws org.springframework.web.HttpRequestMethodNotSupportedException,org.springframework.data.rest.webm
vc.ResourceNotFoundException
2015-07-30 15:21:20.525 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/{id}],methods=[DELETE],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframewor
k.http.ResponseEntity<?> org.springframework.data.rest.webmvc.RepositoryEntityController.deleteItemResource(org.sprin
gframework.data.rest.webmvc.RootResourceInformation,java.io.Serializable) throws org.springframework.data.rest.webmvc
.ResourceNotFoundException,org.springframework.web.HttpRequestMethodNotSupportedException
2015-07-30 15:21:20.528 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/],meth
ods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.HttpEntity<or
g.springframework.data.rest.webmvc.RepositoryLinksResource> org.springframework.data.rest.webmvc.RepositoryController
.listRepositories()
2015-07-30 15:21:20.529 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/],meth
ods=[OPTIONS],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.HttpEntit
y<?> org.springframework.data.rest.webmvc.RepositoryController.optionsForRepositories()
2015-07-30 15:21:20.529 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/],meth
ods=[HEAD],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEnti
ty<?> org.springframework.data.rest.webmvc.RepositoryController.headForRepositories()
2015-07-30 15:21:20.532 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/search/{search}],methods=[HEAD],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.sprin
gframework.http.ResponseEntity<java.lang.Object> org.springframework.data.rest.webmvc.RepositorySearchController.head
ForSearch(org.springframework.data.rest.webmvc.RootResourceInformation,java.lang.String)
2015-07-30 15:21:20.534 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/search/{search}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.spring
framework.http.ResponseEntity<java.lang.Object> org.springframework.data.rest.webmvc.RepositorySearchController.execu
teSearch(org.springframework.data.rest.webmvc.RootResourceInformation,org.springframework.web.context.request.WebRequ
est,java.lang.String,org.springframework.data.rest.webmvc.support.DefaultedPageable,org.springframework.data.domain.S
ort,org.springframework.data.rest.webmvc.PersistentEntityResourceAssembler)
2015-07-30 15:21:20.534 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/search],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework
.hateoas.ResourceSupport org.springframework.data.rest.webmvc.RepositorySearchController.listSearches(org.springframe
work.data.rest.webmvc.RootResourceInformation)
2015-07-30 15:21:20.536 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/search],methods=[HEAD],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframewor
k.http.HttpEntity<?> org.springframework.data.rest.webmvc.RepositorySearchController.headForSearches(org.springframew
ork.data.rest.webmvc.RootResourceInformation)
2015-07-30 15:21:20.536 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/search/{search}],methods=[OPTIONS],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.sp
ringframework.http.ResponseEntity<java.lang.Object> org.springframework.data.rest.webmvc.RepositorySearchController.o
ptionsForSearch(org.springframework.data.rest.webmvc.RootResourceInformation,java.lang.String)
2015-07-30 15:21:20.537 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/search],methods=[OPTIONS],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframe
work.http.HttpEntity<?> org.springframework.data.rest.webmvc.RepositorySearchController.optionsForSearches(org.spring
framework.data.rest.webmvc.RootResourceInformation)
2015-07-30 15:21:20.538 INFO 5704 --- [ main] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped "{[/{repos
itory}/search/{search}],methods=[GET],params=[],headers=[],consumes=[],produces=[application/x-spring-data-compact+js
on],custom=[]}" onto public org.springframework.hateoas.ResourceSupport org.springframework.data.rest.webmvc.Reposito
rySearchController.executeSearchCompact(org.springframework.data.rest.webmvc.RootResourceInformation,org.springframew
ork.web.context.request.WebRequest,java.lang.String,java.lang.String,org.springframework.data.rest.webmvc.support.Def
aultedPageable,org.springframework.data.domain.Sort,org.springframework.data.rest.webmvc.PersistentEntityResourceAsse
mbler)
2015-07-30 15:21:20.545 INFO 5704 --- [ main] o.s.d.r.w.BaseUriAwareHandlerMapping : Mapped "{[/alps/{
repository}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto org.springframework.http.Htt
pEntity<org.springframework.data.rest.webmvc.RootResourceInformation> org.springframework.data.rest.webmvc.alps.AlpsC
ontroller.descriptor(org.springframework.data.rest.webmvc.RootResourceInformation)
2015-07-30 15:21:20.545 INFO 5704 --- [ main] o.s.d.r.w.BaseUriAwareHandlerMapping : Mapped "{[/alps],
methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto org.springframework.http.HttpEntity<org.s
pringframework.hateoas.alps.Alps> org.springframework.data.rest.webmvc.alps.AlpsController.alps()
2015-07-30 15:21:20.546 INFO 5704 --- [ main] o.s.d.r.w.BaseUriAwareHandlerMapping : Mapped "{[/alps |
| /alps/{repository}],methods=[OPTIONS],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto org.springframe
work.http.HttpEntity<?> org.springframework.data.rest.webmvc.alps.AlpsController.alpsOptions()
2015-07-30 15:21:20.734 INFO 5704 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans
for JMX exposure on startup
2015-07-30 15:21:20.899 INFO 5704 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on
port(s): 8080 (http)
2015-07-30 15:21:20.901 INFO 5704 --- [ main] com.example.spring.rest.App : Started App in 14
.696 seconds (JVM running for 15.817)
Testing the Application
Now that the application is running, you can test it. You can use any REST client you want. The following examples use the cURL tool.
Getting the Book records
-
Open a command-line (Windows) or a terminal window (Linux).
-
To get the top-level service, execute the command:
curl http://localhost:8080
.C:\Program Files\curl>curl http://localhost:8080 { "_links" : { "books" : { "href" : "http://localhost:8080/books{?page,size,sort}", "templated" : true }, "profile" : { "href" : "http://localhost:8080/alps" } } }
There is a
book
link located athttp://localhost:8080/books
. The?page
,?size
, and?sort
elements are optional parameters that you can send in the URL.The
alps
link contains information about both the RESTful transitions and the attributes of each repository.Note: By default, Spring Data REST serves up REST resources at the root URI, "/" but there are multiple ways to change the base path.
-
Take a look at the
books
link:curl http://localhost:8080/books
C:\Program Files\curl>curl http://localhost:8080/books { "_links" : { "self" : { "href" : "http://localhost:8080/books{?page,size,sort}", "templated" : true }, "search" : { "href" : "http://localhost:8080/books/search" } }, "page" : { "size" : 20, "totalElements" : 0, "totalPages" : 0, "number" : 0 } }
This URL returns all
book
entities of the repository through thefindAll(…)
method. Currently, there are nobook
elements.The
search
link is a search resource that returns links for all query methods exposed by a repository, in this caseBookRepository
.
Adding Book Data to the Repository
To create a new book element you must call the
resource http://localhost:8080/books
usingPOST
operation and send the
parameters in JSON format.
- Create a new entity book:
curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"Hamlet\", \"author\" : \"William Shakespeare\",\"isbn\":\"978-0486272788\", \"published\":\"1937\",\"genre\":\"Novel\" }" http://localhost:8080/books
-i
includes the HTTP header in the output.-X
specifies the request method to use when communicating with the HTTP server. The specified request is used instead of the default method, which isGET
.-H
"Content-Type:application/json" sets the content type so that the application knows the payload contains a JSON object. Currently, only JSON representations are supported.-d
is used to send data.
C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"Hamlet\", \"author\" : \"William Shakespeare\",\"isbn\":\"978-0486272788\", \"published\":\"1937\",\"genre\":\"Novel\" }" http://localhost:8 080/books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/1 Content-Length: 0 Date: Wed, 10 Jun 2015 21:14:53 GMT
The
POST
operation includes aLocation
header that contains the URI of the newly created resource. -
Query all book entities again:
curl http://localhost:8080/books
C:\Program Files\curl>curl http://localhost:8080/books { "_links" : { "self" : { "href" : "http://localhost:8080/books{?page,size,sort}", "templated" : true }, "search" : { "href" : "http://localhost:8080/books/search" } }, "_embedded" : { "books" : [ { "title" : "Hamlet", "author" : "William Shakespeare", "isbn" : "978-0486272788", "published" : 1937, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/1" } } } ] }, "page" : { "size" : 20, "totalElements" : 1, "totalPages" : 1, "number" : 0 } }
Now, there is one element in the
books
object. -
You can query directly for the individual record:
curl http://localhost:8080/books/1
C:\Program Files\curl>curl http://localhost:8080/books/1 { "title" : "Hamlet", "author" : "William Shakespeare", "isbn" : "978-0486272788", "published" : 1937, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/1" } } }
-
Download the books.sh file (for Linux) or book.bat file (for Windows) file that contains a list of the
POST
requests to create new book elements, and execute the file.C:\Program Files\curl>books.bat C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"One Hundred Years of Sol itude\", \"author\" : \"Gabriel Garcia Marquez\",\"isbn\":\"978-0060883287\", \"published\":\"1967\",\"genre\":\"Nov el\" }" http://localhost:8080/books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/2 Content-Length: 0 Date: Wed, 10 Jun 2015 22:29:14 GMT C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"Love in the Time of Chol era\", \"author\" : \"Gabriel Garcia Marquez\",\"isbn\":\"978-0307389732\", \"published\":\"1985\",\"genre\":\"Novel \" }" http://localhost:8080/books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/3 Content-Length: 0 Date: Wed, 10 Jun 2015 22:29:14 GMT C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"Chronicle of a Death For etold\", \"author\" : \"Gabriel Garcia Marquez\",\"isbn\":\"978-1400034710\", \"published\":\"1981\",\"genre\":\"Nov el\" }" http://localhost:8080/books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/4 Content-Length: 0 Date: Wed, 10 Jun 2015 22:29:14 GMT C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"Of Love and Other Demons \", \"author\" : \"Gabriel Garcia Marquez\",\"isbn\":\"978-1400034925\", \"published\":\"1967\",\"genre\":\"Novel\" }" http://localhost:8080/books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/5 Content-Length: 0 Date: Wed, 10 Jun 2015 22:29:14 GMT C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"Memories of My Melanchol y Whores\", \"author\" : \"Gabriel Garcia Marquez\",\"isbn\":\"978-1400034925\", \"published\":\"2004\",\"genre\":\" Novel\" }" http://localhost:8080/books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/6 Content-Length: 0 Date: Wed, 10 Jun 2015 22:29:14 GMT C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"Lolita\", \"author\" : \"Vladimir Nabokov\",\"isbn\":\"978-0679723165\", \"published\":\"1989\",\"genre\":\"Novel\" }" http://localhost:8080 /books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/7 Content-Length: 0 Date: Wed, 10 Jun 2015 22:29:14 GMT C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"Don Quijote de la Mancha \", \"author\" : \"Miguel de Cervantes\",\"isbn\":\"978-0307475411\", \"published\":\"2010\",\"genre\":\"Novel\" }" http://localhost:8080/books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/8 Content-Length: 0 Date: Wed, 10 Jun 2015 22:29:14 GMT C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"Le Petit Prince\", \"au thor\" : \"Antoine de Saint-Exupery\",\"isbn\":\"978-0156013987\", \"published\":\"2001\",\"genre\":\"Novel\" }" http ://localhost:8080/books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/9 Content-Length: 0 Date: Wed, 10 Jun 2015 22:29:14 GMT C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"La Reine Margot\", \"au thor\" : \"Alexandre Dumas\",\"isbn\":\"978-0199538447\", \"published\":\"2011\",\"genre\":\"Novel\" }" http://localh ost:8080/books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/10 Content-Length: 0 Date: Wed, 10 Jun 2015 22:29:14 GMT C:\Program Files\curl>curl -i -X POST -H "Content-Type:application/json" -d "{ \"title\" : \"The hobbit\", \"author\ " : \" J. R. R. Tolkien\",\"isbn\":\"978-0345339683\", \"published\":\"1937\",\"genre\":\"Novel\" }" http://localhost :8080/books HTTP/1.1 201 Created Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/11 Content-Length: 0 Date: Wed, 10 Jun 2015 22:29:14 GMT
Executing Custom Queries
A core principle of HATEOAS is that resources should be discoverable through the publication of links that point to the available resources. For every query method declared in the repository, you expose a query method resource.
You can find all the custom queries in search
resource
: curl
http://localhost:8080/books/search
.
The search resource returns a list of links pointing to the individual query method resources.
C:\Program Files\curl>curl http://localhost:8080/books/search
{
"_links" : {
"countByAuthor" : {
"href" : "http://localhost:8080/books/search/countByAuthor{?author}",
"templated" : true
},
"findByIsbn" : {
"href" : "http://localhost:8080/books/search/findByIsbn{?isbn}",
"templated" : true
},
"removeByAuthor" : {
"href" : "http://localhost:8080/books/search/removeByAuthor{?author}",
"templated" : true
},
"findByAuthorIgnoreCaseOrderByTitleAsc" : {
"href" : "http://localhost:8080/books/search/getBooksByAuthor{?author}",
"templated" : true
},
"findByTitleContaining" : {
"href" : "http://localhost:8080/books/search/findByTitleContaining{?title}",
"templated" : true
},
"getBooksBetweenYears" : {
"href" : "http://localhost:8080/books/search/getBooksBetweenYears{?startYear,endYear}",
"templated" : true
}
}
}
By default, the path of the search resource is
the name of the method declared in the
repository class and can be customized using the
@RestResource
annotation.
Here there are some request examples that you can try:
-
Search for a book by ISBN:
curl http://localhost:8080/books/search/findByIsbn?isbn=978-0156013987
.C:\Program Files\curl>curl http://localhost:8080/books/search/findByIsbn?isbn=978-0156013987 { "_embedded" : { "books" : [ { "title" : "Le Petit Prince", "author" : "Antoine de Saint-Exupery", "isbn" : "978-0156013987", "published" : 2001, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/9" } } } ] } }
This method returns a
Book
instance. If the parameter (isbn
) is not unique and more than one row is returned from the query then you get the500 - Internal Server Error
status. -
Search for books by author:
curl http://localhost:8080/books/search/getBooksByAuthor?author=Gabriel%20Garcia%20Marquez
C:\Program Files\curl>curl http://localhost:8080/books/search/getBooksByAuthor?author=Gabriel%20Garcia%20Marquez { "_embedded" : { "books" : [ { "title" : "Chronicle of a Death Foretold", "author" : "Gabriel Garcia Marquez", "isbn" : "978-1400034710", "published" : 1981, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/4" } } }, { "title" : "Love in the Time of Cholera", "author" : "Gabriel Garcia Marquez", "isbn" : "978-0307389732", "published" : 1985, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/3" } } }, { "title" : "Memories of My Melancholy Whores", "author" : "Gabriel Garcia Marquez", "isbn" : "978-1400034925", "published" : 2004, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/6" } } }, { "title" : "Of Love and Other Demons", "author" : "Gabriel Garcia Marquez", "isbn" : "978-1400034925", "published" : 1967, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/5" } } }, { "title" : "One Hundred Years of Solitude", "author" : "Gabriel Garcia Marquez", "isbn" : "978-0060883287", "published" : 1967, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/2" } } } ] } }
You must replace the spaces with
%20.
-
Search for books that contains "Love" in the title:
curl http://localhost:8080/books/search/findByTitleContaining?title=Love
C:\Program Files\curl>curl http://localhost:8080/books/search/findByTitleContaining?title=Love { "_embedded" : { "books" : [ { "title" : "Love in the Time of Cholera", "author" : "Gabriel Garcia Marquez", "isbn" : "978-0307389732", "published" : 1985, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/3" } } }, { "title" : "Of Love and Other Demons", "author" : "Gabriel Garcia Marquez", "isbn" : "978-1400034925", "published" : 1967, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/5" } } } ] } }
-
Count the books by author:
curl http://localhost:8080/books/search/countByAuthor?author=Gabriel%20Garcia%20Marquez
C:\Program Files\curl>curl http://localhost:8080/books/search/countByAuthor?author=Gabriel%20Garcia%20Marquez 5
-
Search books between years:
curl "http://localhost:8080/books/search/getBooksBetweenYears?startYear=2000&endYear=2015"
C:\Program Files\curl>curl "http://localhost:8080/books/search/getBooksBetweenYears?startYear=2000&endYear=2015" { "_embedded" : { "books" : [ { "title" : "Le Petit Prince", "author" : "Antoine de Saint-Exupery", "isbn" : "978-0156013987", "published" : 2001, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/9" } } }, { "title" : "Memories of My Melancholy Whores", "author" : "Gabriel Garcia Marquez", "isbn" : "978-1400034925", "published" : 2004, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/6" } } }, { "title" : "Don Quijote de la Mancha", "author" : "Miguel de Cervantes", "isbn" : "978-0307475411", "published" : 2010, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/8" } } }, { "title" : "La Reine Margot", "author" : "Alexandre Dumas", "isbn" : "978-0199538447", "published" : 2011, "genre" : "Novel", "_links" : { "self" : { "href" : "http://localhost:8080/books/10" } } } ] } }
Updating and Deleting the Book Records
Spring Data REST exposes a resource for
individual collection items as subresources of
the collection resource. The PUT
,
PATCH
, and DELETE
REST calls are used to replace, update, and
delete existing records.
-
Replace a record:
curl -i -X PUT -H "Content-Type:application/json" -d "{ \"title\" : \"The gift\", \"author\" : \"Vladimir Nabokov\",\"isbn\":\"978-0679725874\", \"published\":\"1992\",\"genre\":\"Novel\" }" http://localhost:8080/books/7
C:\Program Files\curl>curl -i -X PUT -H "Content-Type:application/json" -d "{ \"title\" : \"The gift\", \"author\" : \"Vladimir Nabokov\",\"isbn\":\"978-0679725874\", \"published\":\"1992\",\"genre\":\"Novel\" }" http://localhost:8080/books/7 HTTP/1.1 204 No Content Server: Apache-Coyote/1.1 Location: http://localhost:8080/books/7 Date: Fri, 12 Jun 2015 14:48:32 GMT
-
Update a record:
curl -i -X PATCH -i -H "Content-Type:application/json" -d "{ \"published\":\"1980\" }" http://localhost:8080/books/7
C:\Program Files\curl>curl -i -X PATCH -i -H "Content-Type:application/json" -d "{ \"published\":\"1980\" }" http://localhost:8080/books/7 HTTP/1.1 204 No Content Server: Apache-Coyote/1.1 Date: Fri, 12 Jun 2015 15:50:21 GMT
The
PUT
call replaces an entire record. Fields that aren't supplied will be replaced with thenull
value. ThePATCH
call can be used to update a subset of items. -
Delete a record:
curl -i -X DELETE -i http://localhost:8080/books/9
C:\Program Files\curl>curl -i -X DELETE -i http://localhost:8080/books/9
HTTP/1.1 204 No Content
Server: Apache-Coyote/1.1
Date: Fri, 12 Jun 2015 16:41:47 GMT
Spring Data REST is a really good choice for small to medium size applications, where you do not have a lot of custom business logic because it may be difficult to integrate custom business logic in a Spring Data REST application.
Deploying an Application for Oracle Cloud Deployment
To properly package, deploy, and execute your application in Oracle Application Container Cloud Service, there are a few key concepts you must understand.
Container Considerations
Oracle Application Container Cloud Service applications are run in Docker containers. Docker is an open source software container project. Think of a container as a lightweight virtual machine. Your application runs in its own isolated execution space that has its own memory, file system, and network access. Access to these operating system resources takes place without the cost of having to implement an entire virtual operating system.
When you deploy your application to Oracle Application Container Cloud Service, a container is dynamically generated for your application. The implication of this is that all the typical configuration information like the host name and port number are also dynamically generated.
Note: Any application that runs in this environment must be read key configuration information from the environment before the application starts.
Additionally, containers for your application can be dynamically allocated to scale up or down as is required by the application load. This dynamic scaling feature is made possible by the load balancers provided by Oracle Application Container Cloud Service. The balancer routes traffic to the application instances balancing the load.
Application Requirements
For a Java application to run on Oracle Application Container Cloud Service, there are a few requirements that must be met for the application to execute properly. The following is a list of architectural properties your application must have.
- Applications must be stateless: Because the service can run multiple instances of the same application, applications cannot share state. For example, items added to a shopping cart application on one instance would not be available to other instances of the application. Because application load is balanced between instances, there is no guarantee that the next request would be handled by the same instance as the last request. Trying to store an application state could result in possible data loss. Therefore, state information should be stored outside your application in a database. Modern databases are designed to handle connections from multiple concurrent clients.
- Applications communicate via network ports: The only way to communicate to a running application is through its assigned network port.
- Application must be configurable at runtime:
Applications must be able to read their
HOSTNAME
andPORT
using environment variables. This data can only be determined when and application launches in its host container. The application must be able to read these variables from the environment. - All dependencies must be included with the application: If your application requires a library to execute, then that library must be included with the application when it is deployed. This can be accomplished two ways:
- Create an uber JAR: When creating your application, all dependent libraries are included in the JAR file with the application.
- Use the classpath: All dependent libaries are included in separate jars files, but the path to each file is included in the CLASSPATH passed to your application.
If you follow the architecture principles described, your application should work fine on Oracle Application Container Cloud Service. For more information, see the Developer Guide.
Configuring Your Application to Run in the Cloud
For a Java SE application to run in Oracle
Application Container Cloud Service, the application
must be able to read settings from environment
variables set in the application's container.
Specifically, your application must read the HOSTNAME
and PORT
environment variables; you
must configure your application accordingly.
- Create the
resources
folder insrc/main
directory. - Create a new file name
application.properties.
-
Add following lines:
server.port=${PORT} server.address=${HOSTNAME}
With these improvements, you can set the host name or port using environment variables.
Downloading Updated Suggested Solution
If you want to see the complete set of project files, then download the source files and Maven project from: book-service.zip.
Creating a Cloud-Ready Package Using the manifest.json File
The final step for packaging your application is
combine your application archive with the manifest.json
file.
The manifest.json File
The manifest.json
file provides
metadata about your Java application. This
information includes the version of Java and the
command used to execute the application. Notes
about your application along with release
information may be included.
{
"runtime": {
"majorVersion": "7"
},
"command": "sh target\/bin\/start",
"release": {
"build": "150520.1154",
"commit": "d8c2596364d9584050461",
"version": "15.1.0"
},
"notes": "notes related to release"
}
JSON Fields Defined
- runtime
majorVersion:
the version of the runtime for example, for Java it is 7 or 8command:
the command to execute after deploying the application- release
build:
a user-specified text value that identify this buildcommit:
a user-specified text value related to this deploymentversion:
the version of the text string maintained by the user
Here is the manifest.json
file
used in the sample application:
{
"runtime":{
"majorVersion": "8"
},
"command": "java -jar book-service-1.0-SNAPSHOT.jar",
"release": {
"build": "20150813Build",
"commit": "commitString",
"version": "20150813Version"
},
"notes": "REST app for testing"
}
Note: From the simplicity of the command used to launch the application, you can see that this is an uber JAR deployment. This application uses Java 8. If your application needs any special command-line switches to run, this is where you would specify here.
Creating the Final Archive
As a final step, you create an archive that
contains the application archive and the manifest.json
file. Both files are stored at the root level of
the archive file.
To create an archive using zip
command.
zip book-rest-service.zip manifest.json
book-service-1.0-SNAPSHOT.jar
To create an archive using the tar
command:.
tar cvfz
book-rest-service
.tgz
manifest.json book-service-1.0-SNAPSHOT.jar
After creating the archive, your application should be ready for deployment.
Packaging an Application Archive Without an Uber JAR File
As an alternative to an uber JAR file, an application can be started by specifying libraries on the java command line or by using a Bash shell script. Oracle Application Container Cloud Service uses a Linux container for the execution of applications so most of the rules that apply to running a command in Linux apply.
Assumptions
The two examples that follow are based on the following assumptions:
- The home directory for the application is
/u01/app
. - The application execution code is stored in a
JAR file named
app.jar
. - All required Java libraries are stored in the
lib
directory. Thelib
directory will be included as part of the final archive as a subdirectory of the archive root directory. - The
lib
directory contains the following JAR libraries:web.jar, rest.jar, media.jar
.
Executing the Application with Java and Classpath
Given the previews assumptions, and assuming the lib
directory is included as described, the following
command could be used to launch the application:
java -cp '/u01/app/lib/*' -jar app.jar
After, the application is deployed, the libraries
in the lib
directory are copied under
the application's home directory. The previews
command executes the java
command and
loads all of the libraries in the /u01/app/lib
directory. This should provide the JVM with all the
necessary classes to run the application.
Executing the Application with a Shell Script
As an alternative you can execute your application using a shell script. The command line for executing your application should look something like this:
bash -l ./applicationScript.sh
The CLASSPATH
environment variable
can also be used to set the path. In this example,
the script may contain the following two lines.
export
CLASSPATH=/u01/app/lib/web.jar;/u01/app/lib/rest.jar;/u01/app/lib/media.jar;
java -jar app.jar
Cloud Deployment Summary
To properly package your application, you must go follow these steps:
- Create an application that is configurable at runtime.
- Package your application as an uber JAR or as separate library JARS file.
- Create an application archive that contains your
application and the
manifest.json
file in a single file. Themanifest.json
file must be stored in theroot
directory of your archive. - Deploy your application to Oracle Application Container Cloud Service.
Note: The manifest.json
file
is optional because the information in the file can
also be configured using the user interface.
Deploying Your Applicaiton to Oracle Application Container
Now that you have created an application and packaged it for deployment on Oracle Application Container Cloud Service OBE, you are ready to deploy it. The steps for deploying your application are described in the OBE: Deploying an Application to Oracle Application Container Cloud Service.
Want to Learn More?
Credits
- Curriculum Developer: Luz Elena Peralta Ayala
- QA: Veerabhadra Rao Putrevu