Show Navigation

Building a REST application with GORM and Hibernate 5

This guide will demonstrate how you can use Grails, GORM and Hibernate 5 to build a REST application

Authors: Graeme Rocher

Grails Version: 4.0.1

1 Grails Training

Grails Training - Developed and delivered by the folks who created and actively maintain the Grails framework!.

2 Getting Started

In this guide you are going to build a Grails application that uses GORM for Hibernate to access a SQL database and produce a JSON response in a RESTful manner.

2.1 What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

2.2 How to complete the guide

To get started do the following:

or

The Grails guides repositories contain two folders:

  • initial Initial project. Often a simple Grails app with some additional code to give you a head-start.

  • complete A completed example. It is the result of working through the steps presented by the guide and applying those changes to the initial folder.

To complete the guide, go to the initial folder

  • cd into grails-guides/rest-hibernate/initial

and follow the instructions in the next sections.

You can go right to the completed example if you cd into grails-guides/rest-hibernate/complete

Alternatively, if you already have Grails 4.0.1 installed then you can create a new application using the following command in a Terminal window:

$ grails create-app hibernate-example --profile rest-api
$ cd hibernate-example

When the create-app command completes, Grails will create a hibernate-example directory with an application configured to create a REST application by default (using the rest-api profile) and configured to use the hibernate feature.

3 Writing the Application

Now you are ready to start writing the application.

3.1 Configure the Application

By default Grails will create an application that uses an H2 in-memory SQL database, thus allowing your to develop Grails without the need to setup a database.

However, if you do want to configure a database then you can do so by adding a runtime dependency to the corresponding JDBC driver to build.gradle. For example, for MySQL:

build.gradle
dependencies {
    ...
    runtime 'mysql:mysql-connector-java:6.0.5'
}

And then altering the configuration found in grails-app/conf/application.yml. The default global configuration can be seen below:

grails-app/conf/application.yml
dataSource:
    pooled: true
    jmxExport: true
    driverClassName: org.h2.Driver
    username: sa
    password: ''

You will notice that there is environment specific configuration:

grails-app/conf/application.yml
environments:
    development:
        dataSource:
            dbCreate: create-drop
            url: jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE
    test:
        dataSource:
            dbCreate: update
            url: jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE

Grails comes with three built in environments development, test and production corresponding to the different environments that the application will run in.

To alter this for MySQL you should specify the driver class name, username and password. In order to change it only for the development environment, you can specify it under the development block:

grails-app/conf/application.yml
development:
    dataSource:
        driverClassName: com.mysql.jdbc.Driver
        dbCreate: create-drop
        url: jdbc:mysql://localhost/test
        username: xxxxx
        password: yyyyy

3.2 Create a Domain Class

Each table in the SQL database is represented by a GORM domain class.

To create a domain class you can use the create-domain-class CLI command from a Terminal window within the root of your project:

$ ./grailsw create-domain-class Product
| Created grails-app/domain/hibernate/example/Product.groovy
| Created src/test/groovy/hibernate/example/ProductSpec.groovy

Alternatively, you can equally create a domain class with your favourite text editor or IDE.

A domain class is a simple Groovy class and you can represent each column of a table in the database using a property. For example:

grails-app/domain/hibernate/example/Product.groovy
package hibernate.example

import grails.compiler.GrailsCompileStatic

@GrailsCompileStatic
class Product {

    String name
    Double price
}

Each domain class defined in the application will be compiled to implement the GormEntity trait. If you prefer you can define this explicitly:

grails-app/domain/hibernate/example/Product.groovy
import org.grails.datastore.gorm.*

class Product implements GormEntity<Product> {
    ..
}

3.3 Apply Domain Constraints

If you wish to define validation constraints on properties defined in a GORM domain class, you can do so using the constraints property:

grails-app/domain/hibernate/example/Product.groovy
static constraints = {
    name blank:false
    price range:0.0..1000.00
}

The above example applies two constraints:

  • The name property is constrained so that it cannot be a blank string.

  • The price property is constrained so that it must be greater than 0 and less than a thousand using the range constraint.

To verify these constraints work to our expectation you can write a test. If you used the create-domain-class a test called src/test/groovy/hibernate/example/ProductSpec.groovy was generated for you already. Otherwise simply creating an appropriately named test in src/test/groovy using your IDE or text editor will suffice.

To effectively test interactions with Hibernate in a unit test it is recommended you use grails.test.hibernate.HibernateSpec which you can use combined with the H2 in-memory database.

To achieve this first modify the imports of the ProductSpec class as follows:

src/test/groovy/hibernate/example/ProductSpec.groovy
import grails.test.hibernate.HibernateSpec

Then ensure the ProductSpec extends the HibernateSpec base class:

src/test/groovy/hibernate/example/ProductSpec.groovy
@SuppressWarnings(['MethodName', 'DuplicateNumberLiteral'])
class ProductSpec extends HibernateSpec {

The HibernateSpec super class will wrap each test method in a transaction that is rolled back at the end of the test ensuring cleanup between tests.

With the test prepared you are ready to write a test. Tests in Grails are written with Spock Framework, which allows you to define much more readable tests including having spaces in the name:

src/test/groovy/hibernate/example/ProductSpec.groovy
void 'test domain class validation'() {
    ...
}

First lets write a test for the invalid case. You can do so using Spock when and then blocks to make the test more readable:

src/test/groovy/hibernate/example/ProductSpec.groovy
when: 'A domain class is saved with invalid data'
Product product = new Product(name: '', price: -2.0d)
product.save()

then: 'There were errors and the data was not saved'
product.hasErrors()
product.errors.getFieldError('price')
product.errors.getFieldError('name')
Product.count() == 0

The above example sets the name to a blank value and the price to a negative value and attempts to save the instance using the save() method.

The then block asserts that the object is invalid and was not saved.

To write a test for the valid case populate some valid data and try and assert that the object was saved:

src/test/groovy/hibernate/example/ProductSpec.groovy
when: 'A valid domain is saved'
product.name = 'Banana'
product.price = 2.15d
product.save()

then: 'The product was saved successfully'
Product.count() == 1
Product.first().price == 2.15d

All done! Now to run the test you can run ./gradlew check from a terminal window or run the test within your IDE if the IDE supports it:

$ ./gradlew check

3.4 Create a Controller

With the data model defined now it time to write a controller.

The quickest way to do that is with the create-restful-controller command from a terminal window:

$ ./grailsw create-restful-controller hibernate.example.Product
| Created grails-app/controllers/hibernate/example/ProductController.groovy

However, you can also simply create the controller with your favourite text editor or IDE.

The contents of the controller should look like the following:

grails-app/controllers/hibernate/example/ProductController.groovy
import groovy.transform.CompileStatic

@CompileStatic
class ProductController extends RestfulController {
    static responseFormats = ['json', 'xml']
    ProductController() {
        super(Product)
    }
    ProductService productService
}

The RestfulController super class will implement all the necessary operations to perform the common REST verbs such as GET, POST, PUT and DELETE. If is you wish to override or forbid certain verbs you can override the equivalent method from the super (for example the delete method for DELETE) to return an alternative or forbidden response.

3.5 Map the Controller to a URI

By default the controller will be exposed under the /product URI. This is due to the default grails-app/conf/hibernate/example/UrlMappings.groovy class:

grails-app/controllers/hibernate/example/UrlMappings.groovy
delete "/$controller/$id(.$format)?"(action: 'delete')
get "/$controller(.$format)?"(action: 'index')
get "/$controller/$id(.$format)?"(action: 'show')
post "/$controller(.$format)?"(action: 'save')
put "/$controller/$id(.$format)?"(action: 'update')
patch "/$controller/$id(.$format)?"(action: 'patch')

As you can see above each HTTP verb is mapped such that the controller name is established from the URI itself using the $controller syntax.

If you wish to use another name or be explicit about the URI used you can define an additional mapping using the resources mapping:

grails-app/controllers/hibernate/example/UrlMappings.groovy
"/products"(resources:"product")

In this case the controller will be mapped to /products instead of /product.

3.6 Implement a Search Endpoint

If you wish to add an additional endpoint to your REST API then it is simply a matter of implementing the corresponding action.

For example, say you wanted to be able to search for products using the /products/search URI and a query. To do so the first step is to implement a search action in the controller:

grails-app/controllers/hibernate/example/ProductController.groovy
def search(String q, Integer max ) { (1)
    if (q) {
        respond productService.findByNameLike("%${q}%".toString(), [max: Math.min( max ?: 10, 100)]) (3)
    }
    else {
        respond([]) (4)
    }
}
1 An action called search is defined that takes a query parameter, called q, and a max parameter
2 A where query is executed that uses a SQL like query.
3 The respond method is used to respond with a list of results
4 For the case where no query is specified we respond with an empty list

With the action written you now need to expose the /products/search endoint by defining the appropriate mapping in grails-app/conf/hibernate/example/UrlMappings.groovy:

grails-app/controllers/hibernate/example/UrlMappings.groovy
'/products'(resources: 'product') {
    collection {
        '/search'(controller: 'product', action: 'search')
    }
}

The above example uses the collection method to nest URIs directly underneath the /products URI (for example /product/search) instead of nesting it below the resource identifier (for example /product/1/search).

See the Grails user guide on Mapping REST Resources for more information on controlling how URIs map to controllers.

3.7 Testing the Search Endpoint

To write a unit test for the search action your can use the create-unit-test command of the CLI to create one:

$ ./grailsw create-unit-test hibernate.example.ProductController

Or alternatively just create a src/test/groovy/hibernate/example/ProductControllerSpec.groovy file using your favourite text editor or IDE.

Like the previously implemented ProductSpec it should extend HibernateSpec, but this time the TestFor definition should be for ProductController:

src/test/groovy/hibernate/example/ProductControllerSpec.groovy
@SuppressWarnings('MethodName')
class ProductControllerSpec extends HibernateSpec implements ControllerUnitTest<ProductController> {
    ...
}

In addition, the test should define a doWithSpring block to enable JSON views:

src/test/groovy/hibernate/example/ProductControllerSpec.groovy
static doWithSpring = {
    jsonSmartViewResolver(JsonViewResolver)
}

The doWithSpring block is used to register additional Spring configuration (beans) for the context of the test. In this case we wish to test the controller responds with JSON views, so a jsonSmartViewResolver bean is registered.

Finally, we are ready to implement the test and can perform a search by executing the search method, passing appropriate arguments and verifying the JSON written to the response:

src/test/groovy/hibernate/example/ProductControllerSpec.groovy
    void 'test the search action finds results'() {
        given:
        controller.productService = Stub(ProductService) {
            findByNameLike(_, _) >> [new Product(name: 'Apple', price: 2.0)]
        }
        when: 'A query is executed that finds results'
        controller.search('pp', 10)

        then: 'The response is correct'
        response.json.size() == 1
        response.json[0].name == 'Apple'
    }
}

As you can see from the example above, we are asserting the value of the json property of the response. The resulting JSON rendered by Grails looks like:

[{"id":1,"name":"Apple","price":2.0}]

Let’s take a look at how we can customize this JSON.

3.8 Customizing the JSON Output

Grails uses JSON Views to represent render JSON responses. The idea being to continue the philosophy of separating the controller logic from the view logic.

The default view rendered if no specific view is found is grails-app/views/object/_object.gson:

grails-app/views/object/_object.gson
import groovy.transform.*

@Field Object object

json g.render(object)

The default _object.gson view simply uses the g.render(..) method to automatically produce a JSON representation of the object.

If you want to alter the output of the JSON the way that is done in Grails is by creating a view. You can generate a starting point with the generate-views command of the CLI:

./grailsw generate-views hibernate.example.Product
| Rendered template index.gson to destination grails-app/views/product/index.gson
| Rendered template show.gson to destination grails-app/views/product/show.gson
| Rendered template _domain.gson to destination grails-app/views/product/_product.gson

As you can see 3 templates were generated:

  • grails-app/views/product/index.gson - This view will be used when a collection (typically a list of results from GORM) is rendered via the respond method in a controller.

  • grails-app/views/product/show.gson - This view will be rendered when a single Product instance is rendered via the respond method in a controller.

  • grails-app/views/product/_product.gson - This is the template used by both the index.gson and show.gson views to actually display the data.

The contents of the _product.gson by default look like:

grails-app/views/product/_product.gson
import hibernate.example.Product

model {
    Product product
}

json g.render(product)

The call to g.render(product) outputs all properties.

However, the json property is an instance of Groovy’s StreamingJsonBuilder and you can use it to alter the output as per your needs.

For example:

grails-app/views/product/_product.gson
import hibernate.example.Product

model {
        Product product
}

Currency currency = locale?.country ? Currency.getInstance(locale) : Currency.getInstance('USD')
json {
    id product.id
    name product.name
    price "${currency.symbol}${product.price}"
}

In this trivialized example, we output the currency symbol based on the user’s locale. Now the resulting JSON will look like:

[{id:1,"name":"Apple","price":"$2.0"}]
JSON Views are very flexible, for more information on customizing the output to your needs see the documentation.

4 Running the Application

To run the application use the ./gradlew bootRun command which will start the application on port 8080.

4.1 Creating Data with POST

Once the application has started up you can create a Product instance using your preferred HTTP client. In the following examples we will be using the curl.

To submit a POST request use the following in a terminal window:

$ curl -i -H "Content-Type:application/json" -X POST localhost:8080/products -d '{"name":"Orange","price":2.0}'

The resulting output will be:

HTTP/1.1 201
X-Application-Context: application:development
Location: http://localhost:8080/products/2
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 23 Nov 2016 08:46:40 GMT

{"name":"Orange","price":"$2.0"}

As you can see an HTTP 201 status code is returned.

4.2 Reading Data with GET

You can read all of the Product instances back using a GET request:

$ curl -i  localhost:8080/products

Or read only a single instance by id:

$ curl -i  localhost:8080/products/1

Which will return:

HTTP/1.1 200
X-Application-Context: application:development
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 23 Nov 2016 08:50:58 GMT

{"id":1,"name":"Orange","price":"$2.0"}

4.3 Updating Data with PUT

To update data you can use a PUT request with the id and in the URI and data you want to change:

$ curl -i -H "Content-Type:application/json" -X PUT localhost:8080/products/1 -d '{"price":3.0}'

In this case the resulting output will be:

HTTP/1.1 200
X-Application-Context: application:development
Location: http://localhost:8080/products/1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 23 Nov 2016 08:52:14 GMT

{"id":1,"name":"Orange","price":"$3.0"}

If you were to attempt update the data with an invalid value:

$ curl -i -H "Content-Type:application/json" -X PUT localhost:8080/products/1 -d '{"price":-2.0}'

Then an error response will be received:

HTTP/1.1 422
X-Application-Context: application:development
Location: http://localhost:8080/products/1
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 23 Nov 2016 08:54:25 GMT

{"message":"Property [price] of class [class hibernate.example.Product] with value [-2] does not fall within the valid range from [0] to [1,000]","path":"","_links":{"self":{"href":"http://localhost:8080/products/1"}}}
The error response is rendered by the grails-app/views/error.gson view and the messages are obtained from the message bundles located at grails-app/i18n

4.4 Deleting Data with DELETE

To delete a Product simply send a DELETE request:

$ curl -i -X DELETE localhost:8080/products/1

If deleting the instance was successful the output will be:

HTTP/1.1 204
X-Application-Context: application:development
Content-Type: application/json;charset=UTF-8
Date: Wed, 23 Nov 2016 08:57:27 GMT

Congratulations! You have built your first REST application with Grails, GORM and Hibernate 5!

Remember you can obtain the source code for the completed examples using the links on the right.

5 Do you need help with Grails?

Object Computing, Inc. (OCI) sponsored the creation of this Guide. A variety of consulting and support services are available.

OCI is Home to Grails

Meet the Team