viernes, 26 de octubre de 2012

Lo nuevo de Groovy 2.0

Recordaremos primero lo que llevaba Groovy 1.8:

– Mejores DSLs con command chains
 Una sintaxis mejorada en donde puedes eliminar puntos y paréntesis cuando encadenas varias llamadas a métodos.
– Mejoras en el rendimiento en tiempo de ejecución
– GPars está ya agrupado dentro de la distribución de Groovy
– Mejoras en las Closures
 Anotaciones con closures como parámetros y método memoize() en closures para cachear un número de invocaciones.
– Soporte mejorado para la producción, construcción e impresión de JSON
– Nuevas transformaciones AST
 4 loggers diferentes pueden ser inyectados (@Log, @Commons, @Log4j y @Slf4j) o posiblidad de implementar tu propia estrategia.
import groovy.util.logging.*

@Log
class Car{
  Car() {
     log.info 'Car constructed'
  }
}

def car = new Car()
@Field
@AutoClone y @AutoExternalizable
@Canonical
 @toString: para crear un método toString() a tus tipos.
import groovy.transform.ToString

@ToString
class Person {
  String name
  int age
}

println new Person(name:'Edu', age:31)
// => Person(Edu,31)
 @EqualsAndHashCode: para crear una implemetación por defecto de equals() y hashCode()
import groovy.transform.EqualsAndHashCode

@EqualsAndHashCode
class Coord {
  int x, y
}

def c1 = new Coord(x:20,y:10)
def c2 = new Coord(x:20,y:10)

assert c1==c2
assert c1.hashCode()==c2.hashCode()
 @TupleConstructor: crea un clásico constructor con todas las propiedades
import groovy.transform.TupleConstructor

class Person {
  String name
  int age
}

def person = new Person(name:'Edu', age:31)
assert person.name=='Edu'
assert person.age==31
Controlar la ejecución del código para cuando el código entra en un bucle infinito o consume muchos recursos. Se hace con las 3 nuevas anotaciones: @ThreadInterrupt, @TimeInterrupt y @ConditionalInterrupt.
import groovy.transform.ThreadInterrupt

@ThreadInterrupt
void main(){
  while(true){
    if(Thread.currentThread().isInterrupted())
      throw new InterruptedException()
    //eat lots of CPU
  }
}
@InheritConstructor: para que se pinten todos los constructores heredados
@WithReadLock y @WithWriteLock
@ListenerList

Lo nuevo de Groovy 2.0 (NO INCLUIDO EN GRAILS 2):

– Alineaciones con JDK 7: Proyecto Coin (Binary literals, Underscore en literales y Multicatch) y InvokeDynamic ()
//Ejemplo de Multicatch
  try{
      ...
  }catch(IOException | NullPointerException e){
    //Un bloque para tratar 2 excepciones
  }
– Continuan las mejoras de rendimiento en tiempo de ejecución
– Modularidad (todos los jars de Groovy pesan 6 Mb, con un pequeño core (3 Mb) y un pequeño jar por feature)
 console – jsr-223 – test – docgenerator – jmx – testng – groovydoc – sql – json – groovysh – swing – xml – ant – servlet – bsf – templates.
Dejaremos las dos más importantes features para el final: static type checking y static compilation.

@TypeChecked
Puede estar a nivel de clase o de método. Ayuda a comprobar errores de compilación (no en tiempo de ejecución). No todo el tiempo necesitas las features dinámicas. Comprueba tipos en métodos y variables, asignaciones erroneas, tipos devueltos erroneos y tipos heredados.
import  groovy.transform.TypeChecked

void  method() { ... }   

@TypeChecked  
test() {
  // No se puede encontrar el método  metthhoood()        
  metthhoood()          
  def  name  =  "Eduuuuuu"        
  // Variable  naamme  no está declarada       
  println  naamme 
}
La anotación @TypeChecked funciona en tiempo de compilación y no cambia el comportamiento del código. La mayoría de las features dinámicas no pueden ser @TypeChecked. Sin embargo, la metaprogramación funciona en tiempo de compilación.

@CompileStatic
Esto quiere decir que todo el código se compila estáticamente, es decir genera el mismo byte code que si lo compilásemos con javac.
Ventajas que consigues:
- Tipado seguro
- Código más rápido (casi a la altura de Java)
- Código inmune
- Byte code generado más pequeño
Desventajas
- Pierdes las features dinámicas
- Pierdes el método dispatch
import  groovy.transform.CompileStatic  

@CompileStatic
test() { ... }

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 }
    ...
}

lunes, 22 de octubre de 2012

Inyectando beans adicionales

Por convención de Grails, todos los servicios metidos en nuestra carpeta "services" serán inyectados automáticamente por el framework ayudándose de las bondades de Spring.
Pero habrá otros casos en donde definamos un bean en src/groovy o en src/java que vayamos a tener que utilizar en cualquiera de nuestros artefactos más adelante.
Se puede inyectar de dos maneras: usando Spring Bean DSL o por XML.
- Por Spring Bean DSL: modificando el archivo grails-app/conf/spring/resources.groovy y añadiendo el bean dentro de la clousure beans.
import my.company.MyBeanImpl

beans = {
 myBean(MyBeanImpl) { 
   someProperty = 42 
   otherProperty = "blue" 
 } 
}
- Usando XML: modificando el archivo grails-app/conf/spring/resources.xml (lo tienes que crear tu a mano primero) y añadiendo el bean como lo definirías en el applicationContext.xml con Spring.
<bean id="myBean" class="my.company.MyBeanImpl">
    <property name="someProperty" value="42" />
    <property name="otherProperty" value="blue" />
</bean>
Una vez configurado, el bean estará disponible para ser inyectado en cualquiera de los artefactos de Grails, así como en clases que soportan la inyección de depedencias (como puede ser Bootstrap o test de integración)
class ExampleController {

  def myBean … 

}
NOTA: Si defines un bean en el fichero resources.groovy con el mismo nombre que uno que se haya configurado previamente por Grails o por un plugin instalado, tu bean reemplazará al bean previamente registrado. Esto es una forma conveniente de customizar el comportamiento sin recurrir a editar el código del plugin u otra clase que pudiera afectar a la mantenibilidad de la aplicación.

lunes, 8 de octubre de 2012

Consumir servicios web con el Plugin WS-Client

En la entrada anterior veíamos cómo exponer servicios web a través del Plugin CXF de Grails de una manera rápida y sencilla. Ahora nos toca ver cómo consumir un servicio web de un tercero.
Para consumir servicios web, usaremos el Plugin WS-Client basado en GroovyWS.
Lo primero de todo instalaremos el plugin añadiendo esta linea en tu BuildConfig,
compile ":ws-client:1.0"

Ahora sólo tendremos que crear un controlador para consumir ese servicio web con la url del WSDL donde esté publicado.
import org.grails.plugins.wsclient.service.WebService

class ClienteController {
    WebService webService

    def index = {
        def wsdlUrl = "http://ejemplo.com/services/pruebas?wsdl"
        def proxy = webService.getClient(wsdlUrl)
        
        String resul = proxy.saluda("Eylen")
        ...
    }
}
En este ejemplo sólo se ejecuta el método "saluda" del WSDL donde se construye un XML de entrada con un sólo nivel de tipo String y nos devuelve un XML de salida con un sólo nivel también de tipo String.
Habrá XML de salida muchos más complicados que éste, donde habrá que ir navegando por los diferentes niveles consultando la información.
import org.grails.plugins.wsclient.service.WebService

class ClienteController {
    WebService webService

    def index = {
        def wsdlUrl = "http://ejemplo.com/services/pruebas?wsdl"
        def proxy = webService.getClient(wsdlUrl)
        
        def libros = proxy.getLibros()
        libros.libro.each{
           println it.campo
        }
        ...
    }
}
También se puede dar la situación en la que tenemos que construir un XML de entrada con más niveles. Esto se hace construyendo proxies. Tened cuidado con los namespace de los WSDL que puede dar más de un dolor de cabeza. Y también prestad atención si la entrada del WSDL es de tipo String o es una lista.
import org.grails.plugins.wsclient.service.WebService

class ClienteController {
    WebService webService

    def index = {
        def wsdlUrl = "http://ejemplo.com/services/pruebas?wsdl"
        def proxy = webService.getClient(wsdlUrl)

        def libro = proxy.create("defaultnamespace.Libro")

        libro.titulo= "Groovy in Action"
        libro.autor = "Dierk"
        libro.isbn = "123"

        //Detalles es una lista!!!
        def detalle1 = proxy.create("defaultnamespace.Detalles")       
        detalle1.paginas = "150"
        detalle1.indice = "S" 
        libro.detalles << detalle1

        proxy.addLibro(libro)
        ...
    }
}
Para poder debuguear en todos estas construcciones, es mejor que tengais puesto en el Config,
log4j = {
    …
    debug  'org.apache.cxf'
}
Como nota final decir que si teneis instalados en el mismo proyecto los plugins ws-client y cxf puede haber conflictos con la librería cxf. Se recomienda instalar primero el CXF y luego el WS-CLIENT y eliminar de ésta última el jar cxf.

Exponer servicios Grails a través de SOAP con CXF

Una de las facilidades de Grails es que tiene plugins para casi todo. Y uno de los mejores a la hora de exponer servicios web de tipo SOAP es el CXF plug-in for Grails. La única dependencia que teneis que añadir a vuestro BuildConfig es,
compile ":cxf:0.9.0"
Con esta línea, cuando arranqueis vuestra aplicación otra vez, te instalará el plugin. La instalación ya te meterá las librerías necesarias de CXF. La última versión que han sacado ya es compatible para Grails 2.
Para configurarlo se puede hacer de dos formas diferentes:

1. Con la propiedad estática "expose" igual a "cxf"
class TestService {

  static expose=['cxf'] 
  static exclude=["ignoredMethod"]

  boolean serviceMethod(YourDomainClass dc){ return true; }

  boolean ignoredMethod() { return "you shouldn't see me" }

}
Si queremos que algún método del servicio sea ignorado y no se exponga lo declararemos con la propiedad estática "exclude" y el nombre del método.
Todos los métodos que no estén en el exclude serán expuestos y serán accesibles para terceros a través de su url "http://127.0.0.1:8080/nombreApp/services/miServicio?wsdl"
Todas las clases de dominio que sean pasadas o devueltas como parámetros en los servicios web deberán llevar la anotación JAXB @XmlAccessorType(XmlAccessType.FIELD). Esto hará que todos los campos públicos y privados sean serializables. Más info de las anotaciones JAXB aquí.
@XmlAccessorType(XmlAccessType.FIELD)
class YourDomainClass {
  ...
}

2. Con la propiedad estática "expose" igual a "cxfjax". Configurado esto, ya podemos utilizar las anotaciones JaxWS para tener más control sobre tu WSDL.
import javax.jws.*

class TestService { static expose=['cxfjax']

  @WebResult(name="addResult") 
  @WebMethod(operationName="add") 
  int add(@WebParam(name="a")int a, @WebParam(name="b")int b) { 
    return a + b 
  } 
}
Con cxfjax, tienes que poner la anotación @WebMethod en el método del servicio que quieres exponer. Con la anotación @WebResult, le decimos el nombre del parámetro que devuelve el servicio en el WSDL y con @WebParam definimos el nombre del parámetro de entrada que está definido en el WSDL.

Anotaciones de JAXWS:
- @WebService indica que una clase Java está implementando un servicio Web o indica que una SEI (Service Endpoint Interface) está implementando una interfaz de servicio Web. Destino de anotación: Type. Parámetros que permite:

name="" -> El nombre de wsdl:portType. El valor por omisión es el nombre sin calificar de la interfaz o clase Java. (String)

targetNamespace="" -> Especifica el espacio de nombres XML o los elementos WSDL y XML generados a partir del servicio Web. El valor por omisión es el espacio de nombres correlacionado a partir del nombre del paquete que contiene el servicio Web. (String)

serviceName="" -> Especifica el nombre de servicio del servicio Web: wsdl:service. El valor por omisión es el nombre sencillo de la clase java más Service. (String)

endpointInterface="" -> Especifica el nombre calificado de la interfaz de punto final de servicio que define el contrato de servicio web abstracto de los servicios. Si se especifica, se utiliza la interfaz de punto final de servicio para determinar el contrato WSDL abstracto. (String)

portName="" -> wsdl:portName. El valor por omisión es WebService.name más Port. (String)

wsdlLocation="" -> Especifica la dirección Web del documento WSDL que define el servicio Web. La dirección Web puede ser relativa o absoluta. (String)

- @WebMethod indica un método que es una operación de servicio Web. Destino de anotación: Method. Parámetros que permite:

operationName="" -> Especifica el nombre de wsdl:operation que coincide con este método. El valor por omisión es el nombre del método Java. (String)

action="" -> Define la acción de esta operación. En los enlaces SOAP, este valor determina el valor de la cabecera SOAPAction. El valor por omisión es el nombre del método Java. (String)

exclude="" -> Especifica si se ha de excluir un método del servicio Web. El valor por omisión es false.(Boolean)

- @Oneway indica un método como una operación unidireccional de servicio Web que sólo tiene un mensaje de entrada y ningún mensaje de salida. Destino de anotación: Method. No hay parámetros.

- @WebParam personaliza la correlación de un parámetro individual con una parte de mensaje de servicio Web y un elemento XML. Destino de anotación: Parameter. Parámetros que permite:

name="" -> El nombre del parámetro. Si la operación es una RPC (Remote Procedure Call) y no se especifica el atributo partName, este es el nombre del atributo wsdl:part que representa el parámetro. Si la operación es de estilo documento o si el parámetro se correlaciona con una cabecera, -name es el nombre local del elemento XML que representa el parámetro. Este atributo es necesario si la operación es de estilo documento, el estilo de parámetro es BARE, y la modalidad OUT o INOUT. (String)

partName="" -> Define el nombre del atributo wsdl:part que representa este parámetro. Sólo se utiliza si la operación es de estilo RPC o si el estilo de documento y el estilo de parámetro es BARE. (String)

targetNamespace="" -> Especifica el espacio de nombres XML del elemento XML del parámetro. Se aplica únicamente a los enlaces de documentos cuando el atributo se correlaciona con un elemento XML. El valor por omisión es el targetNamespace del servicio Web. (String)

mode="" -> El valor representa la dirección en que fluye el parámetro para este método. Los valores válidos son IN, INOUT y OUT. (String)

header="" -> Especifica si el parámetro es una cabecera de mensaje y no el cuerpo de un mensaje. El valor por omisión es false. (Boolean)

- @WebResult personaliza la correlación de un valor de retorno con una parte WSDL o elemento XML. Destino de anotación: Method. Parámetros que permite:

name="" -> Especifica el nombre del valor de retorno como figura en el archivo WSDL y aparece en los mensajes de la comunicación. Para los enlaces RPC, este es el nombre del atributo wsdl:part que representa el valor de retorno. Para los enlaces de documentos, el parámetro -name es el nombre local del elemento XML que representa el valor de retorno. El valor por omisión es return para enlaces RPC y DOCUMENT/WRAPPED. El valor por omisión es el nombre de método más Response para enlaces DOCUMENT/BARE. (String)

targetNamespace="" -> Especifica el espacio de nombres XML para el valor de retorno. Este parámetro sólo se utiliza si la operación es de estilo RPC o si la operación es de estilo DOCUMENT y el estilo de parámetro es BARE. (String)

header="" -> Especifica si el resultado se lleva a cabo en una cabecera. El valor por omisión es false. (Boolean)

partName="" -> Especifica el nombre de la parte para el resultado con las operaciones RPC o DOCUMENT/BARE. El valor por omisión es @WebResult.name. (String)

- @HandlerChain asocia el servicio Web con una cadena de manejadores definida externamente. Destino de anotación: Type. Parámetros que permite:

file="" -> Especifica la ubicación del archivo de la cadena de manejadores. La ubicación del archivo es un java.net.URL absoluto con forrmato externo o una vía de acceso relativa al archivo de clases. (String)

name="" -> Especifica el nombre de la cadena de manejadores en el archivo de configuración. (String)

- @SOAPBinding especifica la correlación del servicio Web en el protocolo de mensajes SOAP. Destino de anotación: Type o Method. Parámetros que permite:

style="" -> Define el estilo de codificación para los mensajes enviados a y desde el servicio Web. Los valores válidos son DOCUMENT y RPC. El valor por omisión es DOCUMENT. (String)

use="" -> Define el formato utilizado para los mensajes enviados a y desde el servicio Web. El valor por omisión es LITERAL. ENCODED no está soportado en Feature Pack for Web Services. (String)

parameterStyle="" -> Determina si los parámetros del método representan todo el cuerpo del mensaje o si los parámetros son elementos envueltos en un elemento de nivel superior con el nombre de la operación. Los valores válidos son WRAPPED o BARE. Sólo puede utilizar el valor BARE con los enlaces de estilo DOCUMENT. El valor por omisión es WRAPPED.(String)

Mas info en Web Services Metadata Annotations (JSR 181)

viernes, 5 de octubre de 2012

Error de sincronización Grails 2.1.1 con Intellij IDEA 11.1.1

En uno de mis proyectos personales, teniendo instalado la version de Grails 2.1.1 me salía el siguiente error cuando añadía una dependencia en el BuildConfig.groovy,
IDEA hook: Grails not found!
| Error java.lang.reflect.InvocationTargetException
| Error
 at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
| Error
 at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
| Error
 at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
| Error
 at java.lang.reflect.Method.invoke(Method.java:601)
| Error
 at org.jetbrains.groovy.grails.rt.Agent$2.run(Agent.java:73)
| Error at java.lang.Thread.run(Thread.java:722)
| Error Caused by: java.lang.NullPointerException
| Error at org.jetbrains.groovy.grails.rt.GrailsSettingsGetter.printSettings(GrailsSettingsGetter.java:62)
Después de volverme loco durante una hora, encontré la solución. Y es que es un bug en la versión 11.1.1 del Intellij IDEA. La solución hacer un upgrade a las versión 11.1.3 y todo solucionado.

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'
    }