Ошибка «Максимальное количество выражений в списке - 1000» с Grails и Oracle - PullRequest
3 голосов
/ 17 февраля 2012

Я использую Grails с базой данных Oracle. Большая часть данных в моем приложении является частью иерархии, которая выглядит примерно так (каждый элемент содержит следующий):

  • Направление
  • * Группа 1006 *
  • Строительная площадка
  • Договор
  • Осмотр
  • Несоответствие

Данные, видимые пользователю, фильтруются в соответствии с его доступом, который может быть на уровне Направления, Группы или Строительной площадки в зависимости от роли пользователя.

Мы легко достигли этого, создав метод listWithSecurity для класса домена BuildingSite, который мы используем вместо списка в большей части системы. Мы создали еще один метод listWithSecurity для Contract. Это в основном делает Contract.findAllByContractIn (BuildingSite.listWithSecurity). И так далее с другими классами. Преимущество заключается в том, что вся логика доступа хранится в BuildingSite.listWithsecurity.

Проблема возникла, когда мы начали получать реальные данные в системе. Мы быстро нажали «ora-01795, максимальное количество выражений в списке - 1000». Справедливо сказать, передача списка из более чем 1000 литералов - не самая эффективная вещь, поэтому я попробовал другие способы, хотя это означало, что мне пришлось бы депортировать логику безопасности на каждый контроллер.

Казалось, очевидный способ - использовать такие критерии (для простоты я поставил здесь только доступ к уровню Направления):

def c = NonConformity.createCriteria()
def listToReturn = c.list(max:params.max, offset: params.offset?.toInteger() ?: 0)
{
    inspection {
        contract {
            buildingSite {
                group {
                    'in'("direction",listOfOneOrTwoDirections)
                }
            }
        }
    }
}

Я ожидал, что Grails создаст один запрос с объединениями, который позволит избежать ошибки ora-01795, но, похоже, он вызывает отдельный запрос для каждого уровня и передает результат обратно в Oracle как литерал в «in» для запроса другой уровень. Другими словами, он делает именно то, что делал я, поэтому получаю ту же ошибку.

На самом деле, это может немного оптимизировать. Кажется, это решает проблему, но только для одного уровня. В предыдущем примере я не получил бы ошибку для 1001 инспекции, но я бы получил ее для 1001 контракта или строительной площадки.

Я также пытался сделать в основном то же самое с findAll и одним оператором HQL where, которому я передал единственное направление, чтобы получить несоответствия в одном запросе. То же самое. Это решает первые уровни, но я получаю ту же ошибку для других уровней.

Мне удалось исправить это, разделив мои критерии «in» на множество «in» внутри «или», поэтому ни один из списков литералов не может быть длиннее 1000, но это ужасно уродливый код. Один findAllBy […] In становится более 10 строк кода. И в долгосрочной перспективе это, вероятно, вызовет проблемы с производительностью, поскольку мы застряли при выполнении запросов с очень большим количеством параметров.

Кто-нибудь сталкивался и решал эту проблему более элегантно и эффективно?

Ответы [ 2 ]

2 голосов
/ 17 февраля 2014

Это не принесет никаких наград за эффективность, но я решил опубликовать его как вариант, если вам просто нужно запросить список из более чем 1000 наименований, ни один из более эффективных вариантов не доступен / не подходит. (Этот вопрос о переполнении стека находится в верхней части результатов поиска Google для "grails oracle 1000")

В критериях Грааля вы можете использовать метод colov () Groovy, чтобы разбить ваш список ...

Вместо этого:

    def result = MyDomain.createCriteria().list {
        'in'('id', idList)
    }

... который выдает это исключение:

could not execute query
org.hibernate.exception.SQLGrammarException: could not execute query
    at grails.orm.HibernateCriteriaBuilder.invokeMethod(HibernateCriteriaBuilder.java:1616)
    at TempIntegrationSpec.oracle 1000 expression max in a list(TempIntegrationSpec.groovy:21)
Caused by: java.sql.SQLSyntaxErrorException: ORA-01795: maximum number of expressions in a list is 1000
    at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:440)

В итоге вы получите что-то вроде этого:

    def result = MyDomain.createCriteria().list {
        or { idList.collate(1000).each { 'in'('id', it) } }
    }

К сожалению, Hibernate или Grails не делают этого для вас за кулисами, когда вы пытаетесь создать inList из> 1000 элементов и используете диалект Oracle.

Я согласен с многочисленными дискуссиями по этой теме о рефакторинге вашего дизайна, чтобы в итоге не появилось более 1000 списков элементов, но независимо от этого вышеприведенный код сработает.

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

В том же ключе, что и комментарий Юргена, я подошел к аналогичной проблеме, создав представление БД, которое выравнивает правила доступа пользователей / ролей на их наиболее детальном уровне (Building Site в вашем случае?) Как минимум, это представлениеможет содержать только два столбца: идентификатор строительной площадки и имя пользователя / группы.Таким образом, в случае, когда у пользователя есть доступ на уровне Направления, он / она будет иметь много строк в представлении безопасности - по одной строке на каждый дочерний Строительный Сайт Направления (й), к которым пользователю разрешен доступ.

Тогда нужно создать класс GORM только для чтения, который сопоставляется с вашим представлением безопасности, присоединить его к другим классам вашего домена и выполнить фильтрацию с использованием поля пользователя / роли представления.Если вам повезет, вы сможете сделать это полностью в GORM (несколько советов здесь: http://grails.1312388.n4.nabble.com/Grails-Domain-Class-and-Database-View-td3681188.html)

Возможно, вам понадобится повеселиться с Hibernate: http://grails.org/doc/latest/guide/hibernate.html

...