Spring Boot and Spring Data REST - exposing repositories over REST

Exposing Spring Data repositories over REST is pretty easy with Spring Boot and Spring Data REST. With minimal code one can create REST representations of JPA entities that follow the HATEOAS principle. I decided to re-use Spring PetClinic’s JPA entities (business layer) as the foundation for this article.

Change Log

  • 18/12/2016 - Source code updated: Spring Boot to 1.4.2, migrated from Joda to JSR 310 DateTime API

Application foundation

The PetClinic’s model is relatively simple, but it consist of some unidirectional and bi-directional associations, as well as basic inheritance:

In addition, the Spring’s PetClinic provides SQL scripts for HSQLDB which makes that generating schema and populating it with sample data in my new application was super easy.

Project dependencies

As a base for the configuration I used Spring Initializr and I generated a basic Gradle project. In order to utilize Spring Data REST in a Spring Boot Application I added the following Boot Starters:

compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-data-jpa")
compile("org.springframework.boot:spring-boot-starter-data-rest")

In addition, I added HSQLDB dependency to the project:

compile("org.hsqldb:hsqldb:2.3.2")

The original project uses org.joda.time.DateTime for date fields and uses org.jadira.usertype.dateandtime.joda.PersistentDateTime that allows persisting it with Hibernate. In order to be able to use it in the new project I needed to add the following dependencies:

compile("joda-time:joda-time:2.4")
compile("org.jadira.usertype:usertype.jodatime:2.0.1")

While working with the API, I noticed that although the date fields in the original project were annotated with Spring’s @DateTimeFormat they were not properly serialized. I found out that I need to use @JsonFormatter, so another dependency was added to the build.gradle:

compile("com.fasterxml.jackson.datatype:jackson-datatype-joda:2.4.2");

Once in the classpath, Spring Boot auto configures com.fasterxml.jackson.datatype.joda.JodaModule via org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration.

Please note, that if you wanted to serialize Java 8 Date & Time types properly, you would need to add Jackson Datatype JSR310 dependency to project and make org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters available during component scan.

Initializing the database

To initialize data source I added schema-hsqldb.sql and data-hsqldb.sql files to src/main/resources. Finally, I several properties were added to application.properties:

   spring.datasource.platform = hsqldb
   spring.jpa.generate-ddl = false
   spring.jpa.hibernate.ddl-auto = none

Now, at the application start up, files will be picked up automatically and the data source will be initialized and discovering the API will be much easier, as there is data!

Repositories

The general idea of Spring Data REST is that builds on top of Spring Data repositories and automatically exports those as REST resources. I created several repositories, one for each entity (OwnerRepository, PetRepository and so on). All repositories are Java interfaces extending from PagingAndSortingRepository.

No additional code is needed at this stage: no @Controllers, no configuration (unless customization is needed). Spring Boot will automagically configure everything for us.

Running the application

With the whole configuration in place the project can be executed (you will find link to the complete project in the bottom of the article). If you are lucky, the application will start and you can navigate to http://localhost:8080 that points to a collection of links to all available resources (root resource). The response’s content type is .

HAL

The resources are implemented in a Hypermedia-style and by default Spring Data REST uses HAL with content type application/hal+json to render responses. HAL is a simple format that gives an easy way to link resources. Example:

$ curl localhost:8080/owners/1
{
  "firstName" : "George",
  "lastName" : "Franklin",
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/owners/1"
    },
    "pets" : {
      "href" : "http://localhost:8080/owners/1/pets"
    }
  }
}

In terms of Spring Data REST, there are several types of resources: collection, item, search, query method and association and all utilize application/hal+json content type in responses.

Collection and item resource

Collection resource support both GET and POST methods. Item resources generally support GET, PUT, PATCH and DELETE methods. Note that, PATCH applies values sent with the request body whereas PUT replaces the resource.

Search and find method resource

The search resource returns links for all query methods exposed by a repository whereas the query method resource executes the query exposed through an individual query method on the repository interface. Both are read-only therefore support only GET method.

To visualize that, I added a find method to OwnerRepository:

List<Owner> findBylastName(@Param("lastName") String lastName);

Which was then exposed under http://localhost:8080/owners/search

$ curl http://localhost:8080/owners/search                                     
{                                                                              
  "_links" : {                                                                 
    "findBylastName" : {                                                       
      "href" : "http://localhost:8080/owners/search/findBylastName{?lastName}",
      "templated" : true                                                       
    }                                                                          
  }                                                                            
}                                                                              

Association resource

Spring Data REST exposes sub-resources automatically. The association resource supports GET, POST and PUT methods.

and allow managing them. While working with association you need to be aware of text/uri-list content type. Requests with this content type contain one or more URIs (each URI shall appear on one and only one line) of resource to add to the association.

In the first example, we will look at unidirectional relation in Vet class:

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
        inverseJoinColumns = @JoinColumn(name = "specialty_id"))
private Set<Specialty> specialties;

In order to add existing specialties to the collection of vet’s specialties PUT request must be executed:

curl -i -X PUT -H "Content-Type:text/uri-list" -d $'http://localhost:8080/specialties/1\nhttp://localhost:8080/specialties/2' http://localhost:8080/vets/1/specialties

Removing the association can be done with DELETE method as below:

curl -i -X DELETE http://localhost:8080/vets/1/specialties/2

Let’s look at another example:

// Owner
@OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<Pet> pets;

// Pet
@ManyToOne(cascade = CascadeType.ALL, optional = false)
@JoinColumn(name = "owner_id")
private Owner owner;

Setting owner of a pet can be done with the below request:

curl -i -X PUT -H "Content-Type:text/uri-list" -d "http://localhost:8080/owners/1" http://localhost:8080/pets/2/owner

But what about removing the owner? Since the owner must be always set for the pet, we get HTTP/1.1 409 Conflict whilst trying to unset it with the below command:

curl -i -X DELETE http://localhost:8080/pets/2/owner

Embedded Entity References

What if creating an object with a reference to existing @Entity because of integrity constraints is needed? In the below example vet address has a relation to vet, and vet must be existent:

@Entity
@Table(name = "vet_addresses")
public class VetAddress extends BaseEntity {

    @Column(name = "postal_code")
    @NotBlank
    private String postalCode;

    // Other properties

    @OneToOne(optional = false)
    @JoinColumn(name = "vet_id")
    private Vet vet;
}

To create new vet address the existing reference to vet must be provided. And this can be done by providing a link to the reference:

curl -i -X POST -H "Content-Type: application/json" -d '{"postalCode": "12345", "city": "Zukowo", "province": "Pomorskie", "vet" : "http://localhost:8080/vets/1"}' localhost:8080/vetAddresses

In case the reference is not provided or it does not exist, the appropriate error will be returned.

The above is based on the following article: https://github.com/spring-projects/spring-data-rest/wiki/Embedded-Entity-references-in-complex-object-graphs

Integration Tests

With Spring Boot, it is possible to start a web application in a test and verify it with Spring Boot’s @IntegrationTest. Instead of using mocked server side web application context (MockMvc) we will use RestTemplate and its Spring Boot’s implementation to verify actual REST calls.

As we already know, the resources are of content type application/hal+json. So actually, it will not be possible to deserialize them directly to entity object (e.g. Owner). Instead, it must be deserialized to org.springframework.hateoas.Resource that wraps an entity and adds links to it. And since Resource is a generic type ParameterizedTypeReference must be used with RestTemplate.

The below example visualizes that:


private RestTemplate restTemplate = new TestRestTemplate();

@Test
public void getsOwner() {
    String ownerUrl = "http://localhost:9000/owners/1";

    ParameterizedTypeReference<Resource<Owner>> responseType = new ParameterizedTypeReference<Resource<Owner>>() {};

    ResponseEntity<Resource<Owner>> responseEntity =
            restTemplate.exchange(ownerUrl, GET, null, responseType);

    Owner owner = responseEntity.getBody().getContent();
    assertEquals("George", owner.getFirstName());

    // more assertions

}

This approach is well described in the following article: Consuming Spring-hateoas Rest service using Spring RestTemplate and Super type tokens

Summary

With couple steps and the power of Spring Boot and Spring Data REST I created API for an existing PetClinic’s database. There is much more one can do with Spring Data REST (e.g. customization) and apart from rather poor documentation, comparing to other Spring projects, it seems like Spring Data REST may speedup the development significantly. In my opinion, this is a good project to look at when rapid prototyping is needed.

References

Popular posts from this blog

Parameterized tests in JavaScript with Jest

macOS: Insert current date shortcut with `Shortcuts.app`