Grails colecciones, sort [list, order, sort]
Al utilizar hasMany
por default grails define la propiedad como un Set
, específicamente como un java.util.HashSet
.
Por definición un Set
no permite duplicados y no conserva ningún orden.
class Author {
// books is java.util.HashSet
static hasMany = [books: Book]
}
Para mantener el orden en el que fueron insertados los objetos dentro de la colección, es necesario definir la propiedad como un List
:
class Author {
List books = []
static hasMany = [books: Book]
}
Hibernate crea la columna books_idx
la cual guarda el índice del elemento dentro de la colección.
Cuando utilices un List
, los elementos se deben agregar a la lista antes de ser guardados, de lo contrario se lanzará una excepción (org.hibernate.HibernateException
null index column for collection):
def book = new Book(title: 'Misery')
author.addToBooks(book)
author.save()
Cuando se define una asociación como LinkedHashSet
, no guarda los items que estan en la colección dentro de la bd, por lo tanto no se recomienda utilizar este tipo de colección.
Un bag no garantiza orden ni elementos únicos. Es la colección que mejor performance tiene ya que al agregar un nuevo elemento a la colección, no se necesita cargar toda la colección para verificar que el elemento sea único o para garantizar el orden de inserción:
Utilice esta colección cuando la colección tenga muchos elementos:
class Author {
Collection books
static hasMany = [books: Book]
}
Cada que se agrega un nuevo elemento a un Set
, Hibernate necesita cargar todas las entidades para comparar que el nuevo elemento sea único, lo mismo con List
para asignar el índice del elemento.
Si espera tener muchas entidades se recomienda establecer explicitamente la asociación de la relación bidireccional para envitar el uso de una colección (Para más detalles), o en su defecto utilice un Bag.
El siguiente código obtendrá los libros del author ordenados por nombre, sin embargo todavía no entiendo cómo lo hace ya que author.books
es de tipo PersistentSet
:
class Author {
String name
static hasMany = [books: Book]
static mapping = {
books sort: "title"
}
}
class Book {
String title
static belongsTo = [author: Author]
}
http://docs.grails.org/2.0.1/guide/GORM.html#defaultSortOrder
Sin embargo aunque entienda el problema, la alternativa anterior no es lo más conveniente porque:
one-to-many
y many-to-many
.Set
por lo que al ordenar los elementos en base al resultado del query se está rompiendo con la definición de un Set
lo cual vuelve confuso el código.Por lo tanto, no tiene sentido utilizar sort
en static mapping
cuando se trate de cualquier tipo de asociación:
Set
, no funciona definir un sort
porque al introducir el resultado del query en un Set
se pierde el orden.List
no funciona definir un sort
porque siempre se preserva el orden de los índices de la lista.SortedSet
no tiene caso ya que los elementos serán ordenados a nivel de código sin importar como se obtenga el resultado del query.Las alternativas para ordenar asociaciones son las siguientes:
Si necesita un Sort ordenado de acuerdo a cierta propiedad, utilice SortedSet
e implemente Comparable
en la clase Book
:
class Author {
String name
SortedSet books
static hasMany = [books: Book]
}
class Book implements Comparable<Book> {
String title
static belongsTo = [author: Author]
@Override
int compareTo(Book o) {
return this.title.compareTo(o.title)
}
}
Otra alternativa en vez de utilizar Comparable
es utilizar la anotación @Sortable
la cual marca las clases como comparables y define el método compareTo
en base a las propiedades indicadas. Fue incluida en groovy 2.3 por lo que solo se podrá utilizar en Grails 2.4 o superior:
@Sortable(includes = ['title'])
class Book {
String title
static belongsTo = [author: Author]
}
Con este esquema NO se necesita implementar static mapping sort
sobre la asociación ya que el orden se mantendrá a nivel de código a través del SortedSet
.
Si utiliza SortedSet
, los elementos serán insertados en la base de datos de manera ordenada y obtenidos de la misma manera, si se agregan nuevos elementos a la colección, serán puestos en el lugar que les corresponda de acuerdo a su orden.
Note que books
siempre estará ordenado cuando que se obtengan a través de su author (porque los resultados se introducen en un SortedSet
), sin embargo si hace un query para obtener directamente los books
, los obtendrá en el orden que los obtenga el query (podrían no venir ordenados si inserta registros directamente en la bd).
Si @Sortable
es utilizado en una clase base, si se pone en una clase derivada tomará la anotación de la clase base e ignorará la de la clase derivada. Para poder implementar el orden de una clase derivada cuando su clase base está anotada con @Sortable
, deberá implementar Comparable
y reescribir el método compareTo
. Tal vez cuando se tenga una jerarquía de clases lo mejor es implementar Comparable
desde la clase base y reescribirlo en cada clase hija (si lo requieren) para evitar confusiones.
Otra alternativa para obtener los books ordenados es indicando el sort en el query: Book.list(sort: "title")
Al retornar los books en una lista, tomará el orden indicado en la cláusula sort
.
Se pueden obtener los books de la bd y ordenarlos desde el código fuente:
def books = Book.list()
books.sort {a,b->a.title.compareTo(b.title)}
Para no poner sort
en todos los queries que obtengan una lista del domain class, en este ejemplo Author
o Book
, se puede especificar la propiedad con la que hará sort por default en todos los queries:
class Book {
String title
static mapping = {
sort title: "desc"
}
}
query: ... order by lower(this_.title) desc
También se pueden especificar múltiples propiedades:
class Book {
String title
String isbn
static belongsTo = [author: Author]
static mapping = {
sort([title: "asc", isbn:"asc"])
}
}
query: ... order by lower(this_.title) asc, lower(this_.isbn) asc