Tuesday, November 20, 2012

Spring MVC 3.2 with Spring Data Rest (Part 2)

Review

In the previous section, we have updated our app's configuration, so that our repositories are exposed as RESTful endpoints. We've also applied the HATEOAS methodology which "serves to decouple client and server in a way that allows the server to evolve functionality independently" (Wikipedia). In this section, we will build and run the application using Maven, demonstrate how to import the project in Eclipse, and run a series of manual tests to examine the RESTful endpoints.

Table of Contents

Part 1: Configuration
  • Update the pom.xml
  • Update the web.xml
  • Update Spring configurations
  • Update the repositories
Part 2: Running the application

What is HATEOAS?

HATEOAS, an abbreviation for Hypermedia as the Engine of Application State, is a constraint of the REST application architecture that distinguishes it from most other network application architectures. The principle is that a client interacts with a network application entirely through hypermedia provided dynamically by application servers. A REST client needs no prior knowledge about how to interact with any particular application or server beyond a generic understanding of hypermedia. Contrast this with e.g. a service-oriented architecture (SOA), where clients and servers interact through a fixed interface shared through documentation or an interface description language (IDL).

The HATEOAS constraint serves to decouple client and server in a way that allows the server to evolve functionality independently.

Source: http://en.wikipedia.org/wiki/HATEOAS


Running the Application

Access the source code

To download the source code, please visit the project's Github repository (click here)

Preparing the data source

  1. Run MySQL (install one if you don't have one yet)
  2. Create a new database:
    spring_data_rest_tutorial
  3. Import the following file which is included in the source code under the src/main/resources folder:
    spring_data_rest_tutorial.sql

Building with Maven

  1. Ensure Maven is installed
  2. Open a command window (Windows) or a terminal (Linux/Mac)
  3. Run the following command:
    mvn tomcat:run
  4. You should see the following output:
    [INFO] Scanning for projects...
    [INFO] Searching repository for plugin with prefix: 'tomcat'.
    [INFO]                                                                         
    [INFO] --------------------------------------------------------------
    [INFO] Building spring-data-rest-tutorial Maven Webapp 0.0.1-SNAPSHOT
    [INFO] --------------------------------------------------------------
    [INFO]
    [INFO] Preparing tomcat:run
    [INFO] [apt:process {execution: default}]
    [INFO] [resources:resources {execution: default-resources}]
    [INFO] [tomcat:run {execution: default-cli}]
    [INFO] Running war on http://localhost:8080/spring-data-rest-tutorial
    Nov 20, 2012 8:01:45 PM org.apache.catalina.startup.Embedded start
    INFO: Starting tomcat server
    Nov 20, 2012 8:01:45 PM org.apache.catalina.core.StandardEngine start
    INFO: Starting Servlet Engine: Apache Tomcat/6.0.29
    Nov 20, 2012 8:01:46 PM org.apache.catalina.core.ApplicationContext log
    INFO: Initializing Spring root WebApplicationContext
    Nov 20, 2012 8:02:01 PM org.apache.catalina.core.ApplicationContext log
    INFO: Initializing Spring FrameworkServlet 'rest-exporter'
    Nov 20, 2012 8:02:03 PM org.apache.coyote.http11.Http11Protocol init
    INFO: Initializing Coyote HTTP/1.1 on http-8080
    Nov 20, 2012 8:02:03 PM org.apache.coyote.http11.Http11Protocol start
    INFO: Starting Coyote HTTP/1.1 on http-8080
    
  5. Note: If the project will not build due to missing repositories, please enable the repositories section in the pom.xml!

Access the grid page

This displays a grid that allows us to experiment with the data visually.
  1. Follow the steps with Building with Maven
  2. Open a browser
  3. Enter the following URL (8080 is the default port for Tomcat):
    http://localhost:8080/spring-data-rest-tutorial/

Access the RESTful entry endpoint

We've declared entry endpoint in spring-data-rest.xml and web.xml files.
  1. Follow the steps with Building with Maven
  2. Open a browser
  3. Enter the following URL:
    http://localhost:8080/spring-data-rest-tutorial/api/

    This gives the following result:
    {
      "links" : [ {
        "rel" : "role",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/role"
      }, {
        "rel" : "user",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user"
      } ],
      "content" : [ ]
    }
    


Access the User endpoint

  1. Enter the following URL:
    http://localhost:8080/spring-data-rest-tutorial/api/user

    This gives the following result:
    {
      "links" : [ {
        "rel" : "user.search",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/search"
      } ],
      "content" : [ {
        "links" : [ {
          "rel" : "self",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/1"
        }, {
          "rel" : "user.User.role",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/1/role"
        } ],
        "lastName" : "Smith",
        "username" : "john",
        "firstName" : "John",
        "password" : "21232f297a57a5a743894a0e4a801fc3"
      }, {
        "links" : [ {
          "rel" : "self",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/2"
        }, {
          "rel" : "user.User.role",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/2/role"
        } ],
        "lastName" : "Adams",
        "username" : "jane",
        "firstName" : "Jane",
        "password" : "ee11cbb19052e40b07aac0ca060c23ee"
      },
      ... (TRUNCATED)
      ... (TRUNCATED)
      ... (TRUNCATED)
      {
        "links" : [ {
          "rel" : "self",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/13"
        }, {
          "rel" : "user.User.role",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/13/role"
        } ],
        "lastName" : "Zeigler",
        "username" : "edward",
        "firstName" : "Edward",
        "password" : "mncmksk"
      } ],
      "page" : {
        "size" : 20,
        "totalElements" : 13,
        "totalPages" : 1,
        "number" : 1
      }
    }
    


Access the User endpoint with a limit

  1. Enter the following URL:
    http://localhost:8080/spring-data-rest-tutorial/api/user?limit=2

    This gives the following result:
    {
      "links" : [ {
        "rel" : "user.next",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user?page=2&limit=2"
      }, {
        "rel" : "user.search",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/search"
      } ],
      "content" : [ {
        "links" : [ {
          "rel" : "self",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/1"
        }, {
          "rel" : "user.User.role",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/1/role"
        } ],
        "lastName" : "Smith",
        "username" : "john",
        "firstName" : "John",
        "password" : "21232f297a57a5a743894a0e4a801fc3"
      }, {
        "links" : [ {
          "rel" : "self",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/2"
        }, {
          "rel" : "user.User.role",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/2/role"
        } ],
        "lastName" : "Adams",
        "username" : "jane",
        "firstName" : "Jane",
        "password" : "ee11cbb19052e40b07aac0ca060c23ee"
      } ],
      "page" : {
        "size" : 2,
        "totalElements" : 12,
        "totalPages" : 6,
        "number" : 1
      }
    }
    

    Notice the result also provided the endpoint for search and each person's record.

Let's do a search

  1. Open a browser
  2. Enter the following URL:
    http://localhost:8080/spring-data-rest-tutorial/api/user/search

    This gives the following result:
    {
      "links" : [ {
        "rel" : "user.findByFirstNameLike",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/search/findByFirstNameLike"
      }, {
        "rel" : "user.findByUsernameLike",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/search/findByUsernameLike"
      }, {
        "rel" : "user.findByFirstNameLikeAndLastNameLike",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/search/findByFirstNameLikeAndLastNameLike"
      }, {
        "rel" : "user.findByUsername",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/search/findByUsername"
      }, {
        "rel" : "user.findByLastNameLike",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/search/findByLastNameLike"
      }, {
        "rel" : "user.findByRole",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/search/findByRole"
      } ],
      "content" : [ ]
    }
    

    This exposes all the queries we declared in the UserRepository interface.
  3. Let's try searching by username.
  4. Enter the following URL:
    http://localhost:8080/spring-data-rest-tutorial/api/user/search/findByUsername?username=john

    This gives the following result:
    {
      "links" : [ ],
      "content" : [ {
        "links" : [ {
          "rel" : "self",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/1"
        }, {
          "rel" : "user.User.role",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/1/role"
        } ],
        "lastName" : "Smith",
        "username" : "john",
        "firstName" : "John",
        "password" : "21232f297a57a5a743894a0e4a801fc3"
      } ]
    }
    



Delete a record

You are required to have curl installed.
  1. Open a command line.
  2. Enter the following command:
    curl -v -X DELETE http://localhost:8080/spring-data-rest-tutorial/api/user/3

    This gives the following output:
    * About to connect() to localhost port 8080 (#0)
    *   Trying 127.0.0.1... connected
    * Connected to localhost (127.0.0.1) port 8080 (#0)
    > DELETE /spring-data-rest-tutorial/api/user/3 HTTP/1.1
    > User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8r zlib/1.2.3
    > Host: localhost:8080
    > Accept: */*
    > 
    < HTTP/1.1 204 No Content
    < Server: Apache-Coyote/1.1
    < Date: Mon, 19 Nov 2012 14:32:05 GMT
    < 
    * Connection #0 to host localhost left intact
    * Closing connection #0
    
    If you check the records, notice that User with id 3 has been deleted.

Add a record

You are required to have curl installed.
  1. Open a command line.
  2. Enter the following command:
    curl -v -d '{"username":"homer", "firstName":"Homer", "lastName":"Simpson", "password":"12345678"}' -H "Content-Type: application/json" http://localhost:8080/spring-data-rest-tutorial/api/user

    Under Windows use the following command instead:
    curl -v -d "{\"username\":\"homer\", \"firstName\":\"Homer\", \"lastName\":\"Simpson\", \"password\":\"12345678\"}" -H "Content-Type: application/json" http://localhost:8080/spring-data-rest-tutorial/api/user
    

    This adds a User record and gives the following output:
    * About to connect() to localhost port 8080 (#0)
    *   Trying 127.0.0.1... connected
    > POST /spring-data-rest-tutorial/api/user HTTP/1.1
    > User-Agent: curl/7.22.0 (i386-pc-win32) libcurl/7.22.0 OpenSSL/1.0.0e zlib/1.2
    .5
    > Host: localhost:8080
    > Accept: */*
    > Content-Type: application/json
    > Content-Length: 86
    >
    * upload completely sent off: 86out of 86 bytes
    < HTTP/1.1 201 Created
    < Server: Apache-Coyote/1.1
    < Location: http://localhost:8080/spring-data-rest-tutorial/api/user/14
    < Content-Type: application/octet-stream
    < Content-Length: 0
    < Date: Tue, 20 Nov 2012 04:58:56 GMT
    <
    * Connection #0 to host localhost left intact
    * Closing connection #0
    
  3. However, a Role has not been associated with this User record. We have to manually create and associate one. Open a command line.
  4. Enter the following command:
    curl -v -d '{"role":"1","user": {"rel": "user","href": "http://localhost:8080/spring-data-rest-tutorial/api/user/14"}}' -H "Content-Type: application/json" http://localhost:8080/spring-data-rest-tutorial/api/role

    Under Windows use the following command instead:
    curl -v -d "{\"role\":\"1\",\"user\": {\"rel\": \"user\",\"href\": \"http://localhost:8080/spring-data-rest-tutorial/api/user/14\"}}" -H "Content-Type: application/json" http://localhost:8080/spring-data-rest-tutorial/api/role
    

    This adds a Role record and gives the following output:
    * About to connect() to localhost port 8080 (#0)
    *   Trying 127.0.0.1... connected
    > POST /spring-data-rest-tutorial/api/role HTTP/1.1
    > User-Agent: curl/7.22.0 (i386-pc-win32) libcurl/7.22.0 OpenSSL/1.0.0e zlib/1.2
    .5
    > Host: localhost:8080
    > Accept: */*
    > Content-Type: application/json
    > Content-Length: 106
    >
    * upload completely sent off: 106out of 106 bytes
    < HTTP/1.1 201 Created
    < Server: Apache-Coyote/1.1
    < Location: http://localhost:8080/spring-data-rest-tutorial/api/role/14
    < Content-Type: application/octet-stream
    < Content-Length: 0
    < Date: Tue, 20 Nov 2012 04:59:57 GMT
    <
    * Connection #0 to host localhost left intact
    * Closing connection #0
    
  5. Examine the output and we should see the new record:
    http://localhost:8080/spring-data-rest-tutorial/api/user/14
    
    {
        "links" : [ {
          "rel" : "self",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/14"
        }, {
          "rel" : "user.User.role",
          "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/14/role"
        } ],
        "lastName" : "Simpson",
        "username" : "homer",
        "firstName" : "Homer",
        "password" : "12345678"
      }
    
    http://localhost:8080/spring-data-rest-tutorial/api/user/14/role
    {
      "links" : [ {
        "rel" : "self",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/role/14"
      }, {
        "rel" : "role.Role.user",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/role/14/user"
      }, {
        "rel" : "user.User.role",
        "href" : "http://localhost:8080/spring-data-rest-tutorial/api/user/14/role"
      } ],
      "role" : 1
    }
    
  6. Examine the grid and we shall see the new record as well:


Note: You can do more with Spring Data Rest. I suggest reading the docs further for more info.

Import the project in Eclipse

  1. Ensure Maven is installed
  2. Open a command window (Windows) or a terminal (Linux/Mac)
  3. Run the following command:
    mvn eclipse:eclipse -Dwtpversion=2.0
  4. You should see the following output:
    [INFO] Scanning for projects...
    [INFO]                                                                         
    [INFO] --------------------------------------------------
    [INFO] Building spring-data-rest-tutorial Maven Webapp 0.0.1-SNAPSHOT
    [INFO] ---------------------------------------------------
    [INFO] 
    [INFO] Adding support for WTP version 2.0.
    [INFO] 
    [INFO] --------------------------------------------------
    [INFO] BUILD SUCCESS
    [INFO] --------------------------------------------------
    
    This command will add the following files to your project:
    .classpath
    .project
    .settings
    target
    You may have to enable "show hidden files" in your file explorer to view them
  5. Open Eclipse and import the project

Conclusion

That's it! We've have successfully updated our Spring MVC application and exposed our repositories as RESTful endpoints using Spring Data Rest. We've also demonstrated how to access our application using pure hyperlinks with HATEOAS methodology.

I hope you've enjoyed this tutorial. Don't forget to check my other tutorials at the Tutorials section.

Revision History


Revision Date Description
1 Nov 20 2012 Uploaded tutorial and Github repository

StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring MVC 3.2 with Spring Data Rest (Part 2) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

8 comments:

  1. Very nice tutorial, I will give a try, since I just discovered such new feature of the Spring Framework and I was thinking to use it!

    ReplyDelete
  2. Hi,

    When I run this example in eclipse I got this error :

    Apr 5, 2013 9:03:02 PM org.apache.catalina.core.StandardContext listenerStart
    SEVERE: Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener
    java.lang.NoClassDefFoundError: org/springframework/beans/factory/NoUniqueBeanDefinitionException
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2436)
    at java.lang.Class.getDeclaredMethods(Class.java:1793)
    at org.springframework.core.type.StandardAnnotationMetadata.hasAnnotatedMethods(StandardAnnotationMetadata.java:159)
    at org.springframework.context.annotation.ConfigurationClassUtils.isLiteConfigurationCandidate(ConfigurationClassUtils.java:104)
    at org.springframework.context.annotation.ConfigurationClassUtils.checkConfigurationClassCandidate(ConfigurationClassUtils.java:87)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:253)
    at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:223)
    at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:630)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:461)
    at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:383)
    at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:283)
    at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:112)
    at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4791)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5285)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
    at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:901)
    at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:877)
    at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:618)
    at org.apache.catalina.startup.HostConfig.deployDescriptor(HostConfig.java:650)
    at org.apache.catalina.startup.HostConfig$DeployDescriptor.run(HostConfig.java:1582)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:439)
    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
    at java.util.concurrent.FutureTask.run(FutureTask.java:138)
    at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918)
    at java.lang.Thread.run(Thread.java:680)
    Caused by: java.lang.ClassNotFoundException: org.springframework.beans.factory.NoUniqueBeanDefinitionException
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1714)
    at org.apache.catalina.loader.WebappClassLoader.loadClass(WebappClassLoader.java:1559)
    ... 27 more

    ReplyDelete
  3. Excellent tutorial - User from India.

    ReplyDelete
  4. Thanks so much!. It works well on MySQL but when I switch to MsSQL i get the following errors:

    Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessResourceUsageException: Incorrect syntax near 'limit'.; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: Incorrect syntax near 'limit'.

    Please how can I run this on MsSQL.

    thanks

    ReplyDelete
  5. Figured it out! I only needed to change the dialect to SQLServer and it worked!!!

    ReplyDelete
  6. I was very pleased to find this site.I wanted to thank you for this great read!! I definitely enjoying every little bit of it and I have you bookmarked to check out new stuff you post. This is the un-official subreddit for Instagram viewer- Share your posts, ask questions and get feedback on your account. Come join our great page.

    ReplyDelete