Wednesday, October 8, 2014

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

13 comments:

  1. Hi,
    Very nice article as usual.

    But I have few questions regarding Spring Data REST.

    I tried to use Spring Data REST for one of my recent project but gave up on it due to following reason:

    When you ask for a collection resource (ex: /persons) all you got is a bunch of links. Typical scenario will be to display a grid with some basic info. But SD REST will give only links and you have to issue (N+1) GET requests again.

    Is there anyway to say to expand some specified properties inline? Like in addition to a link to /person/1 I want to have id and name properties also to be present in json response.

    One argument might be performance impact for not giving properties details inline. But I am getting data page by page and performance is not a problem.

    Also somewhere I read about Projections to address this issue. But couldn't get much info on it. Any thoughts on Projections support?

    ReplyDelete
    Replies
    1. Hello,

      Thanks for your comment.

      If you browse through e.g. /owners you will notice that there are links to related entities, but all simple properties are exported too for each owner:

      http://localhost:8080/owners

      {
      "firstName": "George",
      "lastName": "Franklin",
      "address": "110 W. Liberty St.",
      "city": "Madison",
      "telephone": "6085551023",
      "_links": {
      "self": {
      "href": "http://localhost:8080/owners/1"
      },
      "pets": {
      "href": "http://localhost:8080/owners/1/pets"
      }
      }
      }

      Could you please try running the project and see if this is how you expect it to be?

      I also think (I am not using Spring Data REST in projects yet) that for better customization it is better to use HATEOAS project and create resources yourself.

      Delete
    2. I have the same issue as asked above. The JSON from my code does not spit out the properties at all.

      {
      "_links": {
      "self": {
      "href": "http://localhost:8080/users/3"
      }
      }
      }

      Delete
  2. Very nice work! Can you know how to configure Spring REST to able to use POST json request like in this post?

    https://github.com/spring-projects/spring-data-rest/wiki/Embedded-Entity-references-in-complex-object-graphs

    ReplyDelete
    Replies
    1. Hi Michał,

      Thanks for your comment. I am not sure if there is a need to configure anything. Actually, it works out of the box. I added new Entity: VetAdreess with non optional @OneToOne relation to Vet and adding VetAddress can be done like this (assuming that Vet is existent):

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

      In response you get: HTTP/1.1 201 Created

      Is this what you were looking for? (I will extend the blog post to include this example, thanks!)

      Delete
    2. I added this description to the article (Embedded Entity References)

      Delete
  3. Hi Rafat,

    How could I get set of pet objects belonging to a owner?
    I don't see the pet names if i access http://localhost:8080/owners/1/pets

    ReplyDelete
  4. Is there data in the database? I just checked it and it returns one pet for owner with id one:

    {
    "_embedded": {
    "pets": [
    {
    "name": "Leo",
    "birthDate": "2010/09/07",
    "_links": {
    "self": {
    "href": "http://localhost:8080/pets/1"
    },
    "visits": {
    "href": "http://localhost:8080/pets/1/visits"
    },
    "owner": {
    "href": "http://localhost:8080/pets/1/owner"
    },
    "type": {
    "href": "http://localhost:8080/pets/1/type"
    }
    }
    }
    ]
    }
    }

    ReplyDelete
  5. Rafal,

    Yes database has data. I posted it to. My question was what method should be written to get pets belonging to a particular owner. I don't see any method written in PetRepository.

    ReplyDelete
  6. How do I POST only to vet_specialties table? Posting the below URL throws 404.
    http://localhost:8080/owners/1/pets/1

    ReplyDelete
    Replies
    1. Add new specialty (e.g. curl --verbose -X POST -H "Content-Type: application/json" -d'{"name":"Demo"}' http://localhost:8080/specialties)

      And then 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/4' http://localhost:8080/vets/1/specialties

      Delete