====== Domain ======
In Grails or Spring, ''Domain'' is an plain old Java object (POJO) that do the mapping between your app, and the database. In Grails by conversion, we put the domain objects under the directory ''/grails-ap/domain/YOUR_PACKAGE_NAME/'', and Grails will do the rest for us.
===== Create a Domain Object =====
There are two ways to do so, and they are listed as follow:
==== In Grails Console ====
In Grails console, type ''create-domain-class YOUR_DOMAIN_NAME'', and a domain skeleton would be created.
grails> create-domain-class Book
| Created grails-app/domain/hello/Book.groovy
| Created src/test/groovy/hello/BookSpec.groovy
An domain skeleton created by the command.
class Book {
static constraints = {
}
}
==== In Intellij IDEA Ultimate ====
Right click the domain directory in the file viewer, select New, and than select Grails Domain Class. Input the name of your domain class, and a class skeleton like the above will be created.
{{ :domain_1.png?direct&400 |}}
===== The Mapping =====
==== Basic ====
Let's focus on creating a POJO first. Assume our book object has a ''title'', an ''author'', and a release date. We would do:
class Book {
String title
String author
@BindingFormat('yyyy-MM-dd')
Date releaseDate
static constraints = {
}
}
Just like how we create a POJO. The ''@BindingFormat('yyyy-MM-dd')'' is usually when we try to map a HTTP request form data to this object. Nothing special. Run the app, and goto the path ''http://localhost:8080/dbconsole/''. The default JDBC URL should be ''jdbc:h2:mem:devDb'' (set inside application.yml). You should able to see the newly created domain has a corresponding table in your database.
{{ :domain_2.png?direct&400 |}}
Note that we did not added ID, and VERSION fields, grails added these for us. The VERSION field are useful for hibernate optimistic locking, or some sort of version console for race condition in your UI for multiple users.
==== Creating Instance, and Save it ====
Just creating the Jave object, and call save() on it.
class BookController {
def index() {
render("index")
}
def add(){
Book book = new Book(title: "ABC", author: "Tom Lee", releaseDate: "2010-03-01")
book.save()
render("Book is created")
}
}
{{ :domain_3.png?direct&200 |}}
=== Save might Fail Silently ===
NOTE that the **save() function fail silently**. Instead of crashing or throwing exception, it ignore it, and continue to run your code. To illustrate it, try to remove title field for the above code, and restart the app, and rerun this code.
Book book = new Book(author: "Tom Lee", releaseDate: "2010-03-01")
You will still see the page said ''Book is created'', but in the dbconsole, you see nothing is created.
There are two ways to solve this problem, and it is personal choice of which one to user:
**Validate It First**
def add() {
Book book = new Book(author: "Tom Lee", releaseDate: "2010-03-01")
if (book.validate()) {
book.save()
render("Book is created")
} else {
render("Book cannot be created with errors:
" + book.errors)
}
}
**try catch, failOnError:true **
def add() {
Book book = new Book(author: "Tom Lee", releaseDate: "2010-03-01")
try {
book.save(failOnError: true)
render("Book is created")
} catch (Exception e) {
render("Book cannot be created with errors:
" + book.errors)
}
}
**The Error Output**
Readable by a programmer, but sure non-readable by a regular user.
Book cannot be created with errors:
org.grails.datastore.mapping.validation.ValidationErrors: 1 errors Field error in object 'hello2.Book' on field 'title': rejected value [null]; codes [hello2.Book.title.nullable.error.hello2.Book.title,hello2.Book.title.nullable.error.title,hello2.Book.title.nullable.error.java.lang.String,hello2.Book.title.nullable.error,book.title.nullable.error.hello2.Book.title,book.title.nullable.error.title,book.title.nullable.error.java.lang.String,book.title.nullable.error,hello2.Book.title.nullable.hello2.Book.title,hello2.Book.title.nullable.title,hello2.Book.title.nullable.java.lang.String,hello2.Book.title.nullable,book.title.nullable.hello2.Book.title,book.title.nullable.title,book.title.nullable.java.lang.String,book.title.nullable,nullable.hello2.Book.title,nullable.title,nullable.java.lang.String,nullable]; arguments [title,class hello2.Book]; default message [Property [{0}] of class [{1}] cannot be null]
==== Auto Timestamp ====
By adding ''dateCreated'', and ''lastUpdated'', Grails will automatically update the date the object created, and updated in the database.
class Book {
String title
String author
@BindingFormat('yyyy-MM-dd')
Date releaseDate
Date dateCreated
Date lastUpdated
static constraints = {
}
}
==== The Constraints ====
You might notice there is a static constraints = {} in the domain. This is use to set the constraints on how to map the fields in the database. If we put nothing there, by default, Grails assumes every field is NON-NULL. Remember we were not able to save() when we omitted the author field? It is because the database require this field to be non-null.
Now lets us add an ISBN field in the domain. As we know, it is possible that book does not have an ISBN. So our constraints will look like:
class Book {
String title
String author
@BindingFormat('yyyy-MM-dd')
Date releaseDate
String isbn
Date dateCreated
Date lastUpdated
static constraints = {
isbn nullable: true, blank: true, maxSize: 13, unique: true
}
}
This tells the database that the isbn field can be null or blank (empty string), and it length contains 13 chars, and it is a unique field, meaning this value is unique across the whole table.
Actually it would be a good idea to constraint all fields so that we know what things are going to be set in the database other than guess what Grails does by convention, so we do:
class Book {
String title
String author
@BindingFormat('yyyy-MM-dd')
Date releaseDate
String isbn
Date dateCreated
Date lastUpdated
static constraints = {
title nullable: false, blank: false, maxSize: 256
author nullable: false, blank: false, maxSize: 256
releaseDate nullable: false
isbn nullable: true, blank: true, maxSize: 13, unique: true
}
}
Now when we save a book object, title, author, release date must be set, and isbn is optional. But if we set the isbn, it might not exist in the database, or we will not able to save it.
==== What Field Can be Use in a Domain ====
This is depending on which database we use. For H2, the result as follow. You can of course change the size of byte[], String in the static constraints{} block.
class WhatField {
Boolean typeBoolean
Byte typeByte
Character typeChar
String typeString
Integer typeInteger
Short typeShort
Long typeLong
Float typeFloat
Double typeDouble
Date typeDate
byte[] typaAttachment
Book typeBook
List typeListOfBooks
static constraints = {
}
}
{{ :grails:domain_5.png?direct&200 |}}
===== Multiple Domains =====
Now we rewrite the domains, and have
class Author {
String name
String address
static constraints = {
name nullable: false, blank: false, maxSize: 255
address nullable: false, blank: false, maxSize: 512
}
}
class Book {
String title
Author author
@BindingFormat('yyyy-MM-dd')
Date releaseDate
String isbn
Date dateCreated
Date lastUpdated
static constraints = {
title nullable: false, blank: false, maxSize: 256
author nullable: false
releaseDate nullable: false
isbn nullable: true, blank: true, maxSize: 13, unique: true
}
}
To save a book with an author, we can do:
def add() {
Author author = new Author(name: "Tom Lee", address: "Some where out there, NY, USA")
Book book = new Book(title: "ABC", author: author, releaseDate: "2010-03-01")
try {
book.save(failOnError: true)
render("Book is created")
} catch (Exception e) {
render("Book cannot be created with errors:
" + book.errors)
}
}
===== Get Domain Data From Database =====
We can use GORM to get the data from the database. After called the add(), we can call show() by http://127.0.0.1/book/show. The dynamic finder findBy...() or findAllBy...() functions are provided by GORM. It is a huge topic, and we will cover more in [[mastering_gorm_dynamic_finders|]].
class BookController {
def index() {
render("index")
}
def add() {
Author author = new Author(name: "Tom Lee", address: "Some where out there, NY, USA")
Book book = new Book(title: "ABC", author: author, releaseDate: "2010-03-01")
try {
book.save(failOnError: true)
render("Book is created")
} catch (Exception e) {
render("Book cannot be created with errors:
" + book.errors)
}
}
def show(){
Book book = Book.findById(1)
render("Title:${book.title}
" +
"Release Date: ${book.releaseDate}
" +
"ISBN:${book.isbn}
" +
"Author: ${book.author.name}, ${book.author.address}")
}
}
The result should be
Title:ABC
Release Date: 2010-03-01 00:00:00.0
ISBN:null
Author: Tom Lee, Some where out there, NY, USA
===== Domain Contains List of Other Domain =====
class Book {
String title
List authors
@BindingFormat('yyyy-MM-dd')
Date releaseDate
String isbn
Date dateCreated
Date lastUpdated
static constraints = {
title nullable: false, blank: false, maxSize: 256
releaseDate nullable: false
isbn nullable: true, blank: true, maxSize: 13, unique: true
}
}
class Author {
String name
String address
static constraints = {
name nullable: false, blank: false, maxSize: 255
address nullable: false, blank: false, maxSize: 512
}
}
Now we can create this book object in the controller like:
class BookController {
def index() {
render("index")
}
def add() {
List authors = []
Author author1 = new Author(name: "Tom Lee", address: "Some where out there, NY, USA")
Author author2 = new Author(name: "Jack Kovsky", address: "Some where out there, Russia")
authors.add(author1)
authors.add(author2)
Book book = new Book(title: "ABC", releaseDate: "2010-03-01", isbn: "1234567890123")
book.authors = authors
try {
book.save(failOnError: true)
render("Book is created")
} catch (Exception e) {
render("Book cannot be created with errors:
" + book.errors)
}
}
def show(){
Book book = Book.findById(1)
StringBuilder sb = new StringBuilder()
sb.append("Title:${book.title}
" +
"Release Date: ${book.releaseDate}
" +
"ISBN:${book.isbn}
")
List authors = book.getAuthors()
authors.each {
sb.append("Author: ${it.name}, ${it.address}
")
}
render(sb.toString())
}
}
The result:
Title:ABC
Release Date: 2010-03-01 00:00:00.0
ISBN:1234567890123
Author: Tom Lee, Some where out there, NY, USA
Author: Jack Kovsky, Some where out there, Russia
In the database, three tables were created. The AUTHOR, BOOK, and a table that links these two tables called ''BOOK_AUTHOR''
{{ :grails:domain_4.png?direct&400 |}}
===== Thing We Skipped Here =====
* static mapping{...} block
* transient variable
* One to One mapping
* One to many mapping
* Many to many mapping
* Functions [beforeInsert, beforeUpdate, beforeDelete, beforeValidate, afterInsert, afterUpdate, afterDelete, onLoad]
* and more!
If want to know more, goto [[http://gorm.grails.org/6.1.x/hibernate/manual/|the GORM hibernate manual]].