Использование Oracle GUID () - сгенерированные идентификаторы в Grails / Hibernate - PullRequest
2 голосов
/ 09 февраля 2012

Я пытался использовать Grails Scaffolding для создания быстрого CRUD-приложения вокруг некоторых устаревших таблиц базы данных.Это база данных Oracle, и значение первичного ключа предназначено для заполнения функцией Oracle GUID().

Исходя из этого более раннего вопроса StackOverflow , я попытался указать "guid" в качествеГенератор Hibernate для этого столбца в моем классе домена Grails:

...
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

Это выглядит не совсем корректно и не соответствует сгенерированному SQL из предыдущего вопроса StackOverflow, указанного выше.Кто-нибудь видит что-то, чего мне здесь не хватает, или иным образом знает, как заставить Grails / Hibernate заполнять столбец первичного ключа значениями Oracle GUID?

Ответы [ 2 ]

1 голос
/ 10 февраля 2012

Вот так ... после еще одного дня борьбы с этим, я думаю, что я обнимаю вещь.Этот ответ охватывает гораздо больше вопросов, чем первоначальное описание вопроса, но это потому, что я обнаружил еще больше проблем после преодоления проблемы с генератором Hibernate.

Проблема № 1: Получение значения Oracle GUID()

Как указано в ответе Адама Хоукса, генератор Hibernate "guid" не поддерживается и работает только для более старых версий диалекта Oracle.

Однако, если вы используете генератор Hibernate "назначенный" (это означает, что выесли вы хотите установить первичные ключи вручную, а не автоматически генерировать их в Hibernate), то вы можете вставить значения, извлеченные из вызова Oracle SYS_GUID().

Даже если более новые диалекты Oracle Hibernate не поддерживают «guid» без проблемони все еще понимают SQL, необходимый для генерации этих значений.Если вы находитесь внутри контроллера, вы можете получить этот SQL-запрос следующим образом:

String guidSQL = grailsApplication.getMainContext().sessionFactory.getDialect().getSelectGUIDString()

Если вы находитесь внутри класса домена, вы все равно можете сделать это ... но высначала нужно будет добавить ссылку на приложение grails.Возможно, вы захотите сделать это в контроллере, хотя ... подробнее об этом ниже.

Если вам интересно, фактическая строка, возвращаемая здесь (для Oracle):

select rawtohex(sys_guid()) from dual

Вы можете выполнить этот SQL-запрос и получить сгенерированное значение идентификатора, например:

String guid = grailsApplication.getMainContext().sessionFactory.currentSession.createSQLQuery(guidSQL).list().get(0)

Проблема № 2: Фактически использование этого значения в объекте домена Grails

Для фактического использования этогоЗначение GUID в вашем доменном классе Grails, вам нужно использовать генератор Hibernate «назначен».Как упоминалось ранее, это объявляет, что вы хотите установить свои собственные идентификаторы вручную, а не позволять Grails / GORM / Hibernate генерировать их автоматически.Сравните этот измененный фрагмент кода с приведенным в моем исходном вопросе выше:

...
static mapping = {
    table name: "OWNER"
    version false
    id column: "OWNER_OID", generator: "assigned"
    name column: "NAME"
    ...
}
...

В своем классе домена я изменил «guid» на «назначенный».Я также обнаружил, что мне нужно исключить блок группировки "columns {}" и переместить всю информацию о моем столбце на уровень (странный).

Теперь, в зависимости от того, какой контроллер создает эти объекты домена ... генерироватьGUID, как описано выше, и вставьте его в поле «id» объекта.В контроллере, автоматически сгенерированном Grails Scaffolding, эта функция будет иметь вид "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, которые не позволяют правильно устанавливать идентификаторы в "beforeInsert()".К сожалению, вам придется сохранить эту логику на уровне контроллера.

Проблема № 3: Заставьте Grails / GORM / Hibernate хранить это правильно

Простая истина в том, что Grails в первую очередь предназначен дляДевственные новые приложения, и его поддержка унаследованных баз данных довольно нечеткая (хотя, если честно, , она немного менее пятнистая, чем другие "динамические" фреймворки, которые я пробовал ).Даже если вы используете «назначенный» генератор, Grails иногда запутывается, когда пытается сохранить объект домена.

Одна из таких проблем заключается в том, что вызов «.save()» иногда пытается выполнить UPDATE, когда он долженделать вставку.Обратите внимание, что в приведенном выше фрагменте кода контроллера я добавил «insert: true» в качестве параметра к вызову «.save()».Это говорит Grails / GORM / Hibernate в явном виде о попытке выполнить операцию INSERT, а не операцию UPDATE.

Все звезды и планеты должны быть выровнены для правильной работы.Если блок класса static mapping {} вашего домена не устанавливает генератор Hibernate на «assigned», а также устанавливает «version false», то Grails / GORM / Hibernate все равно будет сбит с толку и попытается выполнить ОБНОВЛЕНИЕ вместоВСТАВКА.

Если вы используете автоматически сгенерированные контроллеры Grails Scaffolding, тогда безопасно использовать «insert: true» в функции «save()» контроллера, потому что эта функция вызывается только при первом сохранении нового объекта,Когда пользователь редактирует существующий объект, вместо него используется функция контроллера «update()».Тем не менее, если вы делаете что-то свое в своем собственном пользовательском коде где-то ... будет важно проверить, есть ли объект домена уже в базе данных, прежде чем делать вызов ".save()", и передавать толькоПараметр «insert: true», если это действительно первая вставка.

Проблема № 4: Использование естественных ключей с Grails / GORM / Hibernate

Последнее замечание, не относящееся кЗначения Oracle GUID, но связанные с этими проблемами Grails в целом.Предположим, что в устаревшей базе данных (например, той, с которой я имел дело) некоторые из ваших таблиц используют естественный ключ в качестве своего первичного ключа.Допустим, у вас есть таблица OWNER_TYPE, содержащая все возможные «типы» OWNER, а столбец NAME является как понятным человеку идентификатором, так и первичным ключом.

Вы будетенужно сделать несколько других вещей, чтобы сделать эту работу с Grails Scaffolding.Во-первых, автоматически генерируемые виды не отображают поле идентификатора на экране, когда пользователи создают новые объекты.Вам нужно будет вставить некоторый HTML-код в соответствующее представление, чтобы добавить поле для идентификатора.Если вы дадите полю имя «id», то функция «save ()» автоматически сгенерированного контроллера получит это значение как «params.id».

Во-вторых, вы должны убедиться, чточто автоматически сгенерированная функция контроллера "save()" вставляет значение идентификатора.При первом генерировании «save ()» начинается с создания экземпляра объекта домена из параметров CGI, передаваемых представлением:

def ownerTypeInstance = new OwnerType.get( params )

Однако это не обрабатывает поле идентификатора, добавленное вами в представление.Вам все еще нужно будет установить это вручную.Если в представлении вы задали для поля HTML имя «id», то оно будет доступно в «save()» как «params.id»:

...
ownerTypeInstance = new OwnerType()
ownerTypeInstance.id = params.id 
// Proceed to the ".save()" step, making sure to pass "insert: true"
...

Кусок торта, да?Возможно, «Проблема № 5» выясняет, почему вы сами переживаете всю эту боль, а не просто пишете свой CRUD-интерфейс вручную с Spring Web MVC (или даже с ванильным JSP) в первую очередь!:)

0 голосов
/ 10 февраля 2012

Поддержка использования SYS_GUID() зависит от используемого вами диалекта Oracle. Если посмотреть на источник гибернации на GitHub , кажется, что диалект был настроен только на использование сгенерированного Oracle guid в Oracle9Dialect.java и Oracle8iDialect.java. Следовательно, он не будет работать с диалектами 9i или 10g.

Вы должны отправить патч в спящий режим, который добавит необходимые функции, чтобы активировать те же функции, что и на других диалектах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...