唷...在与这个问题搏斗了一天之后,我想我已经抓住了这个东西。这个答案比原始问题描述涵盖了更多的基础,但那是因为我在解决了 Hibernate 生成器问题后发现了更多问题。
问题 #1:获取 Oracle GUID() 值
正如 Adam Hawkes 的回答所涵盖的,“guid”Hibernate 生成器无需维护,仅适用于旧版本的 Oracle 方言。
但是,如果您使用“已分配”的 Hibernate 生成器(意味着您想要手动设置主键而不是让 Hibernate 自动生成它们),那么您可以插入从 Oracle SYS_GUID() 调用中提取的值。
尽管 Hibernate 的较新的 Oracle 方言不无缝支持“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)
问题 #2:实际上在 Grails 域对象中使用此值
要在您的 Grails 域类中实际使用此 GUID 值,您需要使用“已分配”的 Hibernate 生成器。如前所述,这表明您希望手动设置自己的 ID,而不是让 Grails/GORM/Hibernate 自动生成它们。将此修改后的代码 sn-p 与我上面原始问题中的代码进行比较:
...
static mapping = {
table name: "OWNER"
version false
id column: "OWNER_OID", generator: "assigned"
name column: "NAME"
...
}
...
在我的域类中,我将“guid”更改为“assigned”。我还发现我需要消除“columns {}”分组块,并将我的所有列信息上移一个级别(奇怪)。
现在,无论在哪个控制器中创建这些域对象...如上所述生成一个 GUID,并将其插入对象的“id”字段。在 Grails Scaffolding 自动生成的 Controller 中,函数将是“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()”中。遗憾的是,您必须将此逻辑保留在控制器级别。
问题 #3:让 Grails/GORM/Hibernate 正确存储这个
显而易见的事实是,Grails 主要是为全新的应用程序设计的,它对遗留数据库的支持相当参差不齐(不过,公平地说,它比我用过的其他“动态”框架要少一些参差不齐尝试过)。即使您使用“已分配”生成器,Grails 在持久化域对象时有时也会感到困惑。
这样的一个问题是,“.save()”调用有时会在应该执行 INSERT 时尝试执行 UPDATE。请注意,在上面的 Controller sn-p 中,我已将“insert: true”作为参数添加到“.save()”调用中。这告诉 Grails/GORM/Hibernate 明确地尝试 INSERT 操作而不是 UPDATE 操作。
所有恒星和行星都必须对齐才能正常工作。如果您的域类“static mapping {}”块没有将 Hibernate 生成器设置为“assigned”,并且还设置了“version false”,那么 Grails/GORM/Hibernate 仍然会感到困惑并尝试发出 UPDATE 而不是插入。
如果您使用自动生成的 Grails Scaffolding 控制器,那么在控制器的“save()”函数中使用“insert: true”是安全的,因为该函数仅在第一次保存新对象时调用.当用户编辑现有对象时,将使用控制器的“update()”函数。但是,如果您在某个地方在自己的自定义代码中做自己的事情......在进行“.save()”调用之前检查域对象是否已经在数据库中很重要,并且只通过“insert: true”参数,如果它真的是第一次插入。
问题 #4:在 Grails/GORM/Hibernate 中使用自然键
最后一点,与 Oracle GUID 值无关,但通常与这些 Grails 问题有关。假设在一个遗留数据库(比如我一直在处理的那个)中,您的一些表使用自然键作为它们的主键。假设您有一个OWNER_TYPE 表,其中包含OWNER 的所有可能“类型”,而NAME 列既是人类可读的标识符,也是主键。
您必须做一些其他的事情才能使这项工作与 Grails Scaffolding 一起使用。一方面,当用户创建新对象时,自动生成的视图不会在屏幕上显示 ID 字段。您必须在相关视图中插入一些 HTML 才能为 ID 添加字段。如果你给字段命名为“id”,那么自动生成的控制器的“save()”函数将接收这个值作为“params.id”。
其次,您必须确保自动生成的 Controller 的“save()”函数正确插入 ID 值。首次生成时,“save()”首先从视图传递的 CGI 参数实例化域对象:
def ownerTypeInstance = new OwnerType.get( params )
但是,这不会处理您添加到视图中的 ID 字段。您仍然需要手动设置。如果在视图中您为 HTML 字段命名为“id”,那么它将在“save()”中以“params.id”的形式出现:
...
ownerTypeInstance = new OwnerType()
ownerTypeInstance.id = params.id
// Proceed to the ".save()" step, making sure to pass "insert: true"
...
小菜一碟,嗯?也许“问题#5”是在弄清楚为什么你要让自己经历所有这些痛苦,而不是一开始就用 Spring Web MVC(甚至是普通的 JSP)手动编写你的 CRUD 接口! :)