Как работать с большим набором данных с помощью JPA (или хотя бы с Hibernate)? - PullRequest
17 голосов
/ 04 мая 2010

Мне нужно, чтобы мое веб-приложение работало с действительно огромными наборами данных. В настоящий момент я получаю OutOfMemoryException или вывод, который генерируется 1-2 минуты.

Попросту говоря, и предположим, что у нас есть две таблицы в БД: Worker и WorkLog с примерно 1000 строк в первой и 10 000 000 строк во второй. В последней таблице есть несколько полей, в том числе поля workerId и hoursWorked. Что нам нужно это:

  1. общее количество часов, отработанных каждым пользователем;

  2. список периодов работы для каждого пользователя.

Наиболее простой подход (IMO) для каждой задачи в простом SQL:

1)

select Worker.name, sum(hoursWorked) from Worker, WorkLog 
   where Worker.id = WorkLog.workerId 
   group by Worker.name;

//results of this query should be transformed to Multimap<Worker, Long>

2)

select Worker.name, WorkLog.start, WorkLog.hoursWorked from Worker, WorkLog
   where Worker.id = WorkLog.workerId;

//results of this query should be transformed to Multimap<Worker, Period>
//if it was JDBC then it would be vitally 
//to set resultSet.setFetchSize (someSmallNumber), ~100

Итак, у меня два вопроса:

  1. как реализовать каждый из моих подходов с помощью JPA (или, по крайней мере, с Hibernate);
  2. как бы вы справились с этой проблемой (конечно, с JPA или Hibernate)?

Ответы [ 7 ]

18 голосов
/ 04 мая 2010

предположим, что у нас есть две таблицы в БД: Worker и WorkLog, в первой из которых около 1000 строк, а во второй 10 000 000

Для таких больших объемов я рекомендую использовать StatelessSession интерфейс из Hibernate:

В качестве альтернативы, Hibernate предоставляет командно-ориентированный API, который можно использовать для потоковой передачи данных в и из база данных в виде отдельного объекты. A StatelessSession не имеет постоянный контекст, связанный с этим и не обеспечивает многие из семантика жизненного цикла более высокого уровня. В в частности, сеанс без сохранения состояния не реализовать ни кэш первого уровня, ни взаимодействовать с любым вторым уровнем или кеш запросов. Не реализует транзакционная запись или автоматическая проверка грязи. операции выполняется с использованием сеанса без сохранения состояния никогда не каскадом связанных экземпляров. Коллекции игнорируются лицами без гражданства сессия. Операции, выполняемые через обход без сохранения состояния Hibernate's модель событий и перехватчики. Из-за отсутствие кеша первого уровня, Сессии без гражданства уязвимы для эффекты наложения данных. Лицо без гражданства сессия - абстракция нижнего уровня это намного ближе к основному JDBC.

StatelessSession session = sessionFactory.openStatelessSession();
Transaction tx = session.beginTransaction();

ScrollableResults customers = session.getNamedQuery("GetCustomers")
    .scroll(ScrollMode.FORWARD_ONLY);
while ( customers.next() ) {
    Customer customer = (Customer) customers.get(0);
    customer.updateStuff(...);
    session.update(customer);
}

tx.commit();
session.close();

В этом примере кода Customer экземпляры, возвращаемые запросом немедленно отсоединен Они никогда связано с любой настойчивостью контекст.

insert(), update() и delete() операций, определенных StatelessSession интерфейс считается прямой базой данных операции на уровне строк. Они приводят к немедленное выполнение SQL INSERT, UPDATE или DELETE соответственно. У них разные семантика для save(), saveOrUpdate() и delete() операции, определенные Session интерфейс.

4 голосов
/ 26 декабря 2013

Кажется, вы можете сделать это и с EclipseLink. Проверьте это: http://wiki.eclipse.org/EclipseLink/Examples/JPA/Pagination:

Query query = em.createQuery...
query.setHint(QueryHints.CURSOR, true)
     .setHint(QueryHints.SCROLLABLE_CURSOR, true)
ScrollableCursor scrl = (ScrollableCursor)q.getSingleResult();
Object o = null;
while ((o = scrl.next()) != null) { ... }
2 голосов
/ 28 января 2014

Это сообщение в блоге также может помочь. Он обобщает подход с сеансом без сохранения состояния и добавляет некоторые дополнительные подсказки, например, как транслировать результаты с JAX-RS.

1 голос
/ 25 февраля 2013

Существует несколько методов, которые могут потребоваться в сочетании друг с другом для создания и манипулирования запросами для больших наборов данных, где память является ограничением:

  1. Используйте setFetchSize (некоторое значение, может быть, 100+), поскольку по умолчанию (через JDBC) установлено значение 10. Это больше касается производительности и является самым большим связанным с этим фактором. Может быть сделано в JPA с использованием queryHint, доступного от провайдера (Hibernate и т. Д.). Кажется, нет (по какой-либо причине) метода JPA Query.setFetchSize(int).
  2. Не пытайтесь маршалировать весь набор результатов для 10K + записей. Применяются несколько стратегий: для графического интерфейса используйте пейджинг или структуру, которая выполняет пейджинг. Рассмотрим Lucene или коммерческие системы поиска / индексации (Endeca, если у компании есть деньги). Для отправки данных куда-либо, передайте их и очищайте буфер каждые N записей, чтобы ограничить объем используемой памяти. Поток может быть сброшен в файл, сеть и т. Д. Помните, что ниже JPA использует JDBC, а JDBC сохраняет набор результатов на сервере, выбирая одновременно только N-рядов в группе наборов строк. Этим разбиванием можно манипулировать, чтобы облегчить сброс данных в группах.
  3. Подумайте, каков вариант использования. Как правило, приложение пытается ответить на вопросы. Когда ответ состоит в том, чтобы проползти через 10K + рядов, тогда дизайн должен быть пересмотрен Опять же, рассмотрите возможность использования механизмов индексации, таких как Lucene, уточните запросы, рассмотрите возможность использования BloomFilters, поскольку содержит проверочных кэшей для поиска игл в стогах сена без обращения к базе данных и т. Д.
1 голос
/ 24 ноября 2011

Необработанный SQL не следует рассматривать как последнее средство. Это все еще следует рассматривать как вариант, если вы хотите, чтобы все было «стандартно» на уровне JPA, но не на уровне базы данных. JPA также поддерживает нативные запросы, где он по-прежнему будет отображать для вас стандартные сущности.

Однако, если у вас большой набор результатов, который не может быть обработан в базе данных, вам действительно следует просто использовать простой JDBC, поскольку JPA (стандарт) не поддерживает потоковую передачу больших наборов данных.

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

0 голосов
/ 02 октября 2012

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

1)

select w, sum(wl.hoursWorked) 
from Worker w, WorkLog wl
where w.id = wl.workerId 
group by w

или, если сопоставление сопоставлено:

select w, sum(wl.hoursWorked) 
from Worker w join w.workLogs wl
group by w

оба или которые возвращают вам List, где Object [] s Worker и Long. Или вы можете также использовать запросы «динамического создания», чтобы обернуть это, например:

select new WorkerTotal( select w, sum(wl.hoursWorked) )
from Worker w join w.workLogs wl
group by w

или (в зависимости от необходимости) возможно даже просто:

select new WorkerTotal( select w.id, w.name, sum(wl.hoursWorked) )
from Worker w join w.workLogs wl
group by w.id, w.name

WorkerTotal - это простой класс. У него должен быть соответствующий конструктор (ы).

2)

select w, new Period( wl.start, wl.hoursWorked )
from Worker w join w.workLogs wl

это вернет вам результат для каждой строки в таблице WorkLog ... Бит new Period(...) называется «динамическим созданием» и используется для обертывания кортежей из результата в объекты (более простое потребление).

Для манипуляций и общего использования я рекомендую StatelessSession, как указывает Паскаль.

0 голосов
/ 02 октября 2012

Я использую что-то вроде этого, и это работает очень быстро. Я также ненавижу использовать нативный SQL, поскольку наше приложение должно работать с любой базой данных.

Folowing переходит в очень оптимизированный SQL и возвращает список записей, которые являются картами.

String hql = "select distinct " +
            "t.uuid as uuid, t.title as title, t.code as code, t.date as date, t.dueDate as dueDate, " +
            "t.startDate as startDate, t.endDate as endDate, t.constraintDate as constraintDate, t.closureDate as closureDate, t.creationDate as creationDate, " +
            "sc.category as category, sp.priority as priority, sd.difficulty as difficulty, t.progress as progress, st.type as type, " +
            "ss.status as status, ss.color as rowColor, (p.rKey || ' ' || p.name) as project, ps.status as projectstatus, (r.code || ' ' || r.title) as requirement, " +
            "t.estimate as estimate, w.title as workgroup, o.name || ' ' || o.surname as owner, " +
            "ROUND(sum(COALESCE(a.duration, 0)) * 100 / case when ((COALESCE(t.estimate, 0) * COALESCE(t.progress, 0)) = 0) then 1 else (COALESCE(t.estimate, 0) * COALESCE(t.progress, 0)) end, 2) as factor " +
            "from " + Task.class.getName() + " t " +
            "left join t.category sc " +
            "left join t.priority sp " +
            "left join t.difficulty sd " +
            "left join t.taskType st " +
            "left join t.status ss " +
            "left join t.project p " +
            "left join t.owner o " +
            "left join t.workgroup w " +
            "left join p.status ps " +
            "left join t.requirement r " +
            "left join p.status sps " +
            "left join t.iterationTasks it " +
            "left join t.taskActivities a " +
            "left join it.iteration i " +
            "where sps.active = true and " +
            "ss.done = false and " +
            "(i.uuid <> :iterationUuid or it.uuid is null) " + filterHql +
            "group by t.uuid, t.title, t.code, t.date, t.dueDate, " +
            "t.startDate, t.endDate, t.constraintDate, t.closureDate, t.creationDate, " +
            "sc.category, sp.priority, sd.difficulty, t.progress, st.type, " +
            "ss.status, ss.color, p.rKey, p.name, ps.status, r.code, r.title, " +
            "t.estimate, w.title, o.name, o.surname " + sortHql;

    if (logger.isDebugEnabled()) {
        logger.debug("Executing hql: " + hql );
    }

    Query query =  hibernateTemplate.getSessionFactory().getCurrentSession().getSession(EntityMode.MAP).createQuery(hql);
    for(String key: filterValues.keySet()) {
        Object valueSet = filterValues.get(key);

        if (logger.isDebugEnabled()) {
            logger.debug("Setting query parameter for " + key );
        }

        if (valueSet instanceof java.util.Collection<?>) {
            query.setParameterList(key, (Collection)filterValues.get(key));
        } else {
            query.setParameter(key, filterValues.get(key));
        }
    }       
    query.setString("iterationUuid", iteration.getUuid());
    query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);

    if (logger.isDebugEnabled()) {
        logger.debug("Query building complete.");
        logger.debug("SQL: " + query.getQueryString());
    }

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