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: 3.3.0
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.7 or greater installed with
JAVA_HOME
configured appropriately
2.2 How to complete the guide
To get started do the following:
-
Download and unzip the source
or
-
Clone the Git repository:
git clone https://github.com/grails-guides/rest-hibernate.git
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 theinitial
folder.
To complete the guide, go to the initial
folder
-
cd
intograils-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 3.3.0 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:
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:
dataSource:
pooled: true
jmxExport: true
driverClassName: org.h2.Driver
username: sa
password: ''
You will notice that there is environment specific configuration:
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:
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:
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:
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:
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 therange
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:
import grails.test.hibernate.HibernateSpec
Then ensure the ProductSpec
extends the HibernateSpec
base class:
@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:
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:
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:
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:
import groovy.transform.CompileStatic
@CompileStatic
class ProductController extends RestfulController {
static responseFormats = ['json', 'xml']
ProductController() {
super(Product)
}
}
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:
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:
"/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:
def search(String q, Integer max ) { (1)
if (q) {
def query = Product.where { (2)
name ==~ "%${q}%"
}
respond query.list(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
:
'/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
:
@SuppressWarnings('MethodName')
class ProductControllerSpec extends HibernateSpec implements ControllerUnitTest<ProductController> {
...
}
In addition, the test should define a doWithSpring
block to enable JSON views:
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.
Next in order to prepare the test we setup some test data using the setup
method:
void setup() {
Product.saveAll(
new Product(name: 'Apple', price: 2.0),
new Product(name: 'Orange', price: 3.0),
new Product(name: 'Banana', price: 1.0),
new Product(name: 'Cake', price: 4.0)
)
}
The setup
method uses the saveAll
method to save multiple domain classes to serve as test data.
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:
void 'test the search action finds results'() {
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
:
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 therespond
method in a controller. -
grails-app/views/product/show.gson
- This view will be rendered when a singleProduct
instance is rendered via therespond
method in a controller. -
grails-app/views/product/_product.gson
- This is the template used by both theindex.gson
andshow.gson
views to actually display the data.
The contents of the _product.gson
by default look like:
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:
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. |