sql >> Base de Datos >  >> RDS >> Oracle

Uso de ID generados por GUID() de Oracle en Grails/Hibernate

Vaya... después de otro día de luchar con esto, creo que tengo mis brazos alrededor de la cosa. Esta respuesta cubre un poco más de terreno que la descripción de la pregunta original, pero eso se debe a que encontré aún más problemas después de superar el problema del generador de Hibernate.

Problema #1:Obtener un GUID() de Oracle valor

Como lo cubre la respuesta de Adam Hawkes, el generador de Hibernate "guid" no se mantiene y solo funciona para versiones anteriores del dialecto de Oracle.

Sin embargo, si utiliza el generador de Hibernate "asignado" (lo que significa que desea configurar las claves principales manualmente en lugar de que Hibernate las genere automáticamente), puede insertar valores extraídos de un Oracle SYS_GUID() llamar.

Aunque los dialectos de Oracle más nuevos de Hibernate no son compatibles con "guid" sin problemas, todavía entienden el SQL necesario para generar estos valores. Si está dentro de un controlador, puede obtener esa consulta SQL con lo siguiente:

String guidSQL = grailsApplication.getMainContext().sessionFactory.getDialect().getSelectGUIDString()

Si está dentro de una clase de dominio, aún puede hacer esto... pero primero deberá inyectar una referencia a grailsApplication. Probablemente quieras hacer esto en un controlador, aunque... más sobre esto a continuación.

Si tiene curiosidad, la cadena real devuelta aquí (para Oracle) es:

select rawtohex(sys_guid()) from dual

Puede ejecutar este SQL y obtener el valor de ID generado de esta manera:

String guid = grailsApplication.getMainContext().sessionFactory.currentSession.createSQLQuery(guidSQL).list().get(0)

Problema n.º 2:en realidad se usa este valor en un objeto de dominio de Grails

Para usar realmente este valor de GUID en su clase de dominio de Grails, debe usar el generador de Hibernate "asignado". Como se mencionó anteriormente, esto declara que desea configurar sus propios ID manualmente, en lugar de dejar que Grails/GORM/Hibernate los genere automáticamente. Compare este fragmento de código modificado con el de mi pregunta original anterior:

...
static mapping = {
    table name: "OWNER"
    version false
    id column: "OWNER_OID", generator: "assigned"
    name column: "NAME"
    ...
}
...

En mi clase de dominio, cambié "guid" a "asignado". También descubrí que necesitaba eliminar las "columns {} " bloque de agrupación y subo toda la información de mi columna un nivel (raro).

Ahora, en cualquier controlador que esté creando estos objetos de dominio... genere un GUID como se describe anteriormente y conéctelo al "id" del objeto. ". En un controlador generado automáticamente por Grails Scaffolding, la función será "save() ":

def save() {
    def ownerInstance = new Owner(params)
    String guidSQL = grailsApplication.getMainContext().sessionFactory.getDialect().getSelectGUIDString()
    ownerInstance.id = grailsApplication.getMainContext().sessionFactory.currentSession.createSQLQuery(guidSQL).list().get(0)

    if (!ownerInstance.save(flush: true, insert: true)) {
        render(view: "create", model: [ownerInstance: ownerInstance])
        return
    }

    flash.message = message(code: 'default.created.message', args: [message(code: 'owner.label', default: 'Owner'), ownerInstance.id])
    redirect(action: "show", id: ownerInstance.id)
}

Podría pensar en intentar poner esta lógica directamente dentro del objeto de dominio, en un "beforeInsert() ". Eso definitivamente sería más limpio y más elegante, pero hay algunos errores conocidos con Grails que impiden que las ID se configuren en "beforeInsert() " correctamente. Lamentablemente, tendrá que mantener esta lógica en el nivel del controlador.

Problema n.° 3:Hacer que Grails/GORM/Hibernate almacene esto correctamente

La pura verdad es que Grails está diseñado principalmente para aplicaciones nuevas y vírgenes, y su soporte para bases de datos heredadas es bastante irregular (para ser justos, sin embargo, es un poco menos irregular que otros marcos "dinámicos" que he probado ). Incluso si usa el generador "asignado", Grails a veces se confunde cuando persiste el objeto de dominio.

Uno de esos problemas es que un ".save() " la llamada a veces intenta hacer una ACTUALIZACIÓN cuando debería estar haciendo una INSERCIÓN. Tenga en cuenta que en el fragmento del controlador anterior, he agregado "insert: true " como parámetro de ".save() " llamar. Esto le dice a Grails/GORM/Hibernate explícitamente que intente una operación de INSERCIÓN en lugar de una de ACTUALIZACIÓN.

Todas las estrellas y planetas deben estar alineados para que esto funcione correctamente. Si su clase de dominio "static mapping {} El bloque " no establece el generador de Hibernate en "assigned ", y también establezca "version false ", entonces Grails/GORM/Hibernate aún se confundirá e intentará emitir una ACTUALIZACIÓN en lugar de una INSERCIÓN.

Si está utilizando controladores Grails Scaffolding generados automáticamente, entonces es seguro usar "insert: true " en el controlador "save() ", porque esa función solo se llama cuando se guarda un nuevo objeto por primera vez. Cuando un usuario edita un objeto existente, el controlador "update() " se usa la función en su lugar. Sin embargo, si está haciendo lo suyo en su propio código personalizado en algún lugar... será importante verificar si un objeto de dominio ya está en la base de datos antes de hacer un ".save() " llame, y solo pase el "insert: true " parámetro si realmente es una inserción por primera vez.

Problema #4:Uso de claves naturales con Grails/GORM/Hibernate

Una nota final, que no tiene que ver con los valores GUID de Oracle, pero está relacionada con estos problemas de Grails en general. Digamos que en una base de datos heredada (como la que he estado tratando), algunas de sus tablas usan una clave natural como su clave principal. Digamos que tienes un OWNER_TYPE tabla, que contiene todos los "tipos" posibles de OWNER , y el NAME la columna es tanto el identificador legible por humanos como la clave principal.

Tendrá que hacer un par de cosas más para que esto funcione con Grails Scaffolding. Por un lado, las Vistas generadas automáticamente no muestran el campo ID en la pantalla cuando los usuarios están creando nuevos objetos. Tendrá que insertar algo de HTML en la Vista correspondiente para agregar un campo para la ID. Si le da al campo un nombre de "id ", entonces la función "guardar ()" del controlador generado automáticamente recibirá este valor como "params.id ".

En segundo lugar, debe asegurarse de que el controlador "save() generado automáticamente " inserta correctamente el valor de ID. Cuando se genera por primera vez, un "guardar ()" comienza instanciando un objeto de dominio de los parámetros CGI pasados ​​por la Vista:

def ownerTypeInstance = new OwnerType.get( params )

Sin embargo, esto no maneja el campo de ID que agregó a su Vista. Aún tendrá que configurarlo manualmente. Si en la Vista le dio al campo HTML un nombre de "id ", entonces estará disponible en "save() " como "params.id ":

...
ownerTypeInstance = new OwnerType()
ownerTypeInstance.id = params.id 
// Proceed to the ".save()" step, making sure to pass "insert: true"
...

Pan comido, ¿eh? ¡Quizás el "Problema n. ° 5" es descubrir por qué te sometes a todo este dolor, en lugar de simplemente escribir tu interfaz CRUD a mano con Spring Web MVC (o incluso Vanilla JSP) en primer lugar! :)