我尝试使用Grails Scaffolding将一个快速CRUD应用程序放在一些遗留数据库表周围。它是一个Oracle数据库,主键值旨在由Oracle的GUID()
函数填充。
基于this earlier StackOverflow question,我尝试在Grails域类中为此列指定“guid”作为Hibernate生成器:
...
static mapping = {
table name: "OWNER"
version false
columns {
id column: "OWNER_OID", generator: "guid"
name column: "NAME"
...
}
}
...
当我运行我的Grails应用程序时,查看甚至编辑记录的工作正常。但是,当我尝试创建新记录时,事情会因Oracle错误消息“ORA-02289: sequence does not exist
”而爆炸。
我为我的数据源启用了SQL日志记录,并看到Grails / Hibernate尝试在保存操作期间执行以下操作:
select hibernate_sequence.nextval from dual
这看起来并不正确,并且与上面链接的早期StackOverflow问题中生成的SQL不匹配。有没有人看到我在这里缺少的东西,或者知道如何让Grails / Hibernate用Oracle GUID值填充主键列?
答案 0 :(得分:1)
哇...经过另一天的摔跤之后,我想我搂着这个东西。这个答案比原来的问题描述要多一些,但那是因为我在遇到Hibernate生成器问题后发现了更多的问题。
GUID()
值正如亚当霍克斯所述['回答," guid" Hibernate生成器是不受维护的,仅适用于旧版本的Oracle方言。
但是,如果您使用Hibernate生成器"已分配" (意味着您要手动设置主键而不是让Hibernate自动生成它们),然后您可以插入从Oracle SYS_GUID()
调用中提取的值。
即使Hibernate的新甲骨文方言不支持" guid"无缝地,他们仍然了解生成这些值所必需的SQL。如果您位于Controller内部,则可以使用以下命令获取该SQL查询:
String guidSQL = grailsApplication.getMainContext().sessionFactory.getDialect().getSelectGUIDString()
如果您在域类中,您仍然可以执行此操作...但您需要首先注入对grailsApplication的引用。您可能希望在Controller中执行此操作,但更多信息如下所示。
如果您有点好奇,这里返回的实际字符串(对于Oracle)是:
select rawtohex(sys_guid()) from dual
您可以执行此SQL并获取生成的ID值,如下所示:
String guid = grailsApplication.getMainContext().sessionFactory.currentSession.createSQLQuery(guidSQL).list().get(0)
要在Grails域类中实际使用此GUID值,您需要使用Hibernate生成器"已分配"。如前所述,这声明您希望手动设置自己的ID,而不是让Grails / GORM / Hibernate自动生成它们。将此修改后的代码段与上述原始问题中的代码段进行比较:
...
static mapping = {
table name: "OWNER"
version false
id column: "OWNER_OID", generator: "assigned"
name column: "NAME"
...
}
...
在我的域名课程中,我改变了#gu;"分配到#34;。我还发现我需要消除" columns {}
"分组块,并将我的所有列信息上移一个级别(奇怪)。
现在,在创建这些域对象的控制器中......如上所述生成GUID,并将其插入对象" id
"领域。在由Grails脚手架自动生成的控制器中,该功能将为" 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)
}
您可能会考虑将此逻辑直接放在域对象中,在" beforeInsert()
"功能。这肯定会更干净,更优雅,但有一些已知的Grails错误阻止ID被设置在" beforeInsert()
"正常。遗憾的是,您必须将此逻辑保持在控制器级别。
显而易见的事实是,Grails主要用于处理新的应用程序,并且它对遗留数据库的支持非常不稳定(在公平性方面,它比其他"动态"框架我试过)。即使您使用"已分配"生成器,Grails有时会在持久保存域对象时感到困惑。
一个问题是" .save()
"当它应该执行INSERT时,调用有时会尝试执行UPDATE。请注意,在上面的Controller代码段中,我添加了" insert: true
"作为" .save()
"的参数呼叫。这告诉Grails / GORM / Hibernate显式尝试INSERT操作而不是UPDATE操作。
所有的恒星和行星必须齐心协力才能正常工作。如果您的域类" static mapping {}
"块没有将Hibernate生成器设置为" assigned
",并且还设置" version false
",然后Grails / GORM / Hibernate仍然会混淆尝试发出UPDATE而不是INSERT。
如果您使用自动生成的Grails脚手架控制器,那么使用" insert: true
"是安全的。在控制器" save()
"函数,因为该函数仅在第一次保存新对象时才被调用。当用户编辑现有对象时,控制器" update()
"而是使用函数。但是,如果您在自己的自定义代码中使用自己的东西...在制作" .save()
&#之前检查域对象是否已经在数据库中是很重要的。 34;打电话,只传递" insert: true
"参数,如果它确实是第一次插入。
最后一点,不是与Oracle GUID值有关,而是与这些Grails问题有关。让我们说在遗留数据库(例如我正在处理的数据库)中,你的一些表使用自然键作为主键。假设您有一个OWNER_TYPE
表,其中包含所有可能的"类型" OWNER
的{{1}}列,NAME
列既是人类可读的标识符,也是主键。
你必须做其他一些事情来使用Grails脚手架。首先,当用户创建新对象时,自动生成的视图不会在屏幕上显示ID字段。您必须在相关视图中插入一些HTML才能为ID添加字段。如果您为该字段命名为" id
",则自动生成的控制器"保存()"函数将收到此值为" params.id
"。
其次,您必须确保自动生成的控制器" save()
"函数正确插入ID值。首次生成时," save()"通过从View:
def ownerTypeInstance = new OwnerType.get( params )
但是,这不会处理您添加到View中的ID字段。您仍然需要手动设置。如果您在视图中为HTML字段指定了" id
",那么它将在" save()
"中提供。 as" params.id
":
...
ownerTypeInstance = new OwnerType()
ownerTypeInstance.id = params.id
// Proceed to the ".save()" step, making sure to pass "insert: true"
...
一块蛋糕,对吧?也许"问题#5"正在弄清楚为什么要让自己完成所有这些痛苦,而不是仅仅使用Spring Web MVC(甚至是vanilla JSP' s)手工编写CRUD界面! :)
答案 1 :(得分:0)
使用SYS_GUID()
的支持取决于您使用的Oracle方言。查看hibernate source on GitHub,方言似乎只设置为使用Oracle9Dialect.java
和Oracle8iDialect.java
中的Oracle生成的guid。因此,它不适用于9i或10g方言。
你应该向hibernate提交一个补丁,它将添加所需的功能,以启用与其他方言相同的功能。