Grails Object Relational Mapping (GORM)
介绍
Domain classes are core to any business application. They hold state about business processes and hopefully also implement behavior. They are linked together through relationships, either one-to-one or one-to-many.
GORM is Grails' object relational mapping (ORM) implementation. Under the hood it uses Hibernate 3 (an extremely popular and flexible open source ORM solution) but because of the dynamic nature of Groovy, the fact that it supports both static and dynamic typing and the convention of Grails there is less configuration involved in creating Grails domain classes.
You can also write Grails domain class in Java see the section on Hibernate Integration for how to write Grails domain classes in Java but still use dynamic persistent methods.
Creating a Domain Class
A domain class in Grails is essentially a normal Groovy class with 2 key characteristics: An "id" property and a "version" property:
class Book {
@Property Long id
@Property Long version
@Property String title
}
To help you get started you can run the following convenience target from the root of your Grails project:
grails create-domain-class
Creating Relationships
Defining a one-to-many relationship is a simple matter of defining a property of type Set and adding the mapping to the "relatesToMany" map:
class Author {
@Property Long id
@Property Long version
@Property relatesToMany = [ books : Book ]
@Property String name
@Property Set books = new HashSet()
}
One of the main features of Groovy is its support for static typing which means one-to-one and many-to-one relationships require no additions to the "relationships" map:
class Book {
@Property Long id
@Property Long version
@Property Author author
@Property String title
}
For one-to-many's the owning side is assumed to be the "one" side of the relationship (in the above example this would be "Author.groovy"), but if you create a one-to-one relationship it is important to define the owning side of the relationship. For example if we had an Author class which had no relationship to the Book class (ie in a one-to-one scenario) it would be assumed that the book class "owned" the author which clearly is not the case!
The implications of this is that if you delete an instance of Book the delete operation will also delete the Author, not exactly the desired effect. To define the belongings side of one-to-one or many-to-one relationship we use the "belongsTo" property:
class Book {
@Property Long id
@Property Long version
@Property belongsTo = Author
@Property Author author
@Property String title
}
A class can also belong to multiple related classes like this:
@Property belongsTo = [Author,Publisher]
In other words:
- an owner can have belongings
- belongings use the 'belongsTo' property to refer to their owner(s)
- if an owner dies, all his belongings vanish.
Relationship Summary
Here is a tabular summary of possible relationships between two classes A and B.
Relationships may be unidirectional (>) or bidirectional (<>) and have cardinalities one-to-one (1:1), one-to-many (1:m), many-to-one (m:1), and many-to-many (n:m).
The owner is marked as bold.
| A:B | unidirectional | bidirectional |
|---|---|---|
| 1:1 | A -> B | A <-> B ; B.belongsTo = [A] |
| 1:m | A -> Set ; A.relatesToMany= [B] | A -> Set ; A.relatesToMany = [B]; B -> A |
| m:1 | A -> B | A -> B ; B -> Set ; B.relatesToMany = [A] |
| n:m | n/a | n/a |
no belongsTo needed since in one-to-many always one is the owner.
opposite of ![]()
Optional and Transient properties
By default all properties are both persistent and required, to change that you can define List properties called "optionals" and "transients":
class Book {
@Property Long id
@Property Long version
@Property optionals = [ "releaseDate" ]
@Property transients = [ "digitalCopy" ]
@Property Author author
@Property String title
@Property String author
@Property Date releaseDate
@Property File digitalCopy
}
CRUD Operations
Grails domain classes use dynamic persistent methods to facilitate CRUD (Create/Read/Update/Delete) operations on persistent classes:
Create
To create entries in the database, domain class instances support a "save" method which cascades to the instance relationships. In the example below we only call "save" on the author and both the Author and Book instances are persisted:
def a = new Author(name:"Stephen King") def b = new Book(title:"The Shining",author:b) a.books.add(b) // persist a.save()
Note that in general it is recommended that you add logic to your domain classes to manage relationships and not add instances directly to the 'books' Set as done above. For example in this case it would be a appropriate to add a 'addBook' method:
def addBook(book) {
if(!books)books = new HashSet()
book.author = this
books.add(book)
return this
}
GORM will manage the persistence of your object model to the database, but won't manage the relationships for you so this kind of logic is good practice. The code would then become:
def a = new Author(name:"Stephen King") a.addBook(new Book(title:"The Shining")) .addBook(new Book(title:"IT")) .save()
Read
Grails supports a number of ways of retrieving domain class instances, for more detail on querying see the section on #Domain Class Querying, however to retrieve an instance if the "id" is known you can use the "get" static method:
Book.get(1)
Or to find all books the "findAll", "list" or "listOrderByX" static methods can be used:
Book.findAll() // retrieve all Book.list(10) // lists first 10 instances Book.listOrderByTitle() // lists all the instances ordered by the "title" property
Update
The symantics of updating differ from that of saving a domain class. It is possible to update without explicitly calling "save" with the changes automatically being persisted if no exceptions occur:
def b = Book.get(1)
b.releaseDate = new Date()
This behaviour is not always desired however paricularily when combined with validation constraints (ie you don't want your domain object saved if it is not validated). Therefore if you explicity call "save" and the object is not valid changes will not be persisted:
def b = Book.get(1) b.title = null // can't have a null title b.save() // won't save as fails to validate
It is sometimes handy though to make changes to your domain model programmatically if validation fails. In this way the symantics of the "validate" method differ as it won't discard the changes if validation fails:
def b = Book.get(1) b.publisher = "Print It" if(!b.validate()) { b.publisher = Publisher.DEFAULT }
In the above case changes will still be persisted even though validation failed during the "validate" method. If you want the "validate" method to not persist changes if it fails to validate you can pass a boolean "true" argument:
b.validate(true)
Alternatively, if you want to explicitly control if changes are persisted you can use the "discard" method that when called will discard any changes:
def b = Book.get(1) b.title = "A New Title" // something happenedd to change your mind b.discard()
Delete
Domain class instances can be removed from the database by using the "delete" instance method:
def b = Book.get(1) b.delete()
Domain Class Querying
Querying with dynamic methods
There are several ways to query for domain class instances, one of them being via Grails dynamic methods, from more details see DomainClass Dynamic Methods:
def results = Book.findByTitle("The Stand") results = Book.findByTitleLike("Harry Pot%") results = Book.findByReleaseDateBetween( firstDate, secondDate ) results = Book.findByReleaseDateGreaterThan( someDate ) results = Book.findByTitleLikeOrReleaseDateLessThan( "%Something%", someDate ) // find by relationship results = Book.findAllByAuthor( Author.get(1) )
Querying by example
Just pass an example of the domain object you would like to find to the find() method.
def b = Book.find( new Book(title:'The Shining') )
Querying with a criteria builder
For more advanced queries or querying across objects graphs you can use Criteria (for a full reference see the section on Builders):
def c = Book.createCriteria()
def results = c {
like("author.name", "Stephen%")
between("releaseDate", firstDate, secondDate )
}
Querying with HQL queries
Otherwise, as Grails uses Hibernate internally you can use an HQL query:
def results = Book.find("from Book as b where b.title like 'Lord of the%'")
Or with positional parameters:
def results = Book.find("from Book as b where b.title like ?", ["The Shi%"])