Enterprise Java

Spring Data REST in Action

What is spring-data-rest?

spring-data-rest, a recent addition to the spring-data project, is a framework that helps you expose your entities directly as RESTful webservice endpoints. Unlike rails, grails or roo it does not generate any code achieving this goal. spring data-rest supports JPA, MongoDB, JSR-303 validation, HAL and many more. It is really innovative and lets you setup your RESTful webservice within minutes. In this example i’ll give you a short overview of what spring-data-rest is capable of.

Initial Configuration

I’m gonna use the new Servlet 3 Java Web Configuration instead of an ancient web.xml. Nothing really special here.

public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[]{AppConfiguration.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfiguration.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

WebAppInitializer.java on github

I am initializing hibernate as my database abstraction layer in the AppConfiguration class. I’m using an embedded database (hsql), since i want to keep this showcase simple stupid. Still, nothing special here.

@Configuration
@EnableJpaRepositories
@EnableTransactionManagement
public class AppConfiguration {

    @Bean
    public DataSource dataSource() {
        EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
        return builder.setType(EmbeddedDatabaseType.HSQL).build();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        vendorAdapter.setDatabase(Database.HSQL);
        vendorAdapter.setGenerateDdl(true);

        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(vendorAdapter);
        factory.setPackagesToScan(getClass().getPackage().getName());
        factory.setDataSource(dataSource());

        return factory;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        return new JpaTransactionManager();
    }
}

AppConfiguration.java on github

Now to the application servlet configuration: WebConfiguration

@Configuration
public class WebConfiguration extends RepositoryRestMvcConfiguration {
}

WebConfiguration.java on github

Oh, well thats a bit short isnt it? Not a single line of code required for a complete setup. This is a really nice application of the convention over configuration paradigm. We can now start creating spring-data-jpa repositories as they will be exposed as RESTful resources automatically. And we can still add custom configuration to the WebConfiguration class if needed.

The Initialization was really short and easy. We didn’t have to code anything special. The only thing we did was setting up a database connection and hibernate, which is obviously inevitable. Now, that we have setup our “REST Servlet” and persistence, lets move on to the application itself, starting with the model.

The Model

I’ll keep it really simple creating only two related entities.
bookapi_model

@Entity
public class Book {

    @Id
    private String isbn;

    private String title;

    private String language;

    @ManyToMany
    private List<Author> authors;

}

Book.java on github

@Entity
public class Author {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Integer id;

    private String name;

    @ManyToMany(mappedBy = "authors")
    private List<Book> books;

}

Author.java on github

To finally make the entities persistent and exposed as a RESTful webservice, we need spring-data repositories. A repository is basically a DAO. It offers CRUD functionality for our entities. spring-data takes away most of your programming effort creating such repositories. We just have to define an empty interface, spring-data does everything else out of the box. Still, it is easily customizable thanks to its design by convention over configuration!

The Actual Repositories

@RestResource(path = "books", rel = "books")
public interface BookRepository extends PagingAndSortingRepository<Book, Long> {
}

BookRepository.java on github

@RestResource(path = "authors", rel = "authors")
public interface AuthorRepository extends PagingAndSortingRepository<Author, Integer> {
}

AuthorRepository.java on github

Again, there is nearly no code needed. Even the @RestResource annotation could be left out. But if i did, the path and rel would be named after the entity, which i dont want. A REST resource that contains multiple children should be named plural though.

Accessing The Result

Our RESTful webservice is now ready for deployment. Once run, it lists all available resources on the root, so you can navigate from there.

GET http://localhost:8080/

{
  "links" : [ {
    "rel" : "books",
    "href" : "http://localhost:8080/books"
  }, {
    "rel" : "authors",
    "href" : "http://localhost:8080/authors"
  } ],
  "content" : [ ]
}

Fine! Now lets create an author and a book.

POST http://localhost:8080/authors

{"name":"Uncle Bob"}

Response

201 Created
Location: http://localhost:8080/authors/1

PUT http://localhost:8080/books/0132350882

{
  "title": "Clean Code",
  "authors": [
      {
          "rel": "authors",
          "href": "http://localhost:8080/authors/1"
      }
  ]
}

Response

201 Created

Noticed how i used PUT to create the book? This is because its id is the actual isbn. I have to tell the server which isbn to use since he cant guess it. I used POST for the author as his id is just an incremental number that is generated automatically. Also, i used a link to connect both, the book (/books/0132350882) and the author (/authors/1). This is basically what hypermedia is all about: Links are used for navigation and relations between entities.

Now, lets see if the book was created accordingly.

GET http://localhost:8080/books

{
  "links" : [ ],
  "content" : [ {
    "links" : [ {
      "rel" : "books.Book.authors",
      "href" : "http://localhost:8080/books/0132350882/authors"
    }, {
      "rel" : "self",
      "href" : "http://localhost:8080/books/0132350882"
    } ],
    "title" : "Clean Code"
  } ],
  "page" : {
    "size" : 20,
    "totalElements" : 1,
    "totalPages" : 1,
    "number" : 1
  }
}

Fine!

Here is an Integration Test, following these steps automatically. It is also available in the example on github.

public class BookApiIT {

    private final RestTemplate restTemplate = new RestTemplate();

    private final String authorsUrl = "http://localhost:8080/authors";
    private final String booksUrl = "http://localhost:8080/books";

    @Test
    public void testCreateBookWithAuthor() throws Exception {
        final URI authorUri = restTemplate.postForLocation(authorsUrl, sampleAuthor()); // create Author

        final URI bookUri = new URI(booksUrl + "/" + sampleBookIsbn);
        restTemplate.put(bookUri, sampleBook(authorUri.toString())); // create Book linked to Author

        Resource<Book> book = getBook(bookUri);
        assertNotNull(book);

        final URI authorsOfBookUri = new URI(book.getLink("books.Book.authors").getHref());
        Resource<List<Resource<Author>>> authors = getAuthors(authorsOfBookUri);
        assertNotNull(authors.getContent());
        assertFalse(authors.getContent().isEmpty()); // check if /books/0132350882/authors contains an author
    }

    private String sampleAuthor() {
        return "{\"name\":\"Robert C. Martin\"}";
    }

    private final String sampleBookIsbn = "0132350882";

    private String sampleBook(String authorUrl) {
        return "{\"title\":\"Clean Code\",\"authors\":[{\"rel\":\"authors\",\"href\":\"" + authorUrl + "\"}]}";
    }

    private Resource<Book> getBook(URI uri) {
        return restTemplate.exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference<Resource<Book>>() {
        }).getBody();
    }

    private Resource<List<Resource<Author>>> getAuthors(URI uri) {
        return restTemplate.exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference<Resource<List<Resource<Author>>>>() {
        }).getBody();
    }
}

BookApiIT.java on github

Conclusion

We have created a complete RESTful webservice without much coding effort. We just defined our entities and database connection. spring-data-rest stated that everything else is just boilerplate, and i agree.

To consume the webservices manually, consider the rest-shell. It is a command-shell making the navigation in your webservice as easy and fun as it could be. Here is a screenshot:
rest_shell
The complete example is available on my github
https://github.com/gregorriegler/babdev-spring/tree/master/spring-data-rest
https://github.com/gregorriegler/babdev-spring

 

Reference: Spring Data REST in Action from our JCG partner Gregor Riegler at the Be a better Developer blog.

Gregor Riegler

Gregor is a passionate software engineer and RESTafarian who loves to continuously improve. He is interested in modern web development, oo-design and extreme programming. He is also serious about clean code and TDD.
Subscribe
Notify of
guest

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

24 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
John Smith
John Smith
10 years ago

Your maven project doesn’t build. mvn package produces errors.

Gregor
10 years ago
Reply to  John Smith

Thank you! i just found out that some really nice guy called Jay Sissom already fixed that.
Thank you Jay, your pull request was merged!

It should now build

Inshan
Inshan
10 years ago
Reply to  Gregor

Maven still cannot build. I am getting the following errors:
Failed to execute goal org.apache.maven.plugins:maven-war-plugin:2.1.1:war (default-war) on project message-converter: Error assembling WAR: webxml attribute is required (or pre-existing WEB-INF/web.xml if executing in update mode) -> [Help 1]

Gregor
10 years ago
Reply to  Inshan

web.xml was also missing for message-converter. i added it. you should now be able to package.

but please, if you want to build the spring-data-rest package, just build the submodule not the main module. otherwise you are building both.

greetings, gregor

Inshan
Inshan
10 years ago
Reply to  Gregor

Thanks. Builds fine. However when starting jetty (Jetty6 since we are using Java 6), the server starts up but it doesn’t seem to work well. In all, it would be great to have some steps as to how to get your code up and running soup to nuts.

Edirne
10 years ago

How i can fix if i use Turkish char set like; Ş, ı, ö, ğ ?

Gregor
10 years ago
Reply to  Edirne

Hi Edirne, Im not familiar how spring-data-rest deals with unicode in particular, but what i do know is that there are several Points you need to check in order to get your characters to work. 1.) Does your database support unicode characters? Did you use the right collation? Can you store a Ş in your database without spring-data? Often unicode problems are not caused by the framework but the database itself. So check that first! 2.) Are the characters broken in Java? Debug a Request, follow the parsed body and check if they are already broken within java. If not,… Read more »

Erik Peterson
10 years ago

I’m attempting to startup your project and so far been unsuccessful.

Do I need to download both of your projects? The REST project seems to complain about a parent pom.xml… I am unfamiliar with Maven and would really appreciate some assistance because your project is so far the simplest implementation I’ve found of a REST Data project..

My own needs will require a MySQL instead of HSQL implementation as well, but I imagine it shouldn’t be as hard to figure out as Maven has been.

Gregor
10 years ago
Reply to  Erik Peterson

Hi Erik,

Unfortunately, the examples are both inside a single multi-module-maven-project. Thus, i suggest you download the whole trunk. Maybe it’d be better if i separate those examples. I’ll do this as soon as possible.

Greetings, Gregor

Erik
10 years ago
Reply to  Gregor

Perhaps if you just explain what projects you’re leveraging I can try to whip up a gradle file.

I see Hibernate, Spring-Data, but unsure what else. Trying to get this awesome code to run in a standalone manner via a Gradle build is my current goal.

erik
10 years ago
Reply to  Gregor

After much research and toil, I got your project to compile in a standalone version. If you message me a good email I can zip it up to you.

The only thing I need now, is to learn how to customize this example. I’m running Tomcat and want this service to deploy in a folder .. not root.

I tried changing just WebAppInitializer.java , under the getServletMappings.. but it did not work.

Gregor
10 years ago
Reply to  erik

Hi Erik,

If you have a github account you can just make a pull request and change it. Otherwise, if you want to send me an e-mail, please goto my website. I have an obfuscated e-mail link in the right column

Erik
10 years ago
Reply to  Gregor

While it’d be great to get my first open source props, I’m too noob of a git user.. emailed you.

Erik
10 years ago

This is supposed to be deployed to a server, not run standalone.. correct? You kind of gloss over that in this part:

“Our RESTful webservice is now ready for deployment. Once run, it lists all available resources on the root, so you can navigate from there.”

I popped this into TomCat on my gradle based build of your project and it’s throwing errors.

Erik
10 years ago

In attempting to backtrack from my gradle build (it’s not deploying to TOMCAT properly), I thought I’d try building just your whole maven project.. however your pom.xml has no build goals. I’m brand new to most of these tools and it would help if your POM had a goal to clarify what it’s building either a .war or .jar (.war in this case right?) [ERROR] No goals have been specified for this build. You must specify a valid lifecycle phase or a goal in the format : or :[:]:. Available lifecycle phases are: validate, initialize, generate-sources, process-sources, generate-resources, process-resources, compile,… Read more »

Erik
10 years ago
Reply to  Erik

Does using H2 or HSQL db require some sort of configuration file?

Gregor
10 years ago
Reply to  Erik

running “mvn package” should build a deployable war.
you should also be able to run it using “mvn tomcat7:run” using the http://tomcat.apache.org/maven-plugin-2.0/tomcat7-maven-plugin/plugin-info.html

the errors you are getting are maybe due to the gradle configuration. make sure you can compile a web archive (war). i never used gradle though

Erik
10 years ago
Reply to  Gregor

Im successfully compiling a war in gradle, it see no errors in Gradle OR Eclipse. But when deployed to Tomcat it’s not seeing something. My next strategy is to try a different example from Spring-Data-Rest (Rest-Bucks) and see if I missed something

Vidhya
Vidhya
10 years ago

Nice article. i can build and deploy successfully.

Could you please tell me where this data persist

SDR
SDR
10 years ago

thanks Gregor for writing up this tutorial, it has been very helpful to get started with Spring data rest.
I have noticed one issue here: ID for book is String (isbn) yet, the BookRepository is declared as Long. Anyway, that was a easy fix. The part that I am having trouble with is adding book with author href. I am getting the following error:

message: “Could not read JSON: (was java.lang.NullPointerException) (through reference chain: com.khan.sdr2.model.Book[“authors”]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.khan.sdr2.model.Book[“authors”])”

any idea ?

Gowtham
Gowtham
10 years ago

Hi, How to include validation for the fields using spring data rest. I have tried many ways all in vain. Any tutorials to do that?

Alexis
Alexis
9 years ago

Hi is it possible to create a new author with his books in only ONE POST?

Edgar
Edgar
9 years ago

How do I can configure the methods save, update and delete to only ROLE_ADMIN can access them? My repository its like:

public interface OrganizationRepository extends CrudRepository {

}

Kandeepa
Kandeepa
7 years ago

Is it possible to add a new book and an author in a single post request? i am having a projection used to display data from multiple interrelated entities but so far finding it hard to save to different entities with single post.

Back to top button