Oracle
 sql >> Base de Dados >  >> RDS >> Oracle

Usando os IDs gerados por GUID() do Oracle no Grails/Hibernate


Ufa... depois de mais um dia de luta com isso, acho que tenho meus braços em volta da coisa. Esta resposta cobre um pouco mais do que a descrição original da pergunta, mas isso é porque encontrei ainda mais problemas depois de superar o problema do gerador de hibernação.

Problema nº 1:Obtendo um Oracle GUID() valor


Conforme coberto pela resposta de Adam Hawkes, o gerador Hibernate "guid" não é mantido e funciona apenas para versões mais antigas do dialeto Oracle.

No entanto, se você usar o gerador do Hibernate "atribuído" (o que significa que você deseja definir as chaves primárias manualmente em vez de fazer com que o Hibernate as gere automaticamente), então você pode inserir valores extraídos de um Oracle SYS_GUID() ligar.

Mesmo que os dialetos Oracle mais recentes do Hibernate não suportem "guid" perfeitamente, eles ainda entendem o SQL necessário para gerar esses valores. Se você estiver dentro de um controlador, poderá buscar essa consulta SQL com o seguinte:
String guidSQL = grailsApplication.getMainContext().sessionFactory.getDialect().getSelectGUIDString()

Se você estiver dentro de uma classe de domínio, ainda poderá fazer isso... mas primeiro precisará injetar uma referência a grailsApplication. Você provavelmente quer fazer isso em um controlador, no entanto... mais sobre isso abaixo.

Se você estiver curioso, a String real retornada aqui (para Oracle) é:
select rawtohex(sys_guid()) from dual

Você pode executar este SQL e buscar o valor do ID gerado assim:
String guid = grailsApplication.getMainContext().sessionFactory.currentSession.createSQLQuery(guidSQL).list().get(0)

Problema 2:na verdade, usando esse valor em um objeto de domínio Grails


Para realmente usar esse valor GUID em sua classe de domínio Grails, você precisa usar o gerador Hibernate "atribuído". Como mencionado anteriormente, isso declara que você deseja definir seus próprios IDs manualmente, em vez de permitir que o Grails/GORM/Hibernate os gere automaticamente. Compare este trecho de código modificado com o da minha pergunta original acima:
...
static mapping = {
    table name: "OWNER"
    version false
    id column: "OWNER_OID", generator: "assigned"
    name column: "NAME"
    ...
}
...

Na minha classe de domínio, mudei "guid" para "atribuído". Também descobri que precisava eliminar as "columns {} " bloco de agrupamento e mova todas as informações da minha coluna para um nível (estranho).

Agora, em qualquer controlador que esteja criando esses objetos de domínio... gere um GUID conforme descrito acima e conecte-o ao "id" do objeto ". Em um Controller gerado automaticamente pelo Grails Scaffolding, a função 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)
}

Você pode pensar em tentar colocar essa lógica diretamente dentro do objeto de domínio, em um "beforeInsert() ". Isso definitivamente seria mais limpo e elegante, mas existem alguns bugs conhecidos com Grails que impedem que os IDs sejam definidos em "beforeInsert() " corretamente. Infelizmente, você terá que manter essa lógica no nível do Controlador.

Problema nº 3:Faça com que o Grails/GORM/Hibernate armazene isso corretamente


A verdade é que o Grails é destinado principalmente a aplicativos novos e virgens, e seu suporte para bancos de dados legados é bastante irregular (para ser justo, porém, é um pouco menos irregular do que outros frameworks "dinâmicos" que experimentei em> ). Mesmo se você usar o gerador "atribuído", o Grails às vezes fica confuso quando vai persistir o objeto de domínio.

Um desses problemas é que um arquivo ".save() " a chamada às vezes tenta fazer um UPDATE quando deveria estar fazendo um INSERT. Observe que no snippet do Controller acima, adicionei "insert: true " como um parâmetro para o ".save() ". Isso diz ao Grails/GORM/Hibernate explicitamente para tentar uma operação INSERT em vez de uma UPDATE.

Todas as estrelas e planetas devem estar alinhados para que isso funcione corretamente. Se sua classe de domínio "static mapping {} " bloco não define o gerador de hibernação para "assigned ", e também defina "version false ", então o Grails/GORM/Hibernate ainda ficará confuso e tentará emitir um UPDATE em vez de um INSERT.

Se você estiver usando controladores Grails Scaffolding gerados automaticamente, é seguro usar "insert: true " no "save() do Controlador ", porque essa função só é chamada ao salvar um novo objeto pela primeira vez. Quando um usuário edita um objeto existente, o controlador "update() " é usada em vez disso. No entanto, se você estiver fazendo suas próprias coisas em seu próprio código personalizado em algum lugar... será importante verificar se um objeto de domínio já está no banco de dados antes de fazer um ".save() ", e passe apenas o "insert: true " se for realmente uma inserção pela primeira vez.

Problema nº 4:usando chaves naturais com Grails/GORM/Hibernate


Uma nota final, não tendo a ver com os valores do Oracle GUID, mas relacionada a esses problemas do Grails em geral. Digamos que em um banco de dados legado (como aquele com o qual estou lidando), algumas de suas tabelas usam uma chave natural como chave primária. Digamos que você tenha um OWNER_TYPE tabela, contendo todos os "tipos" possíveis de OWNER , e o NAME coluna é o identificador legível por humanos, bem como a chave primária.

Você terá que fazer algumas outras coisas para fazer isso funcionar com Grails Scaffolding. Por um lado, as visualizações geradas automaticamente não mostram o campo ID na tela quando os usuários estão criando novos objetos. Você terá que inserir algum HTML na visualização relevante para adicionar um campo para o ID. Se você der ao campo um nome de "id ", então a função "save()" do Controlador gerado automaticamente receberá este valor como "params.id ".

Em segundo lugar, você deve certificar-se de que o controlador gerado automaticamente "save() A função " insere corretamente o valor do ID. Quando gerado pela primeira vez, um "save()" começa instanciando um objeto de domínio dos parâmetros CGI passados ​​pela View:
def ownerTypeInstance = new OwnerType.get( params )

No entanto, isso não lida com o campo de ID que você adicionou à sua visualização. Você ainda precisará definir isso manualmente. Se na Visualização você deu ao campo HTML um nome de "id ", então estará disponível em "save() " como "params.id ":
...
ownerTypeInstance = new OwnerType()
ownerTypeInstance.id = params.id 
// Proceed to the ".save()" step, making sure to pass "insert: true"
...

Pedaço de bolo, hein? Talvez o "Problema nº 5" seja descobrir por que você se submete a toda essa dor, em vez de apenas escrever sua interface CRUD manualmente com Spring Web MVC (ou mesmo JSP's vanilla) em primeiro lugar! :)