(Quick Reference)

5.2 Modelado del dominio en GORM - Reference Documentation

Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith

Version: null

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 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.
Estos se modelan en GORM como clases Groovy, por lo que una clase 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 grails-app/domain/org/bookstore/Book.groovy:
El resultado será una clase en grails-app/domain/org/bookstore/Book.groovy:

package org.bookstore

class Book { }

This class will map automatically to a table in the database called book (the same name as the class). This behaviour is customizable through the ORM Domain Specific Language
A esta clase se corresponde automáticamente una tabla en la base de datos llamada 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.bookstore

class 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 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.
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 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 Face to Nose. To make this relationship bidirectional define the other side as follows:
En este caso tenemos una relación unidireccional varios-a-uno desde 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 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:
En este caso establecemos mediante 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 Face:
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 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 Face instance, the Nose will go too:
Si borramos la instancia 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 hasOne property on the owning side, e.g. Face:
Para hacer que la relación sea verdaderamente uno-a-uno, utilice la propiedad 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 nose table inside a column called face_id. Also, hasOne only works with bidirectional relationships.
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 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 Author, has many instances of a another class, example Book. With Grails you define such a relationship with the hasMany setting:
Una relación uno-a-varios es cuando una clase, por ejemplo 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 instead
El DSL ORM permite mapear relaciones unidireccionales mediante el uso de una clave externa.

Grails will automatically inject a property of type java.util.Set into the domain class based on the hasMany setting. This can be used to iterate over the collection:
Grails inyectara automáticamente una propiedad de tipo 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.

If you need "eager" fetching you can use the ORM DSL or specify eager fetching as part of a query
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 belongsTo is also specified:
El comportamiento predeterminado de la cascada es guardar y actualizar, pero no eliminar a menos que también se especifique 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 mappedBy to specify which the collection is mapped:
Si usted tiene dos propiedades del mismo tipo en el lado varios de uno-a-varios, deberá utilizar 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 hasMany on both sides of the relationship and having a belongsTo on the owned side of the relationship:
Grails tiene soporte para relaciones varios-a-varios mediante la inclusión de 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 Author, takes responsibility for persisting the relationship and is the only side that can cascade saves across.
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, 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 Book and not the authors!
Sin embargo, esto sólo guardaría a 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 yourself
Actualmente 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 nicknames association that is a Set of String instances:
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 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 joinTable argument:
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:

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 the Address class in a separate Groovy file in the grails-app/domain directory you will also get an address table. If you don't want this to happen use Groovy's ability to define múltiple classes per file and include the Address class below the Person class in the grails-app/domain/Person.groovy file
Si la clase Address es definida por separado en un archivo Groovy dentro del directorio grails-app/domain, la tabla address también sera generada. Si no desea que esto suceda, utilice la capacidad de Groovy para definir múltiples clases por archivo e incluya la clase Address debajo de la clase Person en el archivo grails-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 Content class and then various child classes with more specific behaviour.
En el ejemplo anterior tenemos una clase padre 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 class so the parent class (Content) and its subclasses (BlogEntry, Book etc.), share the same table.
A nivel de la base de datos, Grails mapeara una tabla-por-jerarquía e incluirá una columna discriminador llamada 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 Content super class will return all subclasses of Content:
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 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 author

def podCasts = PodCast.list() // list only podcasts

def content = Content.list() // lista todas las entradas de blog, books y podcasts
content = Content.findAllByAuthor('Joe Bloggs') // encontrar todo por author

def podCasts = PodCast.list() // lista únicamente los podcasts

5.2.4 Conjuntos, Listas y Mapas

Sets of Objects

Conjuntos de objetos

By default when you define a relationship with GORM it is a java.util.Set which is an unordered collection that cannot contain duplicates. In other words when you have:
Al definir una relación con GORM, de manera predeterminada sera un 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 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:
La propiedad books que GORM inyecta es un 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 java.util.SortedSet implementation is used which means you must implement java.lang.Comparable in your Book class:
En este caso, se hace uso de la implementación 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 List:
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:

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 books_idx column where it saves the index of the elements in the collection to retain this order at the database level.
A nivel de la base de datos, Hibernate crea una columna 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 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):
Cuando se utiliza un 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 Collection:
El único cambio requerido para esto, es definir el tipo de una colección utilizando 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 Set or a List.
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 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 hasMany property defines the type of the elements within the Map. The keys for the map must be strings.
La propiedad estática 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 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.
El tipo 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 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:
El mismo funcionamiento aplica para el tipo 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 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:
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 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.