Ouf ... après une autre journée de lutte avec ça, je pense que j'ai les bras autour de la chose. Cette réponse couvre un peu plus de terrain que la description de la question d'origine, mais c'est parce que j'ai trouvé encore plus de problèmes après avoir surmonté le problème du générateur Hibernate.
Problème 1 :Obtenir un GUID()
Oracle valeur
Comme couvert par la réponse d'Adam Hawkes, le générateur Hibernate "guid" n'est pas maintenu et ne fonctionne que pour les anciennes versions du dialecte Oracle.
Cependant, si vous utilisez le générateur Hibernate "assigné" (ce qui signifie que vous souhaitez définir les clés primaires manuellement plutôt que de laisser Hibernate les générer automatiquement), vous pouvez insérer des valeurs extraites d'un Oracle SYS_GUID()
appeler.
Même si les nouveaux dialectes Oracle d'Hibernate ne prennent pas en charge "guid" de manière transparente, ils comprennent toujours le SQL nécessaire pour générer ces valeurs. Si vous êtes à l'intérieur d'un Controller, vous pouvez récupérer cette requête SQL avec ce qui suit :
String guidSQL = grailsApplication.getMainContext().sessionFactory.getDialect().getSelectGUIDString()
Si vous êtes à l'intérieur d'une classe de domaine à la place, vous pouvez toujours le faire... mais vous devrez d'abord injecter une référence à grailsApplication. Vous voudrez probablement le faire dans un contrôleur, cependant... vous en saurez plus ci-dessous.
Si vous êtes curieux, la chaîne réelle renvoyée ici (pour Oracle) est :
select rawtohex(sys_guid()) from dual
Vous pouvez exécuter ce SQL et récupérer la valeur d'ID générée comme ceci :
String guid = grailsApplication.getMainContext().sessionFactory.currentSession.createSQLQuery(guidSQL).list().get(0)
Problème n° 2 :Utiliser réellement cette valeur dans un objet de domaine Grails
Pour utiliser réellement cette valeur GUID dans votre classe de domaine Grails, vous devez utiliser le générateur Hibernate "assigné". Comme mentionné précédemment, cela déclare que vous souhaitez définir vos propres identifiants manuellement, plutôt que de laisser Grails/GORM/Hibernate les générer automatiquement. Comparez cet extrait de code modifié à celui de ma question initiale ci-dessus :
...
static mapping = {
table name: "OWNER"
version false
id column: "OWNER_OID", generator: "assigned"
name column: "NAME"
...
}
...
Dans ma classe de domaine, j'ai changé "guid" en "assigné". J'ai également constaté que je devais éliminer les "columns {}
" bloc de regroupement et déplacer toutes mes informations de colonne d'un niveau (bizarre).
Maintenant, quel que soit le contrôleur qui crée ces objets de domaine ... générez un GUID comme décrit ci-dessus et branchez-le dans le "id
" de l'objet ". Dans un Controller généré automatiquement par Grails Scaffolding, la fonction sera "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)
}
Vous pourriez penser à essayer de mettre cette logique directement à l'intérieur de l'objet de domaine, dans un "beforeInsert()
". Ce serait certainement plus propre et plus élégant, mais il y a quelques bogues connus avec Grails qui empêchent les ID d'être définis dans "beforeInsert()
" correctement. Malheureusement, vous devrez conserver cette logique au niveau du contrôleur.
Problème n° 3 :Assurez-vous que Grails/GORM/Hibernate stockent cela correctement
La pure vérité est que Grails est principalement destiné aux nouvelles applications vierges, et sa prise en charge des bases de données héritées est assez inégale (en toute honnêteté, cependant, c'est un peu moins inégal que les autres frameworks "dynamiques" que j'ai essayés ). Même si vous utilisez le générateur "assigné", Grails est parfois confus lorsqu'il persiste l'objet domaine.
Un de ces problèmes est qu'un ".save()
" call essaie parfois de faire un UPDATE alors qu'il devrait faire un INSERT. Notez que dans l'extrait de Controller ci-dessus, j'ai ajouté "insert: true
" comme paramètre du ".save()
". Cela indique explicitement à Grails/GORM/Hibernate de tenter une opération INSERT plutôt qu'une opération UPDATE.
Toutes les étoiles et les planètes doivent être alignées pour que cela fonctionne correctement. Si votre domaine classe "static mapping {}
" le bloc ne définit pas le générateur Hibernate sur "assigned
", et définissez également "version false
", alors Grails/GORM/Hibernate seront toujours confus et essaieront d'émettre un UPDATE plutôt qu'un INSERT.
Si vous utilisez des contrôleurs Grails Scaffolding générés automatiquement, vous pouvez utiliser "insert: true
en toute sécurité " dans le "save()
" du contrôleur ", car cette fonction n'est appelée que lors de l'enregistrement d'un nouvel objet pour la première fois. Lorsqu'un utilisateur modifie un objet existant, la fonction "update()
" du Controller " est utilisée à la place. Cependant, si vous faites quelque chose dans votre propre code personnalisé quelque part... il sera important de vérifier si un objet de domaine est déjà dans la base de données avant de créer un ".save()
", et ne transmettez que "insert: true
" paramètre s'il s'agit vraiment d'une première insertion.
Problème n° 4 :Utiliser des clés naturelles avec Grails/GORM/Hibernate
Une dernière note, sans rapport avec les valeurs Oracle GUID, mais liée à ces problèmes Grails en général. Disons que dans une base de données héritée (telle que celle à laquelle j'ai eu affaire), certaines de vos tables utilisent une clé naturelle comme clé primaire. Disons que vous avez un OWNER_TYPE
table, contenant tous les "types" possibles de OWNER
, et le NAME
colonne est à la fois l'identifiant lisible par l'homme et la clé primaire.
Vous devrez faire quelques autres choses pour que cela fonctionne avec Grails Scaffolding. D'une part, les vues générées automatiquement n'affichent pas le champ ID à l'écran lorsque les utilisateurs créent de nouveaux objets. Vous devrez insérer du code HTML dans la vue pertinente pour ajouter un champ pour l'ID. Si vous donnez au champ un nom de "id
", la fonction "save()" du contrôleur générée automatiquement recevra cette valeur sous la forme "params.id
".
Deuxièmement, vous devez vous assurer que le "save()
" du contrôleur généré automatiquement " insère correctement la valeur de l'ID. Lorsqu'il est généré pour la première fois, un "save()" commence par instancier un objet de domaine à partir des paramètres CGI passés par la vue :
def ownerTypeInstance = new OwnerType.get( params )
Cependant, cela ne gère pas le champ ID que vous avez ajouté à votre vue. Vous devrez toujours le définir manuellement. Si sur la vue vous avez donné au champ HTML un nom de "id
", alors il sera disponible dans "save()
" comme "params.id
" :
...
ownerTypeInstance = new OwnerType()
ownerTypeInstance.id = params.id
// Proceed to the ".save()" step, making sure to pass "insert: true"
...
Du gâteau, hein ? Peut-être que "Issue #5" est de comprendre pourquoi vous vous mettez à travers toute cette douleur, plutôt que de simplement écrire votre interface CRUD à la main avec Spring Web MVC (ou même des JSP vanille) en premier lieu ! :)