【问题标题】:Using Oracle's GUID()-generated ID's in Grails/Hibernate在 Grails/Hibernate 中使用 Oracle 的 GUID() 生成的 ID
【发布时间】:2012-03-02 07:06:05
【问题描述】:

我尝试使用 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 值填充主键列?

【问题讨论】:

    标签: oracle hibernate grails


    【解决方案1】:

    对使用SYS_GUID() 的支持取决于您使用的Oracle 方言。查看hibernate source on GitHub,看来方言仅设置为使用Oracle9Dialect.javaOracle8iDialect.java 中的Oracle 生成的guid。因此,它不适用于 9i 或 10g 方言。

    您应该向 hibernate 提交一个补丁,该补丁将添加所需的功能以启用与其他方言相同的功能。

    【讨论】:

    • 谢谢!我不同意您的回复,也许我会向 Hibernate 团队提交一个补丁...我做到了……在 Hibernate 接受我的补丁并发布新版本之前,我将被困在需要单独维护的自定义构建的过渡中。
    • 此外,正如我在回答中所阐述的那样,除了这个问题之外,我还遇到了许多其他 Grails 问题。至少我学会了如何解决这些问题……并希望记录所有这些内容对以后的其他人有所帮助。
    【解决方案2】:

    唷...在与这个问题搏斗了一天之后,我想我已经抓住了这个东西。这个答案比原始问题描述涵盖了更多的基础,但那是因为我在解决了 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 接口! :)

    【讨论】:

    • 如果您还记得很久以前,您有什么理由使用基于 oracle 的 UUID 生成器而不是基于 java/code 的解决方案?
    猜你喜欢
    • 1970-01-01
    • 2011-01-10
    • 2011-03-03
    • 2015-03-29
    • 2010-12-06
    • 2015-12-10
    • 1970-01-01
    • 1970-01-01
    • 2014-08-05
    相关资源
    最近更新 更多