5 Mapeo Objeto-relacional (GORM) - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith
Version: null
Table of Contents
5 Mapeo Objeto-relacional (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; one-to-one, one-to-many, or many-to-many.
Las clases de dominio son fundamentales para cualquier aplicación de negocios. Representan el estado de los procesos del negocio y es de esperar que también implementen el comportamiento. Están vinculadas entre si mediante relaciones: uno-a-uno, uno-a-varios o varios-a-varios.
GORM is Grails' object relational mapping (ORM) implementation. Under the hood it uses Hibernate 3 (a very popular and flexible open source ORM solution) and thanks to the dynamic nature of Groovy with its static and dynamic typing, along with the convention of Grails, there is far less configuration involved in creating Grails domain classes.
GORM es la implementación del mapeo objeto-relacional (ORM por sus siglas en ingles) de Grails. Internamente se utiliza Hibernate 3 (una solución ORM de código abierto muy popular y flexible) que gracias a la naturaleza dinámica de Groovy con su tipado estático y dinámico, además de la convención empleada por Grails, requiere un mínimo de configuración en la creación de las clases de dominio de Grails.
You can also write Grails domain classes in Java. See the section on Hibernate Integration for how to write domain classes in Java but still use dynamic persistent methods. Below is a preview of GORM in action:
También es posible escribir las clases de dominio de Grails utilizando Java. Consulte la sección sobre Integración con Hibernate para mas información acerca de como escribir las clases en Java sin perder los métodos dinámicos de persistencia. A continuación un ejemplo de GORM en acción:def book = Book.findByTitle("Groovy in Action")book .addToAuthors(name:"Dierk Koenig") .addToAuthors(name:"Guillaume LaForge") .save()
5.1 Guía de inicio rápido
A domain class can be created with the create-domain-class command:
Una clase de dominio puede ser creada con el comando create-domain-class:grails create-domain-class helloworld.Person
If no package is specified with the create-domain-class script, Grails automatically uses the application name as the package name.Si el paquete no se especifica con el script create-domain-class, Grails automáticamente utilizara el nombre de la aplicación como nombre del paquete.
This will create a class at the location
Esto creara una clase como la siguiente en la ubicación grails-app/domain/helloworld/Person.groovy such as the one below:
grails-app/domain/helloworld/Person.groovy:package helloworldclass Person {
}If you have theSi la propiedaddbCreateproperty set to "update", "create" or "create-drop" on your DataSource, Grails will automatically generate/modify the database tables for you.dbCreate, se establece a "update", "create" o "create-drop" en el origen de datos, Grails generara/modificara automáticamente las tablas de la base de datos por usted.
You can customize the class by adding properties:
Usted puede personalizar la clase añadiendo propiedades:class Person {
String name
Integer age
Date lastVisit
}grails console
This loads an interactive GUI where you can run Groovy commands with access to the Spring ApplicationContext, GORM, etc.
Este comando cargara una GUI interactiva donde podrá ejecutar comandos Groovy y con acceso al ApplicationContext de Spring, GORM, etc.
5.1.1 CRUD Básico
Try performing some basic CRUD (Create/Read/Update/Delete) operations.
Pruebe realizando algunas operaciones CRUD (Create/Read/Update/Delete por sus siglas en ingles) básicas.Create
Crear
To create a domain class use Map constructor to set its properties and call save:
Para crear una clase de dominio utilice un constructor Mapa para establecer sus propiedades y llame save:def p = new Person(name: "Fred", age: 40, lastVisit: new Date()) p.save()
The save method will persist your class to the database using the underlying Hibernate ORM layer.
El método save persistirá la clase a la base de datos utilizando la capa ORM subyacente de Hibernate.Read
Leer
Grails transparently adds an implicit
De forma transparente, Grails añade a la clase de dominio la propiedad implícita id property to your domain class which you can use for retrieval:
id que puede utilizarse para su recuperación:def p = Person.get(1) assert 1 == p.id
This uses the get method that expects a database identifier to read the
En este ejemplo se utiliza el método get que espera un identificador para leer el objeto Person object back from the database.
You can also load an object in a read-only state by using the read method:
Person desde la base de datos. Para cargar un objeto en estado de sólo-lectura utilice el método read:def p = Person.read(1)
In this case the underlying Hibernate engine will not do any dirty checking and the object will not be persisted. Note that
if you explicitly call the save method then the object is placed back into a read-write state.
En este caso el motor subyacente de Hibernate no hará ningún "dirty checking" y el objeto no sera persistido. Si usted de manera explicita llama al método save entonces el estado del objeto se modificara a lectura-escritura.
In addition, you can also load a proxy for an instance by using the load method:
Adicionalmente puede utilizar el método load para cargar un proxy para una instancia:def p = Person.load(1)
This incurs no database access until a method other than getId() is called. Hibernate then initializes the proxied instance, or
throws an exception if no record is found for the specified id.
El uso de este método no incurre en acceso a la base de datos hasta que un método distinto a getId () es llamado, entonces Hibernate inicializara la instancia del proxy, o se producirá una excepción si no encuentra un registro con el id especificado.Update
Actualizar
To update an instance, change some properties and then call save again:
Para actualizar una instancia, modifique algunas propiedades y entonces llame save nuevamente:def p = Person.get(1)
p.name = "Bob"
p.save()Delete
Eliminar
To delete an instance use the delete method:
Para eliminar una instancia utilice el método delete:def p = Person.get(1) p.delete()
5.2 Modelado del dominio en GORM
When building Grails applications you have to consider the problem domain you are trying to solve. For example if you were building an Amazon-style bookstore you would be thinking about books, authors, customers and publishers to name a few.
Al crear aplicaciones Grails usted tiene que considerar el dominio del problema que está tratando de resolver. Por ejemplo, si estuviera construyendo una librería al estilo de Amazon estaría pensando en libros, autores, clientes y editores solo por nombrar algunos.
These are modeled in GORM as Groovy classes, so a
Estos se modelan en GORM como clases Groovy, por lo que una clase Book class may have a title, a release date, an ISBN number and so on. The next few sections show how to model the domain in GORM.
Book puede tener un título, una fecha de publicación, un número de ISBN y así sucesivamente. Las siguientes secciones muestran cómo modelar el dominio en GORM.
To create a domain class you run the create-domain-class command as follows:
Para crear una clase de dominio ejecute el comando create-domain-class de la siguiente manera:grails create-domain-class org.bookstore.Book
The result will be a class at
El resultado será una clase en grails-app/domain/org/bookstore/Book.groovy:
grails-app/domain/org/bookstore/Book.groovy:package org.bookstoreclass Book {
}
This class will map automatically to a table in the database called
A esta clase se corresponde automáticamente una tabla en la base de datos llamada book (the same name as the class). This behaviour is customizable through the ORM Domain Specific Language
book (el mismo nombre que la clase). Este comportamiento puede modificarse a través del Lenguaje específico del dominio (DSL por sus siglas en ingles) ORM
Now that you have a domain class you can define its properties as Java types. For example:
Ahora que tiene una clase de dominio usted puede definir sus propiedades como tipos de Java. Por ejemplo:package org.bookstoreclass Book { String title Date releaseDate String ISBN }
Each property is mapped to a column in the database, where the convention for column names is all lower case separated by underscores. For example
A cada propiedad se asigna una columna en la base de datos, la convención para nombrar las columnas es utilizar letras minúsculas separadas por guiones bajos. Por ejemplo releaseDate maps onto a column release_date. The SQL types are auto-detected from the Java types, but can be customized with Constraints or the ORM DSL.
releaseDate se asigna a una columna release_date. Los tipos de SQL se detectan de forma automática a partir de los tipos de Java, pero pueden ser modificados mediante el uso de Constraints o el ORM DSL.
5.2.1 Asociación en GORM
Relationships define how domain classes interact with each other. Unless specified explicitly at both ends, a relationship exists only in the direction it is defined.
Las relaciones definen cómo interactúan entre sí las clases de dominio. A menos que de forma explícita se especifique en ambos lados, una relación existe solo en la dirección que es definida.
5.2.1.1 Varios-a-uno y uno-a-uno
A many-to-one relationship is the simplest kind, and is defined with a property of the type of another domain class. Consider this example:
Una relación de varios-a-uno es el tipo más simple, se define mediante una propiedad del tipo de otra clase de dominio. Considere este ejemplo:Example A
Ejemplo A
class Face {
Nose nose
}class Nose {
}
In this case we have a unidirectional many-to-one relationship from
En este caso tenemos una relación unidireccional varios-a-uno desde Face to Nose. To make this relationship bidirectional define the other side as follows:
Face hacia Nose. Para hacer esta relación bidireccional, es necesario definir el otro lado de la siguiente manera:Example B
Ejemplo B
class Face {
Nose nose
}class Nose {
static belongsTo = [face:Face]
}
In this case we use the
En este caso establecemos mediante belongsTo setting to say that Nose "belongs to" Face. The result of this is that we can create a Face, attach a Nose instance to it and when we save or delete the Face instance, GORM will save or delete the Nose. In other words, saves and deletes will cascade from Face to the associated Nose:
belongsTo que Nose pertenece a Face. Como resultado de esto podemos crear Face, agregar una instancia de Nose y cuando guardemos o eliminemos la instancia de Face, GORM guardara o eliminara Nose. En otras palabras, la actualización y eliminación se realizaran en cascada desde Face hacia Nose.new Face(nose:new Nose()).save()
The example above will save both face and nose. Note that the inverse is not true and will result in an error due to a transient
En el ejemplo anterior ambos, face y nose serán guardados. Esto no funcionara de modo inverso y el resultado seria un error debido a un Face:
Face que es transitorio.new Nose(face:new Face()).save() // will cause an error
new Nose(face:new Face()).save() // Esto causara un error
Now if we delete the
Si borramos la instancia Face instance, the Nose will go too:
Face, Nose también sera eliminada:def f = Face.get(1) f.delete() // both Face and Nose deleted
def f = Face.get(1) f.delete() // Ambos Face y Nose serán eliminados
To make the relationship a true one-to-one, use the
Para hacer que la relación sea verdaderamente uno-a-uno, utilice la propiedad hasOne property on the owning side, e.g. Face:
hasOne en el lado que define la posesión, por ejemplo, Face:Example C
Ejemplo C
class Face {
static hasOne = [nose:Nose]
}class Nose {
Face face
}
Note that using this property puts the foreign key on the inverse table to the previous example, so in this case the foreign key column is stored in the
Tenga en cuenta que al hacer uso de esta propiedad, la clave externa sera colocada en la tabla opuesta al ejemplo anterior, por lo que en este caso la columna de clave externa se almacena en la tabla nose table inside a column called face_id. Also, hasOne only works with bidirectional relationships.
nose en una columna llamada face_id. Además, hasOne sólo funciona en las relaciones bidireccionales.
Finally, it's a good idea to add a unique constraint on one side of the one-to-one relationship:
Por último, es conveniente añadir una restricción de unicidad en un lado de la relación uno-a-uno:class Face {
static hasOne = [nose:Nose] static constraints = {
nose unique: true
}
}class Nose {
Face face
}5.2.1.2 Uno-a-varios
A one-to-many relationship is when one class, example
Una relación uno-a-varios es cuando una clase, por ejemplo Author, has many instances of a another class, example Book. With Grails you define such a relationship with the hasMany setting:
Author, tiene varias instancias de otra clase, por ejemplo Book. En Grails se establece este tipo de relación utilizando hasMany:class Author {
static hasMany = [books: Book] String name
}class Book {
String title
}
In this case we have a unidirectional one-to-many. Grails will, by default, map this kind of relationship with a join table.
En este caso tenemos una relación unidireccional uno-a-varios. De manera predeterminada, Grails mapeara este tipo de relación mediante una tabla de unión.The ORM DSL allows mapping unidirectional relationships using a foreign key association insteadEl DSL ORM permite mapear relaciones unidireccionales mediante el uso de una clave externa.
Grails will automatically inject a property of type
Grails inyectara automáticamente una propiedad de tipo java.util.Set into the domain class based on the hasMany setting. This can be used to iterate over the collection:
java.util.Set en la clase de dominio basándose en hasMany. Esta propiedad puede ser usada para iterar sobre la colección:def a = Author.get(1)for (book in a.books) {
println book.title
}The default fetch strategy used by Grails is "lazy", which means that the collection will be lazily initialized on first access. This can lead to the n+1 problem if you are not careful.De manera predeterminada Grails utilizara una estrategia de recuperación "lazy", lo que significa que la colección sera inicializada hasta que se accede por primera vez. Esto puede provocar que se incurra en el problema n+1 si usted no es cuidadoso. Si necesita recuperación "eager" puede utilizar el DSL ORM o puede especificar la recuperación "eager" como parte de una consulta
The default cascading behaviour is to cascade saves and updates, but not deletes unless a
El comportamiento predeterminado de la cascada es guardar y actualizar, pero no eliminar a menos que también se especifique belongsTo is also specified:
belongsTo:class Author {
static hasMany = [books: Book] String name
}class Book {
static belongsTo = [author: Author]
String title
}
If you have two properties of the same type on the many side of a one-to-many you have to use
Si usted tiene dos propiedades del mismo tipo en el lado varios de uno-a-varios, deberá utilizar mappedBy to specify which the collection is mapped:
mappedBy para especificar la colección a mapear:class Airport {
static hasMany = [flights: Flight]
static mappedBy = [flights: "departureAirport"]
}class Flight {
Airport departureAirport
Airport destinationAirport
}
This is also true if you have multiple collections that map to different properties on the many side:
Esto también sera válido si tiene varias colecciones que mapean a diferentes propiedades en el lado varios:class Airport {
static hasMany = [outboundFlights: Flight, inboundFlights: Flight]
static mappedBy = [outboundFlights: "departureAirport",
inboundFlights: "destinationAirport"]
}class Flight {
Airport departureAirport
Airport destinationAirport
}5.2.1.3 Varios-a-varios
Grails supports many-to-many relationships by defining a
Grails tiene soporte para relaciones varios-a-varios mediante la inclusión de hasMany on both sides of the relationship and having a belongsTo on the owned side of the relationship:
hasMany en ambos lados de la relación y del lado que expresa ser la propiedad se incluye belongsTo:class Book {
static belongsTo = Author
static hasMany = [authors:Author]
String title
}class Author {
static hasMany = [books:Book]
String name
}
Grails maps a many-to-many using a join table at the database level. The owning side of the relationship, in this case
A nivel de la base de datos Grails mapea una relación varios-a-varios mediante una tabla de unión. El lado propietario de la relación, en este caso Author, takes responsibility for persisting the relationship and is the only side that can cascade saves across.
Author, asume la responsabilidad de la persistencia de la relación y es el único que puede propagar la actualización.
For example this will work and cascade saves:
Por ejemplo, esto propagaría la creación de manera correcta:new Author(name:"Stephen King") .addToBooks(new Book(title:"The Stand")) .addToBooks(new Book(title:"The Shining")) .save()
However this will only save the
Sin embargo, esto sólo guardaría a Book and not the authors!
Book y no a los autores!new Book(name:"Groovy in Action") .addToAuthors(new Author(name:"Dierk Koenig")) .addToAuthors(new Author(name:"Guillaume Laforge")) .save()
This is the expected behaviour as, just like Hibernate, only one side of a many-to-many can take responsibility for managing the relationship.
Este es el comportamiento esperado, al igual que en Hibernate, sólo un lado de la relación varios-a-varios puede asumir la responsabilidad de la gestión.Grails' Scaffolding feature does not currently support many-to-many relationship and hence you must write the code to manage the relationship yourselfActualmente el Scaffolding de Grails no es compatible con relaciones del tipo varios-a-varios, y por lo tanto, usted deberá escribir el código para manejar este tipo de relación.
5.2.1.4 Colecciones de tipos básicos
As well as associations between different domain classes, GORM also supports mapping of basic collection types.
For example, the following class creates a
Al igual que con las asociaciones entre diferentes tipos de clases de dominio, GORM también es compatible con el mapeo de colecciones de tipos básicos. Por ejemplo, la clase siguiente, crea una asociación de nicknames association that is a Set of String instances:
nicknames que es un Set de instancias String:class Person {
static hasMany = [nicknames: String]
}
GORM will map an association like the above using a join table. You can alter various aspects of how the join table is mapped using the
GORM mapeara esta asociación utilizando una tabla de unión. Usted puede modificar varios aspectos del mapeo de la tabla de unión mediante el argumento joinTable argument:
joinTable:class Person { static hasMany = [nicknames: String] static mapping = {
hasMany joinTable: [name: 'bunch_o_nicknames',
key: 'person_id',
column: 'nickname',
type: "text"]
}
}
The example above will map to a table that looks like the following:
Al ejemplo anterior corresponderá una tabla como la siguiente:
bunch_o_nicknames Table
Tabla bunch_o_nicknames
--------------------------------------------- | person_id | nickname | --------------------------------------------- | 1 | Fred | ---------------------------------------------
5.2.2 Composición en GORM
As well as association, Grails supports the notion of composition. In this case instead of mapping classes onto separate tables a class can be "embedded" within the current table. For example:
Además de la asociación, Grails también tiene soporte para la composición. En este caso, en lugar de mapear las clases en tablas separadas una clase puede ser "integrada" dentro de la tabla actual. Por ejemplo:class Person {
Address homeAddress
Address workAddress
static embedded = ['homeAddress', 'workAddress']
}class Address {
String number
String code
}
The resulting mapping would looking like this:
El mapeo resultante tendría el siguiente aspecto:
If you define theSi la claseAddressclass in a separate Groovy file in thegrails-app/domaindirectory you will also get anaddresstable. If you don't want this to happen use Groovy's ability to define múltiple classes per file and include theAddressclass below thePersonclass in thegrails-app/domain/Person.groovyfileAddresses definida por separado en un archivo Groovy dentro del directoriograils-app/domain, la tablaaddresstambién sera generada. Si no desea que esto suceda, utilice la capacidad de Groovy para definir múltiples clases por archivo e incluya la claseAddressdebajo de la clasePersonen el archivograils-app/domain/Person.groovy.
5.2.3 Herencia en GORM
GORM supports inheritance both from abstract base classes and concrete persistent GORM entities. For example:
GORM soporta herencia de clases base abstractas y de entidades GORM persistentes concretas: Por ejemplo:class Content {
String author
}class BlogEntry extends Content {
URL url
}class Book extends Content { String ISBN }
class PodCast extends Content { byte[] audioStream }
In the above example we have a parent
En el ejemplo anterior tenemos una clase padre Content class and then various child classes with more specific behaviour.
Content y varias clases hijo con un comportamiento más específico.Considerations
Consideraciones
At the database level Grails by default uses table-per-hierarchy mapping with a discriminator column called
A nivel de la base de datos, Grails mapeara una tabla-por-jerarquía e incluirá una columna discriminador llamada class so the parent class (Content) and its subclasses (BlogEntry, Book etc.), share the same table.
class, de esta forma la clase padre (Content) y las subclases (BlogEntry, Book etc.), comparten la misma tabla.
Table-per-hierarchy mapping has a down side in that you cannot have non-nullable properties with inheritance mapping. An alternative is to use table-per-subclass which can be enabled with the ORM DSL
El mapeado de tabla-por-jerarquía tiene la desventaja de que no permite definir propiedades no nulas. Una alternativa es utilizar una tabla-por-subclase que puede habilitarse mediante el DSL ORM
However, excessive use of inheritance and table-per-subclass can result in poor query performance due to the use of outer join queries. In general our advice is if you're going to use inheritance, don't abuse it and don't make your inheritance hierarchy too deep.
Sin embargo, el uso excesivo de herencia y de tabla-por-subclase, pueden dar lugar a un rendimiento deficiente de las consultas debido a la utilización de combinaciones externas en ellas. Nuestro consejo seria, si va a utilizar la herencia, no abusar de ella y no hacer la jerarquía de la herencia demasiado profunda.Polymorphic Queries
Consultas polimórficas
The upshot of inheritance is that you get the ability to polymorphically query. For example using the list method on the
Como resultado de la herencia se obtiene la capacidad de realizar una consulta polimórfica. Por ejemplo, al utilizar el método list de la super clase Content super class will return all subclasses of Content:
Content este devolverá todas las subclases de Content:def content = Content.list() // list all blog entries, books and podcasts
content = Content.findAllByAuthor('Joe Bloggs') // find all by authordef podCasts = PodCast.list() // list only podcastsdef content = Content.list() // lista todas las entradas de blog, books y podcasts
content = Content.findAllByAuthor('Joe Bloggs') // encontrar todo por authordef podCasts = PodCast.list() // lista únicamente los podcasts5.2.4 Conjuntos, Listas y Mapas
Sets of Objects
Conjuntos de objetos
By default when you define a relationship with GORM it is a
Al definir una relación con GORM, de manera predeterminada sera un java.util.Set which is an unordered collection that cannot contain duplicates. In other words when you have:
java.util.Set, una colección sin orden y que no puede contener duplicados. En otras palabras, cuando usted tiene:class Author {
static hasMany = [books: Book]
}
The books property that GORM injects is a
La propiedad books que GORM inyecta es un java.util.Set. Sets guarantee uniquenes but not order, which may not be what you want. To have custom ordering you configure the Set as a SortedSet:
java.util.Set. El cual garantiza la unicidad de los elementos pero no el orden, lo cual puede no ser lo que usted quiere. Para personalizar el orden de los elementos establezca el conjunto como un SortedSet:class Author { SortedSet books static hasMany = [books: Book]
}
In this case a
En este caso, se hace uso de la implementación java.util.SortedSet implementation is used which means you must implement java.lang.Comparable in your Book class:
java.util.SortedSet, esto significa que la clase Book debe implementar java.lang.Comparable:class Book implements Comparable { String title Date releaseDate = new Date() int compareTo(obj) { releaseDate.compareTo(obj.releaseDate) } }
The result of the above class is that the Book instances in the books collection of the Author class will be ordered by their release date.
Como resultado de esta clase, las instancias Book en la colección books perteneciente a la clase Author, serán ordenadas por su fecha de publicación.
Lists of Objects
Listas de objetos
To keep objects in the order which they were added and to be able to reference them by index like an array you can define your collection type as a
Para mantener los objetos en el orden que se han añadido y poder hacer referencia a ellos por medio del índice como en un arreglo, se debe definir el tipo de colección como un List:
List:class Author { List books static hasMany = [books: Book]
}
In this case when you add new elements to the books collection the order is retained in a sequential list indexed from 0 so you can do:
En este caso, cuando se añaden nuevos elementos a la colección books, el orden se mantiene en una lista secuencial, son indexados desde 0 y es posible hacer lo siguiente:author.books[0] // get the first book
author.books[0] // obtener el primer book
The way this works at the database level is Hibernate creates a
A nivel de la base de datos, Hibernate crea una columna books_idx column where it saves the index of the elements in the collection to retain this order at the database level.
books_idx donde guarda el índice de los elementos en la colección y de esta manera conserva el orden en la base de datos
When using a
Cuando se utiliza un List, elements must be added to the collection before being saved, otherwise Hibernate will throw an exception (org.hibernate.HibernateException: null index column for collection):
List, los elementos se deben añadir a la colección antes de realizar el guardado, de lo contrario Hibernate arrojara una excepción (org.hibernate.HibernateException: null index column for collection):// This won't work!
def book = new Book(title: 'The Shining')
book.save()
author.addToBooks(book)// Esto no funcionara
def book = new Book(title: 'The Shining')
book.save()
author.addToBooks(book)// Do it this way instead. def book = new Book(title: 'Misery') author.addToBooks(book) author.save()
// Debe realizarse de esta forma.
def book = new Book(title: 'Misery')
author.addToBooks(book)
author.save()Bags of Objects
Bags de objetos
If ordering and uniqueness aren't a concern (or if you manage these explicitly) then you can use the Hibernate Bag type to represent mapped collections.
Si el orden y la unicidad no son de importancia (o si usted realiza la gestión de manera explicita), entonces puede utilizar el tipo Bag de Hibernate para representar las colecciones mapeadas.
The only change required for this is to define the collection type as a
El único cambio requerido para esto, es definir el tipo de una colección utilizando Collection:
Collection:class Author { Collection books static hasMany = [books: Book]
}
Since uniqueness and order aren't managed by Hibernate, adding to or removing from collections mapped as a Bag don't trigger a load of all existing instances from the database, so this approach will perform better and require less memory than using a
Debido a que la unicidad y el orden no son gestionados por Hibernate, añadir o eliminar elementos de una colección mapeada como Bag, no desencadenara la carga de las demás instancias desde la base de datos, por lo tanto, este método obtendrá un mejor desempeño y requiere un menor uso de memoria que cuando se usa un Set or a List.
Set o un List.Maps of Objects
Mapas de objetos
If you want a simple map of string/value pairs GORM can map this with the following:
Si lo que desea es un simple mapa de pares cadena-de-texto/valor, GORM puede realizar este mapeo de la siguiente manera:class Author {
Map books // map of ISBN:book names
}def a = new Author()
a.books = ["1590597583":"Grails Book"]
a.save()class Author {
Map books // mapa de ISBN:título
}def a = new Author()
a.books = ["1590597583":"Grails Book"]
a.save()
In this case the key and value of the map MUST be strings.
En este caso, la clave y el valor del mapa DEBEN ser cadenas de texto.
If you want a Map of objects then you can do this:
Si lo que quiere es un mapa de objetos, entonces puede hacer lo siguiente:class Book { Map authors static hasMany = [authors: Author]
}def a = new Author(name:"Stephen King")def book = new Book()
book.authors = [stephen:a]
book.save()
The static
La propiedad estática hasMany property defines the type of the elements within the Map. The keys for the map must be strings.
hasMany define el tipo de elementos que contendrá el mapa. Las claves del mapa deben ser cadenas de texto.A Note on Collection Types and Performance
Acerca de los tipos de colecciones y el desempeño
The Java
El tipo Set type doesn't allow duplicates. To ensure uniqueness when adding an entry to a Set association Hibernate has to load the entire associations from the database. If you have a large numbers of entries in the association this can be costly in terms of performance.
Set de Java no permite duplicados. Para garantizar la unicidad cuando se añade una entrada en una asociación Set, Hibernate debe cargar la asociación completa de la base de datos. Si la asociación contiene una gran cantidad de elementos, esto podría resultar costoso en términos de rendimiento.
The same behavior is required for
El mismo funcionamiento aplica para el tipo List types, since Hibernate needs to load the entire association to maintain order. Therefore it is recommended that if you anticipate a large numbers of records in the association that you make the association bidirectional so that the link can be created on the inverse side. For example consider the following code:
List debido a que para mantener el orden de los elementos, Hibernate necesita cargar la asociación entera. Por lo tanto, si espera una gran cantidad de registros en la asociación, es recomendable definirla de manera bidireccional, así el enlace puede ser creado en el lado contrario:def book = new Book(title:"New Grails Book") def author = Author.get(1) book.author = author book.save()
In this example the association link is being created by the child (Book) and hence it is not necessary to manipulate the collection directly resulting in fewer queries and more efficient code. Given an
En este ejemplo, se crea la asociación por el elemento hijo (Book) y por lo tanto, no es necesario operar directamente sobre la colección, el resultado es un menor numero de consultas y el código es más eficiente. Teniendo en cuenta un Author with a large number of associated Book instances if you were to write code like the following you would see an impact on performance:
Author con una gran cantidad de instancias Book asociadas, si tuviera que escribir código como el siguiente notaria el resultado en el rendimiento:def book = new Book(title:"New Grails Book") def author = Author.get(1) author.addToBooks(book) author.save()
You could also model the collection as a Hibernate Bag as described above.
También podría modelar la colección utilizando un Bag de Hibernate como se describió anteriormente.
5.3 Persistence Basics
A key thing to remember about Grails is that under the surface Grails is using Hibernate for persistence. If you are coming from a background of using ActiveRecord or iBatis Hibernate's "session" model may feel a little strange.Grails automatically binds a Hibernate session to the currently executing request. This lets you use the save and delete methods as well as other GORM methods transparently.Transactional Write-Behind
A useful feature of Hibernate over direct JDBC calls and even other frameworks is that when you call save or delete it does not necessarily perform any SQL operations at that point. Hibernate batches up SQL statements and executes them as late as possible, often at the end of the request when flushing and closing the session. This is typically done for you automatically by Grails, which manages your Hibernate session.Hibernate caches database updates where possible, only actually pushing the changes when it knows that a flush is required, or when a flush is triggered programmatically. One common case where Hibernate will flush cached updates is when performing queries since the cached information might be included in the query results. But as long as you're doing non-conflicting saves, updates, and deletes, they'll be batched until the session is flushed. This can be a significant performance boost for applications that do a lot of database writes.Note that flushing is not the same as committing a transaction. If your actions are performed in the context of a transaction, flushing will execute SQL updates but the database will save the changes in its transaction queue and only finalize the updates when the transaction commits.5.3.1 Saving and Updating
An example of using the save method can be seen below:def p = Person.get(1) p.save()
def p = Person.get(1)
p.save(flush: true)def p = Person.get(1) try { p.save(flush: true) } catch (org.springframework.dao.DataIntegrityViolationException e) { // deal with exception }
save() will simply return null in this case, but if you would prefer it to throw an exception you can use the failOnError argument:def p = Person.get(1) try { p.save(failOnError: true) } catch (ValidationException e) { // deal with exception }
Config.groovy, as described in the section on configuration. Just remember that when you are saving domain instances that have been bound with data provided by the user, the likelihood of validation exceptions is quite high and you won't want those exceptions propagating to the end user.You can find out more about the subtleties of saving data in this article - a must read!
5.3.2 Deleting Objects
An example of the delete method can be seen below:def p = Person.get(1) p.delete()
flush argument:def p = Person.get(1)
p.delete(flush: true)flush argument lets you catch any errors that occur during a delete. A common error that may occur is if you violate a database constraint, although this is normally down to a programming or schema error. The following example shows how to catch a DataIntegrityViolationException that is thrown when you violate the database constraints:def p = Person.get(1)try { p.delete(flush: true) } catch (org.springframework.dao.DataIntegrityViolationException e) { flash.message = "Could not delete person ${p.name}" redirect(action: "show", id: p.id) }
deleteAll method as deleting data is discouraged and can often be avoided through boolean flags/logic.If you really need to batch delete data you can use the executeUpdate method to do batch DML statements:Customer.executeUpdate("delete Customer c where c.name = :oldName", [oldName: "Fred"])
5.3.3 Understanding Cascading Updates and Deletes
It is critical that you understand how cascading updates and deletes work when using GORM. The key part to remember is thebelongsTo setting which controls which class "owns" a relationship.Whether it is a one-to-one, one-to-many or many-to-many, defining belongsTo will result in updates cascading from the owning class to its dependant (the other side of the relationship), and for many-/one-to-one and one-to-many relationships deletes will also cascade.If you do not define belongsTo then no cascades will happen and you will have to manually save each object (except in the case of the one-to-many, in which case saves will cascade automatically if a new instance is in a hasMany collection).Here is an example:class Airport {
String name
static hasMany = [flights: Flight]
}class Flight {
String number
static belongsTo = [airport: Airport]
}Airport and add some Flights to it I can save the Airport and have the updates cascaded down to each flight, hence saving the whole object graph:new Airport(name: "Gatwick") .addToFlights(new Flight(number: "BA3430")) .addToFlights(new Flight(number: "EZ0938")) .save()
Airport all Flights associated with it will also be deleted:def airport = Airport.findByName("Gatwick")
airport.delete()belongsTo then the above cascading deletion code would not work. To understand this better take a look at the summaries below that describe the default behaviour of GORM with regards to specific associations. Also read part 2 of the GORM Gotchas series of articles to get a deeper understanding of relationships and cascading.Bidirectional one-to-many with belongsTo
class A { static hasMany = [bees: B] }class B { static belongsTo = [a: A] }belongsTo then the cascade strategy is set to "ALL" for the one side and "NONE" for the many side.Unidirectional one-to-many
class A { static hasMany = [bees: B] }class B { }Bidirectional one-to-many, no belongsTo
class A { static hasMany = [bees: B] }class B { A a }belongsTo then the cascade strategy is set to "SAVE-UPDATE" for the one side and "NONE" for the many side.Unidirectional one-to-one with belongsTo
class A { }class B { static belongsTo = [a: A] }belongsTo then the cascade strategy is set to "ALL" for the owning side of the relationship (A->B) and "NONE" from the side that defines the belongsTo (B->A)Note that if you need further control over cascading behaviour, you can use the ORM DSL.
5.3.4 Eager and Lazy Fetching
Associations in GORM are by default lazy. This is best explained by example:class Airport {
String name
static hasMany = [flights: Flight]
}class Flight {
String number
Location destination
static belongsTo = [airport: Airport]
}class Location {
String city
String country
}def airport = Airport.findByName("Gatwick") for (flight in airport.flights) { println flight.destination.city }
Airport instance, another to get its flights, and then 1 extra query for each iteration over the flights association to get the current flight's destination. In other words you get N+1 queries (if you exclude the original one to get the airport).Configuring Eager Fetching
An alternative approach that avoids the N+1 queries is to use eager fetching, which can be specified as follows:class Airport {
String name
static hasMany = [flights: Flight]
static mapping = {
flights lazy: false
}
}flights association will be loaded at the same time as its Airport instance, although a second query will be executed to fetch the collection. You can also use fetch: 'join' instead of lazy: false , in which case GORM will only execute a single query to get the airports and their flights. This works well for single-ended associations, but you need to be careful with one-to-manys. Queries will work as you'd expect right up to the moment you add a limit to the number of results you want. At that point, you will likely end up with fewer results than you were expecting. The reason for this is quite technical but ultimately the problem arises from GORM using a left outer join.So, the recommendation is currently to use fetch: 'join' for single-ended associations and lazy: false for one-to-manys.Be careful how and where you use eager loading because you could load your entire database into memory with too many eager associations. You can find more information on the mapping options in the section on the ORM DSL.Using Batch Fetching
Although eager fetching is appropriate for some cases, it is not always desirable. If you made everything eager you could quite possibly load your entire database into memory resulting in performance and memory problems. An alternative to eager fetching is to use batch fetching. You can configure Hibernate to lazily fetch results in "batches". For example:class Airport {
String name
static hasMany = [flights: Flight]
static mapping = {
flights batchSize: 10
}
}batchSize argument, when you iterate over the flights association, Hibernate will fetch results in batches of 10. For example if you had an Airport that had 30 flights, if you didn't configure batch fetching you would get 1 query to fetch the Airport and then 30 queries to fetch each flight. With batch fetching you get 1 query to fetch the Airport and 3 queries to fetch each Flight in batches of 10. In other words, batch fetching is an optimization of the lazy fetching strategy. Batch fetching can also be configured at the class level as follows:class Flight {
…
static mapping = {
batchSize 10
}
}5.3.5 Pessimistic and Optimistic Locking
Optimistic Locking
By default GORM classes are configured for optimistic locking. Optimistic locking is a feature of Hibernate which involves storing a version value in a specialversion column in the database that is incremented after each update.The version column gets read into a version property that contains the current versioned state of persistent instance which you can access:def airport = Airport.get(10)println airport.version
def airport = Airport.get(10)try { airport.name = "Heathrow" airport.save(flush: true) } catch (org.springframework.dao.OptimisticLockingFailureException e) { // deal with exception }
The version will only be updated after flushing the session.
Pessimistic Locking
Pessimistic locking is equivalent to doing a SQL "SELECT * FOR UPDATE" statement and locking a row in the database. This has the implication that other read operations will be blocking until the lock is released.In Grails pessimistic locking is performed on an existing instance with the lock method:def airport = Airport.get(10) airport.lock() // lock for update airport.name = "Heathrow" airport.save()
get() and the call to lock().To get around this problem you can use the static lock method that takes an id just like get:def airport = Airport.lock(10) // lock for update airport.name = "Heathrow" airport.save()
def airport = Airport.findByName("Heathrow", [lock: true])
def airport = Airport.createCriteria().get {
eq('name', 'Heathrow')
lock true
}5.3.6 Modification Checking
Once you have loaded and possibly modified a persistent domain class instance, it isn't straightforward to retrieve the original values. If you try to reload the instance using get Hibernate will return the current modified instance from its Session cache. Reloading using another query would trigger a flush which could cause problems if your data isn't ready to be flushed yet. So GORM provides some methods to retrieve the original values that Hibernate caches when it loads the instance (which it uses for dirty checking).isDirty
You can use the isDirty method to check if any field has been modified:def airport = Airport.get(10) assert !airport.isDirty()airport.properties = params if (airport.isDirty()) { // do something based on changed state }
isDirty() does not currently check collection associations, but it does check all other persistent properties and associations.
You can also check if individual fields have been modified:def airport = Airport.get(10) assert !airport.isDirty()airport.properties = params if (airport.isDirty('name')) { // do something based on changed name }
getDirtyPropertyNames
You can use the getDirtyPropertyNames method to retrieve the names of modified fields; this may be empty but will not be null:def airport = Airport.get(10) assert !airport.isDirty()airport.properties = params def modifiedFieldNames = airport.getDirtyPropertyNames() for (fieldName in modifiedFieldNames) { // do something based on changed value }
getPersistentValue
You can use the getPersistentValue method to retrieve the value of a modified field:def airport = Airport.get(10) assert !airport.isDirty()airport.properties = params def modifiedFieldNames = airport.getDirtyPropertyNames() for (fieldName in modifiedFieldNames) { def currentValue = airport."$fieldName" def originalValue = airport.getPersistentValue(fieldName) if (currentValue != originalValue) { // do something based on changed value } }
5.4 Querying with GORM
GORM supports a number of powerful ways to query from dynamic finders, to criteria to Hibernate's object oriented query language HQL.Groovy's ability to manipulate collections with GPath and methods like sort, findAll and so on combined with GORM results in a powerful combination.However, let's start with the basics.Listing instances
Use the list method to obtain all instances of a given class:def books = Book.list()
def books = Book.list(offset:10, max:20)
def books = Book.list(sort:"title", order:"asc")
sort argument is the name of the domain class property that you wish to sort on, and the order argument is either asc for ascending or desc for descending.Retrieval by Database Identifier
The second basic form of retrieval is by database identifier using the get method:def book = Book.get(23)
def books = Book.getAll(23, 93, 81)
5.4.1 Dynamic Finders
GORM supports the concept of dynamic finders. A dynamic finder looks like a static method invocation, but the methods themselves don't actually exist in any form at the code level.Instead, a method is auto-magically generated using code synthesis at runtime, based on the properties of a given class. Take for example theBook class:class Book {
String title
Date releaseDate
Author author
}class Author {
String name
}Book class has properties such as title, releaseDate and author. These can be used by the findBy and findAllBy methods in the form of "method expressions":def book = Book.findByTitle("The Stand")book = Book.findByTitleLike("Harry Pot%")book = Book.findByReleaseDateBetween(firstDate, secondDate)book = Book.findByReleaseDateGreaterThan(someDate)book = Book.findByTitleLikeOrReleaseDateLessThan("%Something%", someDate)
Method Expressions
A method expression in GORM is made up of the prefix such as findBy followed by an expression that combines one or more properties. The basic form is:Book.findBy([Property][Comparator][Boolean Operator])?[Property][Comparator]def book = Book.findByTitle("The Stand")book = Book.findByTitleLike("Harry Pot%")
Like comparator, is equivalent to a SQL like expression.The possible comparators include:
InList- In the list of given valuesLessThan- less than a given valueLessThanEquals- less than or equal a give valueGreaterThan- greater than a given valueGreaterThanEquals- greater than or equal a given valueLike- Equivalent to a SQL like expressionIlike- Similar to aLike, except case insensitiveNotEqual- Negates equalityBetween- Between two values (requires two arguments)IsNotNull- Not a null value (doesn't take an argument)IsNull- Is a null value (doesn't take an argument)
def now = new Date()
def lastWeek = now - 7
def book = Book.findByReleaseDateBetween(lastWeek, now)books = Book.findAllByReleaseDateIsNull()
books = Book.findAllByReleaseDateIsNotNull()Boolean logic (AND/OR)
Method expressions can also use a boolean operator to combine two or more criteria:def books = Book.findAllByTitleLikeAndReleaseDateGreaterThan(
"%Java%", new Date() - 30)And in the middle of the query to make sure both conditions are satisfied, but you could equally use Or:def books = Book.findAllByTitleLikeOrReleaseDateGreaterThan(
"%Java%", new Date() - 30)And or all Or. If you need to combine And and Or or if the number of criteria creates a very long method name, just convert the query to a Criteria or HQL query.Querying Associations
Associations can also be used within queries:def author = Author.findByName("Stephen King")def books = author ? Book.findAllByAuthor(author) : []Author instance is not null we use it in a query to obtain all the Book instances for the given Author.Pagination and Sorting
The same pagination and sorting parameters available on the list method can also be used with dynamic finders by supplying a map as the final parameter:def books = Book.findAllByTitleLike("Harry Pot%", [max: 3, offset: 2, sort: "title", order: "desc"])
5.4.2 Criteria
Criteria is a type safe, advanced way to query that uses a Groovy builder to construct potentially complex queries. It is a much better approach than building up query strings using aStringBuffer.Criteria can be used either with the createCriteria or withCriteria methods. The builder uses Hibernate's Criteria API. The nodes on this builder map the static methods found in the Restrictions class of the Hibernate Criteria API. For example:def c = Account.createCriteria()
def results = c {
between("balance", 500, 1000)
eq("branch", "London")
or {
like("holderFirstName", "Fred%")
like("holderFirstName", "Barney%")
}
maxResults(10)
order("holderLastName", "desc")
}Account objects in a List matching the following criteria:
balanceis between 500 and 1000branchis 'London'holderFirstNamestarts with 'Fred' or 'Barney'
holderLastName.If no records are found with the above criteria, an empty List is returned.Conjunctions and Disjunctions
As demonstrated in the previous example you can group criteria in a logical OR using anor { } block:or {
between("balance", 500, 1000)
eq("branch", "London")
}and {
between("balance", 500, 1000)
eq("branch", "London")
}not {
between("balance", 500, 1000)
eq("branch", "London")
}Querying Associations
Associations can be queried by having a node that matches the property name. For example say theAccount class had many Transaction objects:class Account {
…
static hasMany = [transactions: Transaction]
…
}transaction as a builder node:def c = Account.createCriteria()
def now = new Date()
def results = c.list {
transactions {
between('date', now - 10, now)
}
}Account instances that have performed transactions within the last 10 days.
You can also nest such association queries within logical blocks:def c = Account.createCriteria()
def now = new Date()
def results = c.list {
or {
between('created', now - 10, now)
transactions {
between('date', now - 10, now)
}
}
}Querying with Projections
Projections may be used to customise the results. Define a "projections" node within the criteria builder tree to use projections. There are equivalent methods within the projections node to the methods found in the Hibernate Projections class:def c = Account.createCriteria()def numberOfBranches = c.get {
projections {
countDistinct('branch')
}
}Using SQL Restrictions
You can access Hibernate's SQL Restrictions capabilities.def c = Person.createCriteria()def peopleWithShortFirstNames = c.list {
sqlRestriction "char_length(first_name) <= 4"
}Note that the parameter there is SQL. Thefirst_nameattribute referenced in the example refers to the persistence model, not the object model like in HQL queries. ThePersonproperty namedfirstNameis mapped to thefirst_namecolumn in the database and you must refer to that in thesqlRestrictionstring.Also note that the SQL used here is not necessarily portable across databases.
Using Scrollable Results
You can use Hibernate's ScrollableResults feature by calling the scroll method:def results = crit.scroll {
maxResults(10)
}
def f = results.first()
def l = results.last()
def n = results.next()
def p = results.previous()def future = results.scroll(10)
def accountNumber = results.getLong('number')A result iterator that allows moving around within the results by arbitrary increments. The Query / ScrollableResults pattern is very similar to the JDBC PreparedStatement/ ResultSet pattern and the semantics of methods of this interface are similar to the similarly named methods on ResultSet.Contrary to JDBC, columns of results are numbered from zero.
Setting properties in the Criteria instance
If a node within the builder tree doesn't match a particular criterion it will attempt to set a property on the Criteria object itself. This allows full access to all the properties in this class. This example callssetMaxResults and setFirstResult on the Criteria instance:import org.hibernate.FetchMode as FM … def results = c.list { maxResults(10) firstResult(50) fetchMode("aRelationship", FM.JOIN) }
Querying with Eager Fetching
In the section on Eager and Lazy Fetching we discussed how to declaratively specify fetching to avoid the N+1 SELECT problem. However, this can also be achieved using a criteria query:def criteria = Task.createCriteria()
def tasks = criteria.list{
eq "assignee.id", task.assignee.id
join 'assignee'
join 'project'
order 'priority', 'asc'
}join method: it tells the criteria API to use a JOIN to fetch the named associations with the Task instances. It's probably best not to use this for one-to-many associations though, because you will most likely end up with duplicate results. Instead, use the 'select' fetch mode:
import org.hibernate.FetchMode as FM … def results = Airport.withCriteria { eq "region", "EMEA" fetchMode "flights", FM.SELECT }
flights association, you will get reliable results - even with the maxResults option.An important point to bear in mind is that if you include associations in the query constraints, those associations will automatically be eagerly loaded. For example, in this query:fetchModeandjoinare general settings of the query and can only be specified at the top-level, i.e. you cannot use them inside projections or association constraints.
def results = Airport.withCriteria {
eq "region", "EMEA"
flights {
like "number", "BA%"
}
}flights collection would be loaded eagerly via a join even though the fetch mode has not been explicitly set.Method Reference
If you invoke the builder with no method name such as:c { … }c.list { … }| Method | Description |
|---|---|
| list | This is the default method. It returns all matching rows. |
| get | Returns a unique result set, i.e. just one row. The criteria has to be formed that way, that it only queries one row. This method is not to be confused with a limit to just the first row. |
| scroll | Returns a scrollable result set. |
| listDistinct | If subqueries or associations are used, one may end up with the same row multiple times in the result set, this allows listing only distinct entities and is equivalent to DISTINCT_ROOT_ENTITY of the CriteriaSpecification class. |
| count | Returns the number of matching rows. |
5.4.3 Hibernate Query Language (HQL)
GORM classes also support Hibernate's query language HQL, a very complete reference for which can be found in the Hibernate documentation of the Hibernate documentation.GORM provides a number of methods that work with HQL including find, findAll and executeQuery. An example of a query can be seen below:def results =
Book.findAll("from Book as b where b.title like 'Lord of the%'")Positional and Named Parameters
In this case the value passed to the query is hard coded, however you can equally use positional parameters:def results =
Book.findAll("from Book as b where b.title like ?", ["The Shi%"])def author = Author.findByName("Stephen King") def books = Book.findAll("from Book as book where book.author = ?", [author])
def results =
Book.findAll("from Book as b " +
"where b.title like :search or b.author like :search",
[search: "The Shi%"])def author = Author.findByName("Stephen King") def books = Book.findAll("from Book as book where book.author = :author", [author: author])
Multiline Queries
Use the line continuation character to separate the query across multiple lines:def results = Book.findAll("\
from Book as b, \
Author as a \
where b.author = a and a.surname = ?", ['Smith'])Triple-quoted Groovy multiline Strings will NOT work with HQL queries.
Pagination and Sorting
You can also perform pagination and sorting whilst using HQL queries. To do so simply specify the pagination options as a Map at the end of the method call and include an "ORDER BY" clause in the HQL:def results =
Book.findAll("from Book as b where " +
"b.title like 'Lord of the%' " +
"order by b.title asc",
[max: 10, offset: 20])5.5 Advanced GORM Features
The following sections cover more advanced usages of GORM including caching, custom mapping and events.5.5.1 Events and Auto Timestamping
GORM supports the registration of events as methods that get fired when certain events occurs such as deletes, inserts and updates. The following is a list of supported events:beforeInsert- Executed before an object is initially persisted to the databasebeforeUpdate- Executed before an object is updatedbeforeDelete- Executed before an object is deletedbeforeValidate- Executed before an object is validatedafterInsert- Executed after an object is persisted to the databaseafterUpdate- Executed after an object has been updatedafterDelete- Executed after an object has been deletedonLoad- Executed when an object is loaded from the database
Do not attempt to flush the session within an event (such as with obj.save(flush:true)). Since events are fired during flushing this will cause a StackOverflowError.
Event types
The beforeInsert event
Fired before an object is saved to the databaseclass Person {
Date dateCreated def beforeInsert() {
dateCreated = new Date()
}
}The beforeUpdate event
Fired before an existing object is updatedclass Person {
Date dateCreated
Date lastUpdated def beforeInsert() {
dateCreated = new Date()
}
def beforeUpdate() {
lastUpdated = new Date()
}
}The beforeDelete event
Fired before an object is deleted.class Person {
String name
Date dateCreated
Date lastUpdated def beforeDelete() {
ActivityTrace.withNewSession {
new ActivityTrace(eventName:"Person Deleted",data:name).save()
}
}
}withNewSession method above. Since events are triggered whilst Hibernate is flushing using persistence methods like save() and delete() won't result in objects being saved unless you run your operations with a new Session.Fortunately the withNewSession method lets you share the same transactional JDBC connection even though you're using a different underlying Session.The beforeValidate event
Fired before an object is validated.class Person {
String name static constraints = {
name size: 5..45
} def beforeValidate() {
name = name?.trim()
}
}beforeValidate method is run before any validators are run.GORM supports an overloaded version of beforeValidate which accepts a List parameter which may include
the names of the properties which are about to be validated. This version of beforeValidate will be called
when the validate method has been invoked and passed a List of property names as an argument.class Person {
String name
String town
Integer age static constraints = {
name size: 5..45
age range: 4..99
} def beforeValidate(List propertiesBeingValidated) {
// do pre validation work based on propertiesBeingValidated
}
}def p = new Person(name: 'Jacob Brown', age: 10)
p.validate(['age', 'name'])Note that whenEither or both versions ofvalidateis triggered indirectly because of a call to thesavemethod that thevalidatemethod is being invoked with no arguments, not aListthat includes all of the property names.
beforeValidate may be defined in a domain class. GORM will
prefer the List version if a List is passed to validate but will fall back on the
no-arg version if the List version does not exist. Likewise, GORM will prefer the
no-arg version if no arguments are passed to validate but will fall back on the
List version if the no-arg version does not exist. In that case, null is passed to beforeValidate.The onLoad/beforeLoad event
Fired immediately before an object is loaded from the database:class Person {
String name
Date dateCreated
Date lastUpdated def onLoad() {
log.debug "Loading ${id}"
}
}beforeLoad() is effectively a synonym for onLoad(), so only declare one or the other.The afterLoad event
Fired immediately after an object is loaded from the database:class Person {
String name
Date dateCreated
Date lastUpdated def afterLoad() {
name = "I'm loaded"
}
}Custom Event Listeners
You can also register event handler classes in an application'sgrails-app/conf/spring/resources.groovy or in the doWithSpring closure in a plugin descriptor by registering a Spring bean named hibernateEventListeners. This bean has one property, listenerMap which specifies the listeners to register for various Hibernate events.The values of the Map are instances of classes that implement one or more Hibernate listener interfaces. You can use one class that implements all of the required interfaces, or one concrete class per interface, or any combination. The valid Map keys and corresponding interfaces are listed here:AuditEventListener which implements PostInsertEventListener, PostUpdateEventListener, and PostDeleteEventListener using the following in an application:beans = { auditListener(AuditEventListener) hibernateEventListeners(HibernateEventListeners) {
listenerMap = ['post-insert': auditListener,
'post-update': auditListener,
'post-delete': auditListener]
}
}def doWithSpring = { auditListener(AuditEventListener) hibernateEventListeners(HibernateEventListeners) {
listenerMap = ['post-insert': auditListener,
'post-update': auditListener,
'post-delete': auditListener]
}
}Automatic timestamping
The examples above demonstrated using events to update alastUpdated and dateCreated property to keep track of updates to objects. However, this is actually not necessary. By defining a lastUpdated and dateCreated property these will be automatically updated for you by GORM.If this is not the behaviour you want you can disable this feature with:class Person {
Date dateCreated
Date lastUpdated
static mapping = {
autoTimestamp false
}
}If you putnullable: falseconstraints on eitherdateCreatedorlastUpdated, your domain instances will fail validation - probably not what you want. Leave constraints off these properties unless you have disabled automatic timestamping.
5.5.2 Custom ORM Mapping
Grails domain classes can be mapped onto many legacy schemas with an Object Relational Mapping DSL (domain specific language). The following sections takes you through what is possible with the ORM DSL.None of this is necessary if you are happy to stick to the conventions defined by GORM for table names, column names and so on. You only needs this functionality if you need to tailor the way GORM maps onto legacy schemas or configures cachingCustom mappings are defined using a a static
mapping block defined within your domain class:class Person {
…
static mapping = { }
}grails.gorm.default.mapping = { version false autoTimestamp false }
5.5.2.1 Table and Column Names
Table names
The database table name which the class maps to can be customized using thetable method:class Person {
…
static mapping = {
table 'people'
}
}people instead of the default name of person.Column names
It is also possible to customize the mapping for individual columns onto the database. For example to change the name you can do:class Person { String firstName static mapping = {
table 'people'
firstName column: 'First_Name'
}
}firstName is a dynamic method within the mapping Closure that has a single Map parameter. Since its name corresponds to a domain class persistent field, the parameter values (in this case just "column") are used to configure the mapping for that property.Column type
GORM supports configuration of Hibernate types with the DSL using the type attribute. This includes specifing user types that implement the Hibernate org.hibernate.usertype.UserType interface, which allows complete customization of how a type is persisted. As an example if you had aPostCodeType you could use it as follows:class Address { String number
String postCode static mapping = {
postCode type: PostCodeType
}
}class Address { String number
String postCode static mapping = {
postCode type: 'text'
}
}postCode column map to the default large-text type for the database you're using (for example TEXT or CLOB).See the Hibernate documentation regarding Basic Types for further information.Many-to-One/One-to-One Mappings
In the case of associations it is also possible to configure the foreign keys used to map associations. In the case of a many-to-one or one-to-one association this is exactly the same as any regular column. For example consider the following:class Person { String firstName
Address address static mapping = {
table 'people'
firstName column: 'First_Name'
address column: 'Person_Address_Id'
}
}address association would map to a foreign key column called address_id. By using the above mapping we have changed the name of the foreign key column to Person_Adress_Id.One-to-Many Mapping
With a bidirectional one-to-many you can change the foreign key column used by changing the column name on the many side of the association as per the example in the previous section on one-to-one associations. However, with unidirectional associations the foreign key needs to be specified on the association itself. For example given a unidirectional one-to-many relationship betweenPerson and Address the following code will change the foreign key in the address table:class Person { String firstName static hasMany = [addresses: Address] static mapping = {
table 'people'
firstName column: 'First_Name'
addresses column: 'Person_Address_Id'
}
}address table, but instead some intermediate join table you can use the joinTable parameter:class Person { String firstName static hasMany = [addresses: Address] static mapping = {
table 'people'
firstName column: 'First_Name'
addresses joinTable: [name: 'Person_Addresses',
key: 'Person_Id',
column: 'Address_Id']
}
}Many-to-Many Mapping
Grails, by default maps a many-to-many association using a join table. For example consider this many-to-many association:class Group {
…
static hasMany = [people: Person]
}class Person {
…
static belongsTo = Group
static hasMany = [groups: Group]
}group_person containing foreign keys called person_id and group_id referencing the person and group tables. To change the column names you can specify a column within the mappings for each class.class Group {
…
static mapping = {
people column: 'Group_Person_Id'
}
}
class Person {
…
static mapping = {
groups column: 'Group_Group_Id'
}
}class Group {
…
static mapping = {
people column: 'Group_Person_Id',
joinTable: 'PERSON_GROUP_ASSOCIATIONS'
}
}
class Person {
…
static mapping = {
groups column: 'Group_Group_Id',
joinTable: 'PERSON_GROUP_ASSOCIATIONS'
}
}5.5.2.2 Caching Strategy
Setting up caching
Hibernate features a second-level cache with a customizable cache provider. This needs to be configured in thegrails-app/conf/DataSource.groovy file as follows:hibernate {
cache.use_second_level_cache=true
cache.use_query_cache=true
cache.provider_class='org.hibernate.cache.EhCacheProvider'
}For further reading on caching and in particular Hibernate's second-level cache, refer to the Hibernate documentation on the subject.
Caching instances
Call thecache method in your mapping block to enable caching with the default settings:class Person {
…
static mapping = {
table 'people'
cache true
}
}class Person {
…
static mapping = {
table 'people'
cache usage: 'read-only', include: 'non-lazy'
}
}Caching associations
As well as the ability to use Hibernate's second level cache to cache instances you can also cache collections (associations) of objects. For example:class Person { String firstName static hasMany = [addresses: Address] static mapping = {
table 'people'
version false
addresses column: 'Address', cache: true
}
}class Address {
String number
String postCode
}addresses collection. You can also use:cache: 'read-write' // or 'read-only' or 'transactional'
Caching Queries
You can cache queries such as dynamic finders and criteria. To do so using a dynamic finder you can pass thecache argument:def person = Person.findByFirstName("Fred", [cache: true])
In order for the results of the query to be cached, you must enable caching in your mapping as discussed in the previous section.You can also cache criteria queries:
def people = Person.withCriteria {
like('firstName', 'Fr%')
cache true
}Cache usages
Below is a description of the different cache settings and their usages:read-only- If your application needs to read but never modify instances of a persistent class, a read-only cache may be used.read-write- If the application needs to update data, a read-write cache might be appropriate.nonstrict-read-write- If the application only occasionally needs to update data (ie. if it is very unlikely that two transactions would try to update the same item simultaneously) and strict transaction isolation is not required, anonstrict-read-writecache might be appropriate.transactional- Thetransactionalcache strategy provides support for fully transactional cache providers such as JBoss TreeCache. Such a cache may only be used in a JTA environment and you must specifyhibernate.transaction.manager_lookup_classin thegrails-app/conf/DataSource.groovyfile'shibernateconfig.
5.5.2.3 Inheritance Strategies
By default GORM classes usetable-per-hierarchy inheritance mapping. This has the disadvantage that columns cannot have a NOT-NULL constraint applied to them at the database level. If you would prefer to use a table-per-subclass inheritance strategy you can do so as follows:class Payment {
Integer amount static mapping = {
tablePerHierarchy false
}
}class CreditCardPayment extends Payment {
String cardNumber
}Payment class specifies that it will not be using table-per-hierarchy mapping for all child classes.
5.5.2.4 Custom Database Identity
You can customize how GORM generates identifiers for the database using the DSL. By default GORM relies on the native database mechanism for generating ids. This is by far the best approach, but there are still many schemas that have different approaches to identity.To deal with this Hibernate defines the concept of an id generator. You can customize the id generator and the column it maps to as follows:class Person {
…
static mapping = {
table 'people'
version false
id generator: 'hilo',
params: [table: 'hi_value',
column: 'next_value',
max_lo: 100]
}
}For more information on the different Hibernate generators refer to the Hibernate reference documentationAlthough you don't typically specify the
id field (Grails adds it for you) you can still configure its mapping like the other properties. For example to customise the column for the id property you can do:class Person {
…
static mapping = {
table 'people'
version false
id column: 'person_id'
}
}5.5.2.5 Composite Primary Keys
GORM supports the concept of composite identifiers (identifiers composed from 2 or more properties). It is not an approach we recommend, but is available to you if you need it:import org.apache.commons.lang.builder.HashCodeBuilderclass Person implements Serializable { String firstName String lastName boolean equals(other) { if (!(other instanceof Person)) { return false } other.firstName == firstName && other.lastName == lastName } int hashCode() { def builder = new HashCodeBuilder() builder.append firstName builder.append lastName builder.toHashCode() } static mapping = { id composite: ['firstName', 'lastName'] } }
firstName and lastName properties of the Person class. To retrieve an instance by id you use a prototype of the object itself:def p = Person.get(new Person(firstName: "Fred", lastName: "Flintstone")) println p.firstName
Serializable interface and override the equals and hashCode methods, using the properties in the composite key for the calculations. The example above uses a HashCodeBuilder for convenience but it's fine to implement it yourself.Another important consideration when using composite primary keys is associations. If for example you have a many-to-one association where the foreign keys are stored in the associated table then 2 columns will be present in the associated table.For example consider the following domain class:class Address {
Person person
}address table will have an additional two columns called person_first_name and person_last_name. If you wish the change the mapping of these columns then you can do so using the following technique:class Address {
Person person
static mapping = {
person {
column: "FirstName"
column: "LastName"
}
}
}5.5.2.6 Database Indices
To get the best performance out of your queries it is often necessary to tailor the table index definitions. How you tailor them is domain specific and a matter of monitoring usage patterns of your queries. With GORM's DSL you can specify which columns are used in which indexes:class Person {
String firstName
String address
static mapping = {
table 'people'
version false
id column: 'person_id'
firstName column: 'First_Name', index: 'Name_Idx'
address column: 'Address', index: 'Name_Idx,Address_Index'
}
}index attribute; in this example index:'Name_Idx, Address_Index' will cause an error.
5.5.2.7 Optimistic Locking and Versioning
As discussed in the section on Optimistic and Pessimistic Locking, by default GORM uses optimistic locking and automatically injects aversion property into every class which is in turn mapped to a version column at the database level.If you're mapping to a legacy schema that doesn't have version columns (or there's some other reason why you don't want/need this feature) you can disable this with the version method:class Person {
…
static mapping = {
table 'people'
version false
}
}If you disable optimistic locking you are essentially on your own with regards to concurrent updates and are open to the risk of users losing data (due to data overriding) unless you use pessimistic locking
Version columns types
By default Grails maps theversion property as a Long that gets incremented by one each time an instance is updated. But Hibernate also supports using a Timestamp, for example:import java.sql.Timestampclass Person { … Timestamp version static mapping = { table 'people' } }
Timestamp instead of a Long is that you combine the optimistic locking and last-updated semantics into a single column.
5.5.2.8 Eager and Lazy Fetching
Lazy Collections
As discussed in the section on Eager and Lazy fetching, GORM collections are lazily loaded by default but you can change this behaviour with the ORM DSL. There are several options available to you, but the most common ones are:- lazy: false
- fetch: 'join'
class Person { String firstName
Pet pet static hasMany = [addresses: Address] static mapping = {
addresses lazy: false
pet fetch: 'join'
}
}class Address {
String street
String postCode
}class Pet {
String name
}lazy: false , ensures that when a Person instance is loaded, its addresses collection is loaded at the same time with a second SELECT. The second option is basically the same, except the collection is loaded with a JOIN rather than another SELECT. Typically you want to reduce the number of queries, so fetch: 'join' is the more appropriate option. On the other hand, it could feasibly be the more expensive approach if your domain model and data result in more and larger results than would otherwise be necessary.For more advanced users, the other settings available are:
- batchSize: N
- lazy: false, batchSize: N
Person:class Person { String firstName
Pet pet static mapping = {
pet batchSize: 5
}
}Person instances, then when we access the first pet property, Hibernate will fetch that Pet plus the four next ones. You can get the same behaviour with eager loading by combining batchSize with the lazy: false option. You can find out more about these options in the Hibernate user guide and this primer on fetching strategies. Note that ORM DSL does not currently support the "subselect" fetching strategy.Lazy Single-Ended Associations
In GORM, one-to-one and many-to-one associations are by default lazy. Non-lazy single ended associations can be problematic when you load many entities because each non-lazy association will result in an extra SELECT statement. If the associated entities also have non-lazy associations, the number of queries grows significantly!Use the same technique as for lazy collections to make a one-to-one or many-to-one association non-lazy/eager:class Person {
String firstName
}class Address { String street
String postCode static belongsTo = [person: Person] static mapping = {
person lazy: false
}
}Person instance (through the person property) whenever an Address is loaded.Lazy Single-Ended Associations and Proxies
Hibernate uses runtime-generated proxies to facilitate single-ended lazy associations; Hibernate dynamically subclasses the entity class to create the proxy.Consider the previous example but with a lazily-loadedperson association: Hibernate will set the person property to a proxy that is a subclass of Person. When you call any of the getters (except for the id property) or setters on that proxy, Hibernate will load the entity from the database.Unfortunately this technique can produce surprising results. Consider the following example classes:class Pet {
String name
}class Dog extends Pet {
}class Person {
String name
Pet pet
}Person instance with a Dog as the pet. The following code will work as you would expect:
def person = Person.get(1) assert person.pet instanceof Dog assert Pet.get(person.petId) instanceof Dog
def person = Person.get(1) assert person.pet instanceof Dog assert Pet.list()[0] instanceof Dog
assert Pet.list()[0] instanceof DogPerson instance, Hibernate creates a proxy for its pet relation and attaches it to the session. Once that happens, whenever you retrieve that Pet instance with a query, a get(), or the pet relation within the same session , Hibernate gives you the proxy.Fortunately for us, GORM automatically unwraps the proxy when you use get() and findBy*(), or when you directly access the relation. That means you don't have to worry at all about proxies in the majority of cases. But GORM doesn't do that for objects returned with a query that returns a list, such as list() and findAllBy*(). However, if Hibernate hasn't attached the proxy to the session, those queries will return the real instances - hence why the last example works.You can protect yourself to a degree from this problem by using the instanceOf method by GORM:def person = Person.get(1) assert Pet.list()[0].instanceOf(Dog)
ClassCastException because the first pet in the list is a proxy instance with a class that is neither Dog nor a sub-class of Dog:def person = Person.get(1) Dog pet = Pet.list()[0]
Dog properties or methods on the instance without any problems.These days it's rare that you will come across this issue, but it's best to be aware of it just in case. At least you will know why such an error occurs and be able to work around it.
5.5.2.9 Custom Cascade Behaviour
As described in the section on cascading updates, the primary mechanism to control the way updates and deletes cascade from one association to another is the static belongsTo property.However, the ORM DSL gives you complete access to Hibernate's transitive persistence capabilities using thecascade attribute.Valid settings for the cascade attribute include:
merge- merges the state of a detached associationsave-update- cascades only saves and updates to an associationdelete- cascades only deletes to an associationlock- useful if a pessimistic lock should be cascaded to its associationsrefresh- cascades refreshes to an associationevict- cascades evictions (equivalent todiscard()in GORM) to associations if setall- cascade all operations to associationsall-delete-orphan- Applies only to one-to-many associations and indicates that when a child is removed from an association then it should be automatically deleted. Children are also deleted when the parent is.
It is advisable to read the section in the Hibernate documentation on transitive persistence to obtain a better understanding of the different cascade styles and recommendations for their usageTo specify the cascade attribute simply define one or more (comma-separated) of the aforementioned settings as its value:
class Person { String firstName static hasMany = [addresses: Address] static mapping = {
addresses cascade: "all-delete-orphan"
}
}class Address {
String street
String postCode
}5.5.2.10 Custom Hibernate Types
You saw in an earlier section that you can use composition (with theembedded property) to break a table into multiple objects. You can achieve a similar effect with Hibernate's custom user types. These are not domain classes themselves, but plain Java or Groovy classes. Each of these types also has a corresponding "meta-type" class that implements org.hibernate.usertype.UserType.The Hibernate reference manual has some information on custom types, but here we will focus on how to map them in Grails. Let's start by taking a look at a simple domain class that uses an old-fashioned (pre-Java 1.5) type-safe enum class:class Book { String title
String author
Rating rating static mapping = {
rating type: RatingUserType
}
}rating field the enum type and set the property's type in the custom mapping to the corresponding UserType implementation. That's all you have to do to start using your custom type. If you want, you can also use the other column settings such as "column" to change the column name and "index" to add it to an index.Custom types aren't limited to just a single column - they can be mapped to as many columns as you want. In such cases you explicitly define in the mapping what columns to use, since Hibernate can only use the property name for a single column. Fortunately, Grails lets you map multiple columns to a property using this syntax:class Book { String title
Name author
Rating rating static mapping = {
name type: NameUserType, {
column name: "first_name"
column name: "last_name"
}
rating type: RatingUserType
}
}author property. You'll be pleased to know that you can also use some of the normal column/property mapping attributes in the column definitions. For example:column name: "first_name", index: "my_idx", unique: true
type, cascade, lazy, cache, and joinTable.One thing to bear in mind with custom types is that they define the SQL types for the corresponding database columns. That helps take the burden of configuring them yourself, but what happens if you have a legacy database that uses a different SQL type for one of the columns? In that case, override the column's SQL type using the sqlType attribute:class Book { String title
Name author
Rating rating static mapping = {
name type: NameUserType, {
column name: "first_name", sqlType: "text"
column name: "last_name", sqlType: "text"
}
rating type: RatingUserType, sqlType: "text"
}
}5.5.2.11 Derived Properties
A derived property is one that takes its value from a SQL expression, often but not necessarily based on the value of one or more other persistent properties. Consider a Product class like this:class Product {
Float price
Float taxRate
Float tax
}tax property is derived based on the value of price and taxRate properties then is probably no need to persist the tax property. The SQL used to derive the value of a derived property may be expressed in the ORM DSL like this:class Product {
Float price
Float taxRate
Float tax static mapping = {
tax formula: 'PRICE * TAX_RATE'
}
}PRICE and TAX_RATE instead of price and taxRate.With that in place, when a Product is retrieved with something like Product.get(42), the SQL that is generated to support that will look something like this:select
product0_.id as id1_0_,
product0_.version as version1_0_,
product0_.price as price1_0_,
product0_.tax_rate as tax4_1_0_,
product0_.PRICE * product0_.TAX_RATE as formula1_0_
from
product product0_
where
product0_.id=?tax property is derived at runtime and not stored in the database it might seem that the same effect could be achieved by adding a method like getTax() to the Product class that simply returns the product of the taxRate and price properties. With an approach like that you would give up the ability query the database based on the value of the tax property. Using a derived property allows exactly that. To retrieve all Product objects that have a tax value greater than 21.12 you could execute a query like this:Product.findAllByTaxGreaterThan(21.12)
Product.withCriteria {
gt 'tax', 21.12f
}select
this_.id as id1_0_,
this_.version as version1_0_,
this_.price as price1_0_,
this_.tax_rate as tax4_1_0_,
this_.PRICE * this_.TAX_RATE as formula1_0_
from
product this_
where
this_.PRICE * this_.TAX_RATE>?Because the value of a derived property is generated in the database and depends on the execution of SQL code, derived properties may not have GORM constraints applied to them. If constraints are specified for a derived property, they will be ignored.
5.5.2.12 Custom Naming Strategy
By default Grails uses Hibernate'sImprovedNamingStrategy to convert domain class Class and field names to SQL table and column names by converting from camel-cased Strings to ones that use underscores as word separators. You can customize these on a per-instance basis in the mapping closure but if there's a consistent pattern you can specify a different NamingStrategy class to use.Configure the class name to be used in grails-app/conf/DataSource.groovy in the hibernate section, e.g.dataSource {
pooled = true
dbCreate = "create-drop"
…
}hibernate {
cache.use_second_level_cache = true
…
naming_strategy = com.myco.myproj.CustomNamingStrategy
}package com.myco.myprojimport org.hibernate.cfg.ImprovedNamingStrategy import org.hibernate.util.StringHelperclass CustomNamingStrategy extends ImprovedNamingStrategy { String classToTableName(String className) { "table_" + StringHelper.unqualify(className) } String propertyToColumnName(String propertyName) { "col_" + StringHelper.unqualify(propertyName) } }
5.5.3 Default Sort Order
You can sort objects using query arguments such as those found in the list method:def airports = Airport.list(sort:'name')
class Airport {
…
static mapping = {
sort "name"
}
}Airports will by default be sorted by the airport name. If you also want to change the sort order , use this syntax:class Airport {
…
static mapping = {
sort name: "desc"
}
}class Airport {
…
static hasMany = [flights: Flight] static mapping = {
flights sort: 'number', order: 'desc'
}
}flights collection will always be sorted in descending order of flight number.
These mappings will not work for default unidirectional one-to-many or many-to-many relationships because they involve a join table. See this issue for more details. Consider using a SortedSet or queries with sort parameters to fetch the data you need.
5.6 Programmatic Transactions
Grails is built on Spring and uses Spring's Transaction abstraction for dealing with programmatic transactions. However, GORM classes have been enhanced to make this simpler with the withTransaction method. This method has a single parameter, a Closure, which has a single parameter which is a Spring TransactionStatus instance.Here's an example of usingwithTransaction in a controller methods:def transferFunds() {
Account.withTransaction { status ->
def source = Account.get(params.from)
def dest = Account.get(params.to) def amount = params.amount.toInteger()
if (source.active) {
if (dest.active) {
source.balance -= amount
dest.amount += amount
}
else {
status.setRollbackOnly()
}
}
}
}Exception or Error (but not a checked Exception, even though Groovy doesn't require that you catch checked exceptions) is thrown during the process the transaction will automatically be rolled back.You can also use "save points" to rollback a transaction to a particular point in time if you don't want to rollback the entire transaction. This can be achieved through the use of Spring's SavePointManager interface.The withTransaction method deals with the begin/commit/rollback logic for you within the scope of the block.
5.7 GORM and Constraints
Although constraints are covered in the Validation section, it is important to mention them here as some of the constraints can affect the way in which the database schema is generated.Where feasible, Grails uses a domain class's constraints to influence the database columns generated for the corresponding domain class properties.Consider the following example. Suppose we have a domain model with the following properties:String name String description
| Column | Data Type |
|---|---|
| name | varchar(255) |
| description | varchar(255) |
| Column | Data Type |
|---|---|
| description | TEXT |
static constraints = {
description maxSize: 1000
}Constraints Affecting String Properties
If either themaxSize or the size constraint is defined, Grails sets the maximum column length based on the constraint value.In general, it's not advisable to use both constraints on the same domain class property. However, if both the maxSize constraint and the size constraint are defined, then Grails sets the column length to the minimum of the maxSize constraint and the upper bound of the size constraint. (Grails uses the minimum of the two, because any length that exceeds that minimum will result in a validation error.)If the inList constraint is defined (and the maxSize and the size constraints are not defined), then Grails sets the maximum column length based on the length of the longest string in the list of valid values. For example, given a list including values "Java", "Groovy", and "C++", Grails would set the column length to 6 (i.e., the number of characters in the string "Groovy").Constraints Affecting Numeric Properties
If themax, min, or range constraint is defined, Grails attempts to set the column precision based on the constraint value. (The success of this attempted influence is largely dependent on how Hibernate interacts with the underlying DBMS.)In general, it's not advisable to combine the pair min/max and range constraints together on the same domain class property. However, if both of these constraints is defined, then Grails uses the minimum precision value from the constraints. (Grails uses the minimum of the two, because any length that exceeds that minimum precision will result in a validation error.)
If the scale constraint is defined, then Grails attempts to set the column scale based on the constraint value. This rule only applies to floating point numbers (i.e., java.lang.Float, java.Lang.Double, java.lang.BigDecimal, or subclasses of java.lang.BigDecimal). The success of this attempted influence is largely dependent on how Hibernate interacts with the underlying DBMS.The constraints define the minimum/maximum numeric values, and Grails derives the maximum number of digits for use in the precision. Keep in mind that specifying only one of min/max constraints will not affect schema generation (since there could be large negative value of property with max:100, for example), unless the specified constraint value requires more digits than default Hibernate column precision is (19 at the moment). For example:someFloatValue max: 1000000, scale: 3
someFloatValue DECIMAL(19, 3) // precision is defaultsomeFloatValue max: 12345678901234567890, scale: 5
someFloatValue DECIMAL(25, 5) // precision = digits in max + scale
someFloatValue max: 100, min: -100000
someFloatValue DECIMAL(8, 2) // precision = digits in min + default scale
