Mostrando entradas con la etiqueta DOMAIN CLASS. Mostrar todas las entradas
Mostrando entradas con la etiqueta DOMAIN CLASS. Mostrar todas las entradas

jueves, 25 de octubre de 2012

Diferencia entre el método addTo y el operador << (leftShift)

Sabemos que cuando creas una clase de dominio en Grails con una relación hasMany, automáticamente te crearán dos métodos para añadir o borrar instancias a la relación (addTo... y removeFrom...)
class Book {
  static hasMany = [authors: Author]

}
Para añadir una instancia author dentro de Book habría dos formas de hacerlo,
def author = Author.get(1)
def book = Book.get(3)

book.addToAuthors(author) //La colección authors ya está inicializada
ó
book.authors << author //La colección tiene que estar inicializada antes con book.authors = []

miércoles, 24 de octubre de 2012

Múltiples datasources

En Grails 2 uno de los plugins que ya vienen instalados es el Multiple Datasources Plugin. Muy interesante si tienes más de una BBDD a la que atacar.
Por defecto todas las clases de dominio atacan a la misma BBDD configurada en tu Config.groovy,
dataSource {
    pooled = true
    driverClassName = "org.h2.Driver"
    username = "sa"
    password = ""
}
hibernate {
    cache.use_second_level_cache = true
    cache.use_query_cache = true
    cache.provider_class = 'net.sf.ehcache.hibernate.EhCacheProvider'
}

environments {
  development {
    dataSource {
      dbCreate = "create-drop" 
      url = "jdbc:h2:mem:devDb" 
    } 
  } 
  test {
    dataSource {
      dbCreate = "update"
      url = "jdbc:h2:mem:testDb"
    }
  }
  production {
    dataSource {
      dbCreate = "update"
      url = "jdbc:h2:prodDb" 
    }
  } 
}

Si quieres configurar otra BBDD, la tendrás que definir al mismo nivel que dataSource, sólo que cambiándole el nombre, en este caso dataSource_lookup,
dataSource {
    pooled = true
    driverClassName = "org.h2.Driver"
    username = "sa"
    password = ""
}
hibernate {
    cache.use_second_level_cache = true
    cache.use_query_cache = true
    cache.provider_class = 'net.sf.ehcache.hibernate.EhCacheProvider'
}

environments {
  development {
    dataSource {
      dbCreate = "create-drop" 
      url = "jdbc:h2:mem:devDb" 
    }
    dataSource_lookup {
            dialect = org.hibernate.dialect.MySQLInnoDBDialect
            driverClassName = 'com.mysql.jdbc.Driver'
            username = 'lookup'
            password = 'secret'
            url = 'jdbc:mysql://localhost/lookup'
            dbCreate = 'update'
    } 
  } 
  test {
    dataSource {
      dbCreate = "update"
      url = "jdbc:h2:mem:testDb"
    }
  }
  production {
    dataSource {
      dbCreate = "update"
      url = "jdbc:h2:prodDb" 
    }
  } 
}

El siguiente paso sería configurar las clases de dominio. Habría varias posibilidades:
- Que ataque a la nueva BBDD
class ZipCode {

  String code

  static mapping = { datasource 'lookup' } 
}
- Que ataque a varias BBDD
class ZipCode {

  String code

  static mapping = { datasources(['lookup', 'auditing']) } 
}
- Que ataque a varias BBDD, entre ellas la de por defecto
class ZipCode {

  String code

  static mapping = { datasources(['lookup', 'DEFAULT']) } 
}
- Que ataque a todas las BBDD
class ZipCode {

  String code

  static mapping = { datasource 'ALL' } 
}

Luego a la hora de hacer save, delete o cualquier método GORM, tendremos que construirlo con un namespace para decirle a qué BBDD tiene que ir.
def zipCode = ZipCode.auditing.get(42)
…
zipCode.auditing.save()

En el ejemplo anterior, le estaremos diciendo que la instancia de la clase ZipCode tiene que hacer el get y el save en la BBDD "auditing".

martes, 23 de octubre de 2012

El argumento "failOnError"

A veces cuando hacemos un save de una clase de dominio, no sabemos por qué no se han persistido los datos en la BBDD. La mayoría de las veces es porque se nos olvida que todas las clases de dominio tienen una constraint implícita con un nullable: false.
def book = new Book(params)
if (!book.save()) {
    // Save falla!
    ...
}
Para solucionarlo y ver más claro donde ha fallado podemos utilizar el argumento "failOnError". De esta manera cuando haya un error en la validación, nos lanzará una excepción para verlo más claramente.
book.save(failOnError: true)
También se puede definir de forma general para que lo haga con todos los "save" lanzados en la aplicación, configurándolo en nuestro grails-app/conf/Config.groovy,
grails.gorm.failOnError = true
También podemos presentar los errores de la siguiente manera,
def book = new Book(params)
if (!book.save() && book.hasErrors()) {
    // Save falla! Presenta los errores.
    book.errors.allErrors.each{ println it }
    ...
}

jueves, 4 de octubre de 2012

Todos las opciones del database mapping

En esta entrada trataremos de explicar todas las features que tiene Grails 2 para customizar el mapeo con la base de datos.
Primero de todo diremos que todo esto se puede definir en dos sitios:
- De una manera específica en una clase de dominio
 
 (static mapping = { ... })

-De forma general definiendolo en el Config.groovy con
 
grails.gorm.default.mapping = { ... }


1) AUTOIMPORT
Permite importar o no la clase de dominio en las queries HQL. Por defecto está a true, es decir las clases de dominio por defecto son auto-importadas en las queries HQL, así que sólo con poner el nombre ya es suficiente. Si ponemos esta característica a false, para formar una query habrá que incluir su paquete también para poder identificar la clase de dominio. Esto es una buena solución para cuando tienes dos clases de dominio con el mismo nombre en distintos paquetes y te da un org.hibernate.DuplicateMappingException.
static mapping = {
        autoImport false
    }

2) AUTO TIMESTAMP
Habilitar o no el auto Timestamping, es decir, cuando queremos guardar su dateCreated y su lastUpdated de una clase de dominio automáticamente deberemos tenerlo a true. Si lo quieres deshabilitado para guardar tus propias fechas ponerlo a false.
static mapping = {
        autoTimestamp false
    }

3) BATCH SIZE
Customizar cuántos resultados queremos cuando capturamos datos de la BBDD en una carga lazy.
static mapping = {
        batchSize 10
    }

4) CACHE
Habilitar la cache de segundo nivel de Hibernate.
static mapping = {
        cache true
    }

5) CACHE
Configurar el comportamiento en cascada de la asociación.
static hasMany = [books: Book]

static mapping = { books cascade: 'all-delete-orphan' }

Aquí podemos definir uno o varios comportamientos separados por coma. Hay varios tipos: all, merge, save-update, delete, lock, refresh, evict, replicate or all-delete-orphan (sólo para relaciones one-to-many)
GORM por defecto configura la política de cascada save-update.

6) COLUMN
Customiza la definición de una columna.
static mapping = {
        currency column: "currency", sqlType: "char", length: 3
    }

Primero se define el nombre del atributo seguido de los siguientes parámetros a cambiar:
- column - nombre de la columna como String
- sqlType (optional) - tipo SQL
- enumType (optional) - tipo de enumerado para las propiedades Enum. (ordinal o string)
- index (optional) - nombre del índice
- unique (optional) - si es único
- length (optional) - longitud de la columna
- precision (optional) - nº de posiciones de la parte entera de la columna
- scale (optional) - nº de decimales de la columna

7) DATASOURCE
Configura qué datasource está relacionada la clase de dominio.
static mapping = {
      datasource 'lookup'
   }
environments {
    development {
        dataSource {
            dbCreate = "create-drop"
            url = "jdbc:h2:mem:devDb"
        }
        dataSource_lookup {
            dialect = org.hibernate.dialect.MySQLInnoDBDialect
            driverClassName = 'com.mysql.jdbc.Driver'
            username = 'lookup'
            password = 'secret'
            url = 'jdbc:mysql://localhost/lookup'
            dbCreate = 'update'
        }
    }
}
Si una clase de dominio no tiene una configuración de DataSource, tirará por defecto del DataSource configurado en el Config.groovy.

8) DISCRIMINATOR
Customiza el discriminador de columna cuando se mapea una herencia tipo "table-per-subclass".
class PodCast extends Content {
    …
    static mapping = {
        discriminator "audio"
    }
}

9) DYNAMIC INSERT
Para construir dinamicamente queries INSERT.
static mapping = {
        dynamicInsert true
    }

Por defecto Hibernate genera todas las queries al principio y las cachea. Esto ayuda a que el rendimiento para los insert, update y delete queries no tengan que ser generadas dinámicamente en tiempo de ejecución. Sin embargo, hay ciertas circunstancias donde las queries dinámicas son útiles.

10) DYNAMIC UPDATE
Para construir dinamicamente queries UPDATE.
static mapping = {
        dynamicUpdate true
    }

11) FETCH
Configura el comportamiento de carga de datos de una asociación.
static hasMany = [books: Book]

static mapping = { books fetch: 'select' }

Se define con el nombre de las asociación seguida del parámetro:
- fetch - Estrategia a usar. (join o select)
Por defecto GORM asume que la carga de asociaciones está hecha con una SELECT cuando se accede a la asociación. Si prefieres que la asociación sea cargada de modo eager, puedes cambiarlo sobreescribiendo:
static hasMany = [books: Book]

static mapping = { books fetch: 'join' }

12) ID
Customizar la forma en que el identificador es generado para un dominio.
static mapping = {
        id generator: 'hilo',
           params: [table: 'hi_value', column: 'next_value', max_lo: 100]
    }

Se define poniendo el id seguido de los siguientes parámetros:
- generator (optional) - nombre del generador a usar. Puede ser increment, identity, sequence, hilo, seqhilo, uuid, guid, native, assigned, select, foreign o sequence-identity.
- composite (optional) - coge una lista de propiedades a usar para formar la clave compuesta.
- name (optional) - nombre de la propiedad a usar como identificador.
- params (optional) - algún parámetro para pasar a un generador de identificadores definido.
- column (optional) - nombre de la columna para mapear el identificador. El resto de propiedades también está disponible.

13) IGNORE NOT FOUND
Especifica cómo las FKs que referencian a filas no encontradas son manejadas en las relaciones many-to-one.
String title 
Thumbnail thumbnail

static mapping = { thumbnail ignoreNotFound: true }

Por defecto el valor es false, es decir si no encuentra la fila Hibernate lanzará una excepción "org.hibernate.ObjectNotFoundException". Si lo tienes a true, aunque no lo encuentre, lo ignorará. Esto viene bien cuando tienes BBDD corruptas que le van a faltar datos.

14) INDEX COLUMN
Customiza el índice de una colección de tipo Lista o un Mapa.
static hasMany = [matrix: Integer]

static mapping = { matrix indexColumn: [name: "the_matrix", type: Integer] }

Esto se define con el nombre de la asociación seguido de los siguientes parámetros:
- name - nombre de columna como un String
- type (optional) - tipo Hibernate
- sqlType (optional) - tipo SQL
- enumType (optional) - tipo de enumerado para las propiedades Enum. (ordinal o string)
- index (optional) - nombre del índice
- unique (optional) - si es único
- length (optional) - longitud de la columna
- precision (optional) - nº de posiciones de la parte entera de la columna
- scale (optional) - nº de decimales de la columna

15) INSERTABLE / UPDATEABLE
Determina si una propiedad de la BBDD está configurada como insertable/updateable cuando una instancia es persistida.
String title

static belongsTo = [author: Author]

static mapping = { 
              author insertable: false 
              author updateable: false 
}

Se define con el nombre del atributo seguido de insertable/updateable: boolean. Por defecto están a true.

16) JOIN TABLE
Customiza el tipo de join table que se usa para las relaciones unidireccionales de tipo one-to-many, many-to-many y tipos de colección primitivos.
static hasMany = [projects: Project]

static mapping = { 
projects joinTable: [name: 'EMP_PROJ', column: 'PROJECT_ID', key: 'EMPLOYEE_ID'] 
}

Se define con el nombre de la asociación seguido de joinTable y de los siguientes parámetros:
- name - nombre de table
- key (optional) - clave foránea
- column (optional) - columna inversa

17) LAZY
Configura si se usa proxies y carga de datos lazy para las relaciones one-to-one y many-to-one.
static belongsTo = [author: Author]

static mapping = { author lazy: false }

Por defecto, en GORM las asociaciones single-ended son lazy: true es decir cuando se carga una instancia de un dominio, la asociación de ese dominio no es cargada hasta que se accede a ella.
Hibernate crea un proxy dinámico para subclasificar la clase de dominio y sus métodos y propiedades.

18) ORDER
Customizar el orden por defecto de los resultado de las queries.
static mapping = {
        order "desc"
    }

19) SORT
Customizar el atributo de la clase dominio para ordenar los resultados de las queries.
static mapping = {
        sort "releaseDate"
    }

20) TABLE
Customizar el nombre de la BBDD asociada de la clase de dominio.
class Book {
    static mapping = {
        table "book_catalog"
    }
}

También se le puede especificar el schema y el catálogo definiéndolo así,
static mapping = {
    table name: "book_catalog", schema: "dbo", catalog: "CRM"
}

21) TYPE
Para configurar el tipo de Hibernate para una propiedad particular.
String title

static mapping = { title type: "text" }

Esta propiedad viene bien para definir tipos CLOB o TEXT dependiendo del dialecto de la BBDD.

22) VERSION
Se usa para deshabilitar el bloqueo optimista o cambiar la columna que mapea la versión.
static mapping = {
        version false
    }
static mapping = {
        version 'revision_number'
    }

jueves, 9 de agosto de 2012

Herencia en GORM

GORM soporta la herencia tanto de clases abstractas como de entidades persistentes.
class Content {
 String author
}

class BlogEntry extends Content {
 URL url
}
A nivel de base de datos, Grails por defecto usa la estrategia de mapeo "table-per-hierarchy" con una columna discriminandora llamada "class", así que la clase padre Content y su hijo/s (BlogEntry, etc) compartirán la misma tabla.

La estrategia de mapeo "table-per-hierarchy" tiene un punto débil que es que no puede tener propiedades non-nullable. Una solución podría ser cambiar la estrategia y ponerla a "table-per-subclass".
class Payment {
Integer amount
static mapping = [ tablePerHierarchy : false ]
}
class CreditCardPayment extends Payment {
String cardNumber
}
Sin embargo, el excesivo uso de la herencia y de la estrategia "table-per-subclass" puede penalizar en el rendimiento por el uso de outer join queries. El consejo es usar la herencia pero no abusar de ella sobre todo si es muy profunda.

miércoles, 8 de agosto de 2012

Entendiendo ignoreNotFound

Sirve para subsanar el error de una referencia perdida de una FK (foreign key borrada) en una base de datos en una relación many-to-one.
Cuando los datos en una base de datos están corruptos y una FK no existe, Hibernate nos dará un mensaje de "No row with the given identifier exists".
Una forma de solucionarlo es poner ignoreNotFound a true para que Hibernate trate la columna perdida como una asociación nula.

*Hay que tener cuidado porque cuando hagamos el save() nos puede dar una excepción por "Missing reference".

Nueva constraint bindable

En Grails 2 se ha introducido una nueva constraint llamada bindable.
Se trata de una constraint que se definirá en la clase de dominio donde le diremos a la propiedad que la tenga si es bindable o no, es decir, que cuando hacemos:
instancia.properties = params 
ó
instancia = new Dominio(params)
NO se hará un set de su propiedad.

martes, 7 de agosto de 2012

Cacheo de Hibernate

Hay tres niveles en Hibernate:
  • Nivel 1 -> se encarga de cachear objetos dentro de una transacción. Activado por defecto en Grails. 
  • Nivel 2 -> se encarga de cachear objetos fuera de las transacciones. Se puede activar de dos formas: 
- De forma general en el archivo DataSource.groovy
hibernate {
          cache.use_second_level_cache=true
          }
- En la clase dominio con
static mapping = {
  cache: true
}
  • Cacheo de queries -> se puede activar también de dos formas:
- En el DataSource.groovy
hibernate {
           cache.use_query_cache=true
          }
- En una query
def person = Person.findByFirstName("Fred", [cache: true])

Querying con GORM

Hay varias formas de tirar una query con GORM:
  • Dynamic finders
  • HQL
  • Criteria
  • Where queries (NUEVO EN GRAILS 2)
  • Detached criteria (NUEVO EN GRAILS 2)

Eager vs Lazy

- Las asociaciones en GORM por defecto son lazy (lazy : true).
- El problema es que con estas asociaciones se comete el error de las N+1 queries.
- Se puede cambiar el tipo de estrategia a Eager (lazy : false) pero con unas curiosidades:
  • lazy : false (sólo para relaciones one-to-many)
  • lazy : false, fetch : 'join' (sólo para relaciones one-to-one y many-to-one)
- También se puede hacer el fetching por lotes, tanto con eager como con lazy
  • lazy : false, batchSize : 10
  • batchSize : 10

Entendiendo los borrados y las actualizaciones en cascada

El tipo de cascada lo dará el belongsTo y el tipo de relación:
  • con belongsTo
          one-to-one             ACTUALIZACIONES EN CASCADA
          one-to-many          (desde la clase propietaria hasta su dependencia)
          many-to-many

          one-to-one             BORRADOS EN CASCADA

          one-to-many          (desde la clase propietaria hasta su dependencia)
          many-to-one

  • sin belongsTo         NO HABRA CASCADAS
          * Tendrás que salvar cada objeto manualmente excepto en las relaciones one-to-many que los "saves" serán automáticamente en cascada si una instancia está en una colección hasMany.

belongsTo

Hay dos formas de poner un belongsTo:
  • sin Mapa
static belongsTo = Author
* Hace que la relación tenga cascada simple.
  • con Mapa
static belongsTo = [author : Author]
* Hace que la relación tenga cascada múltiple y que sea bidireccional.

Relaciones

Hay varios tipos de relaciones que se pueden hacer en GORM:
  • one-to-one (también se utiliza hasOne)
UNIDIRECCIONAL
class Face{
  Nose nose
}

class Nose {

}
BIDIRECCIONAL
class Face{
  Nose nose
}

class Nose { 
  static belongsTo = [face : Face]
}
  • one-to-many / many-to-one
UNIDIRECCIONAL
class Author {
  static hasMany = [books: Book]
class Book {
  
}
* Grails mapea este tipo de relación con una join table.
* ORM permite mapear una relación unidireccional con una FK (foreign key) en la tabla de Author.

BIDIRECCIONAL
class Author {
  static hasMany = [books : Book]
}
class Book {
  static belongsTo = [author : Author]
}
* ORM permite mapear esta relación con una FK (foreign key) en la tabla de Author.
  • many-to-many
class Author {
  static hasMany = [books : Book]
}
class Book {
  static belongsTo = Author //Sin mapa [author : Author] 
  static hasMany = [authors : Author]
}
* belongsTo nos define cual es el propietario de la relación.
* ORM permite mapear este tipo de relaciones con una tabla intermedia con los ids de Author y Book.