Puha... efter endnu en dag med at kæmpe med det her, tror jeg, at jeg har armene rundt om sagen. Dette svar dækker en smule mere end den originale spørgsmålsbeskrivelse, men det er fordi jeg fandt endnu flere problemer efter at komme forbi problemet med Hibernate-generatoren.
Problem #1:Få et Oracle GUID()
værdi
Som dækket af Adam Hawkes' svar, er "guide" Hibernate-generatoren ikke vedligeholdt og fungerer kun til ældre versioner af Oracle-dialekten.
Men hvis du bruger Hibernate-generatoren "tildelt" (hvilket betyder, at du vil indstille primærnøgler manuelt i stedet for at lade Hibernate automatisk generere dem), så kan du indsætte værdier hentet fra en Oracle SYS_GUID()
ring.
Selvom Hibernates nyere Oracle-dialekter ikke understøtter "guid" problemfrit, forstår de stadig den nødvendige SQL for at generere disse værdier. Hvis du er inde i en controller, kan du hente den SQL-forespørgsel med følgende:
String guidSQL = grailsApplication.getMainContext().sessionFactory.getDialect().getSelectGUIDString()
Hvis du i stedet er inde i en domæneklasse, kan du stadig gøre dette... men du skal først indsætte en reference til grailsApplication. Du vil sikkert gerne gøre dette i en controller... mere om dette nedenfor.
Hvis du er nysgerrig, er den faktiske streng returneret her (for Oracle):
select rawtohex(sys_guid()) from dual
Du kan udføre denne SQL og hente den genererede ID-værdi på denne måde:
String guid = grailsApplication.getMainContext().sessionFactory.currentSession.createSQLQuery(guidSQL).list().get(0)
Problem #2:Bruger faktisk denne værdi i et Grails-domæneobjekt
For rent faktisk at bruge denne GUID-værdi i din Grails-domæneklasse, skal du bruge Hibernate-generatoren "tildelt". Som nævnt tidligere, erklærer dette, at du ønsker at indstille dine egne ID'er manuelt, i stedet for at lade Grails/GORM/Hibernate generere dem automatisk. Sammenlign dette ændrede kodestykke med det i mit oprindelige spørgsmål ovenfor:
...
static mapping = {
table name: "OWNER"
version false
id column: "OWNER_OID", generator: "assigned"
name column: "NAME"
...
}
...
I min domæneklasse ændrede jeg "guide" til "tildelt". Jeg fandt også ud af, at jeg var nødt til at fjerne "columns {}
" grupperingsblok, og flytte alle mine kolonneoplysninger op et niveau (underligt).
Nu, uanset hvilken controller der opretter disse domæneobjekter... generer en GUID som beskrevet ovenfor, og sæt den ind i objektets "id
" felt. I en controller genereret automatisk af Grails Scaffolding vil funktionen være "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)
}
Du kunne tænke dig at prøve at sætte denne logik direkte inde i domæneobjektet i en "beforeInsert()
" funktion. Det ville helt sikkert være renere og mere elegant, men der er nogle kendte fejl med Grails, der forhindrer ID'er i at blive sat i "beforeInsert()
" korrekt. Desværre bliver du nødt til at holde denne logik på controllerniveau.
Problem #3:Få Grails/GORM/Hibernate til at gemme dette korrekt
Den almindelige sandhed er, at Grails primært er beregnet til nye applikationer, og dens understøttelse af ældre databaser er ret plettet (retfærdigvis er den dog en smule mindre plettet end andre "dynamiske" rammer, jeg har prøvet em> ). Selvom du bruger den "tildelte" generator, bliver Grails nogle gange forvirret, når det går for at fortsætte domæneobjektet.
Et sådant problem er, at en ".save()
" call forsøger nogle gange at lave en OPDATERING, når det burde lave en INSERT. Bemærk, at jeg i controller-uddraget ovenfor har tilføjet "insert: true
" som en parameter til ".save()
" opkald. Dette fortæller Grails/GORM/Hibernate eksplicit at forsøge en INSERT-operation i stedet for en OPDATERING.
Alle stjerner og planeter skal være på linje for at dette fungerer rigtigt. Hvis dit domæne klasse "static mapping {}
" blok sætter ikke Hibernate-generatoren til "assigned
", og sæt også "version false
", så vil Grails/GORM/Hibernate stadig blive forvirret og forsøge at udstede en OPDATERING i stedet for en INSERT.
Hvis du bruger autogenererede Grails Scaffolding-controllere, er det sikkert at bruge "insert: true
" i controllerens "save()
" funktion, fordi den funktion kun kaldes, når et nyt objekt gemmes for første gang. Når en bruger redigerer et eksisterende objekt, vil controllerens "update()
" funktion bruges i stedet. Men hvis du laver dine egne ting i din egen tilpassede kode et eller andet sted... vil det være vigtigt at tjekke om et domæneobjekt allerede er i databasen, før du laver en ".save()
" opkald, og send kun "insert: true
" parameter, hvis det virkelig er en førstegangsindsættelse.
Udgave #4:Brug af naturlige taster med Grails/GORM/Hibernate
En sidste bemærkning, der ikke har at gøre med Oracle GUID-værdier, men relateret til disse Grails-problemer generelt. Lad os sige, at i en ældre database (såsom den, jeg har beskæftiget mig med), bruger nogle af dine tabeller en naturlig nøgle som deres primære nøgle. Lad os sige, at du har en OWNER_TYPE
tabel, der indeholder alle de mulige "typer" af OWNER
og NAME
kolonnen er både den menneskeligt læsbare identifikator såvel som den primære nøgle.
Du bliver nødt til at gøre et par andre ting for at få dette til at fungere med Grails Stilladser. For det første viser de automatisk genererede visninger ikke ID-feltet på skærmen, når brugere opretter nye objekter. Du bliver nødt til at indsætte noget HTML i den relevante visning for at tilføje et felt til ID'et. Hvis du giver feltet navnet "id
", så vil den autogenererede controllers "save()"-funktion modtage denne værdi som "params.id
".
For det andet skal du sikre dig, at den autogenererede controllers "save()
"-funktionen indsætter id-værdien korrekt. Når den først genereres, starter en "save()" ved at instansiere et domæneobjekt fra de CGI-parametre, der sendes af View:
def ownerTypeInstance = new OwnerType.get( params )
Dette håndterer dog ikke det ID-felt, du føjede til din visning. Du skal stadig indstille det manuelt. Hvis du på visningen gav HTML-feltet navnet "id
", så vil den være tilgængelig i "save()
" som "params.id
":
...
ownerTypeInstance = new OwnerType()
ownerTypeInstance.id = params.id
// Proceed to the ".save()" step, making sure to pass "insert: true"
...
Et stykke kage, hva? Måske er "Udgave #5" at finde ud af, hvorfor du udsætter dig selv gennem al denne smerte, i stedet for bare at skrive din CRUD-grænseflade i hånden med Spring Web MVC (eller endda vanilla JSP'er) i første omgang! :)