Загружать целые таблицы, включая отношения, в память с помощью JPA - PullRequest
0 голосов
/ 29 октября 2018

Мне нужно обработать огромное количество данных, распределенных по 20 таблицам (в общей сложности ~ 5 миллионов записей), и мне нужно эффективно их загружать.

Я использую Wildfly 14 и JPA / Hibernate.

Поскольку, в конце концов, каждая отдельная запись будет использоваться бизнес-логикой (в той же транзакции), я решил предварительно загрузить все содержимое требуемых таблиц в память просто:

em.createQuery("SELECT e FROM Entity e").size();

После этого каждый объект должен быть доступен в транзакции и, следовательно, доступен через:

em.find(Entity.class, id);

Но это как-то не работает, и в БД все еще много обращений, особенно для отношений.

Как эффективно загрузить все содержимое необходимых таблиц, включая отношения и убедитесь, что я получил все / не будет никаких дальнейших вызовов БД?

Что я уже пробовал:

  • FetchMode.EAGER: Все еще слишком много отдельных выборок / граф объектов слишком сложен
  • EntityGraphs: То же, что и FetchMode.EAGER
  • Операторы извлечения соединения: Наилучшие результаты на данный момент, так как он одновременно заполняет отношения к указанным объектам
  • 2-й уровень / кэш запросов: Не работает, вероятно, та же проблема, что и em.find

Следует отметить, что данные являются неизменяемыми (по крайней мере, в течение определенного времени) и могут также использоваться в других транзакциях.

Edit:

Мой план состоит в том, чтобы загружать и управлять всеми данными в компоненте @Singleton. Но я хочу убедиться, что я загружаю его наиболее эффективным способом и убедиться, что загружены все данные. Не должно быть никаких дополнительных запросов, когда бизнес-логика использует данные. Через определенное время (таймер ejb) я собираюсь отбросить все данные и перезагрузить текущее состояние из БД (всегда целые таблицы ).

Ответы [ 3 ]

0 голосов
/ 12 ноября 2018

Имейте в виду, что вам, вероятно, понадобится 64-разрядная JVM и большой объем памяти. Взгляните на Hibernate 2-й уровень кэша . Некоторые вещи для проверки, так как у нас нет вашего кода:

  1. @Cacheable аннотация будет включать Hibernate, так что объект будет кэшироваться
  2. Сконфигурируйте кэширование 2-го уровня, чтобы использовать что-то вроде ehcache, и установите максимальное количество элементов памяти на что-то достаточно большое, чтобы вместить в него ваш рабочий набор
  3. Убедитесь, что вы случайно не используете несколько сессий в своем коде.

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

0 голосов
/ 16 ноября 2018

По сути, это должно быть довольно простой задачей - загрузить целых таблиц одним запросом к каждой таблице и связать объекты, но JPA работает иначе, как показано в этом примере.

Самой большой проблемой являются @OneToMany / @ManyToMany -отношения:

@Entity
public class Employee {
    @Id
    @Column(name="EMP_ID")
    private long id;
    ...
    @OneToMany(mappedBy="owner")
    private List<Phone> phones;
    ...
}
@Entity
public class Phone {
    @Id
    private long id;    
    ...
    @ManyToOne
    @JoinColumn(name="OWNER_ID")
    private Employee owner;
    ...
}

FetchType.EAGER

Если он определен как FetchType.EAGER, а запрос SELECT e FROM Employee e Hibernate генерирует оператор SQL SELECT * FROM EMPLOYEE и сразу после него SELECT * FROM PHONE WHERE OWNER_ID=? для каждой загруженной Employee, обычно называемой 1 + n проблемы .

Я мог бы избежать проблемы n + 1, используя JPQL-запрос SELECT e FROM Employee e JOIN FETCH e.phones, что приведет к чему-то вроде SELECT * FROM EMPLOYEE LEFT OUTER JOIN PHONE ON EMP_ID = OWNER_ID.

Проблема в том, что это не будет работать для сложной модели данных с ~ 20 задействованными таблицами.

FetchType.LAZY

Если он определен как FetchType.LAZY, запрос SELECT e FROM Employee e будет просто загружать всех сотрудников в качестве прокси, загружая связанные телефоны только при доступе к phones, что в итоге также приведет к проблеме 1 + n.

Чтобы избежать этого, достаточно просто загрузить все телефоны в один сеанс SELECT p FROM Phone p. Но при доступе к phones Hibernate все равно будет выполнять SELECT * FROM PHONE WHERE OWNER_ID=?, потому что Hibernate не знает, что в его текущем сеансе уже есть все телефоны.

Даже при использовании кэша 2-го уровня оператор будет выполняться в БД, поскольку Phone индексируется его первичным ключом в кэше 2-го уровня, а не OWNER_ID.

Заключение

В Hibernate нет такого механизма, как "просто загрузить все данные".

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

EDIT:

Я только что нашел решение, которое работает очень хорошо. Я определил все соответствующие @ManyToMany и @OneToMany как FetchType.EAGER в сочетании с @Fetch(FetchMode.SUBSELECT) и все @ManyToOne с @Fetch(FetchMode.JOIN), что приводит к приемлемому времени загрузки. Помимо добавления javax.persistence.Cacheable(true) ко всем сущностям, я добавил org.hibernate.annotations.Cache к каждой соответствующей коллекции, что позволяет кэшировать коллекции в кэше 2-го уровня. Я отключил удаление тайм-аута кэша 2-го уровня и «прогрел» кэш 2-го уровня через @Singleton EJB в сочетании с @Startup при запуске / развертывании сервера. Теперь у меня есть 100% контроль над кешем, дальнейшие обращения к БД не выполняются, пока я не очищу его вручную.

0 голосов
/ 29 октября 2018

Я понимаю, о чем вы спрашиваете, но JPA / Hibernate не захочет кэшировать столько данных для вас, или, по крайней мере, я не ожидаю от этого гарантии. Учтите, что вы описали 5 миллионов записей. Какова средняя длина записи? 100 байт дают 500 мегабайт памяти, которая просто сломает вашу незатронутую JVM. Вероятно, больше как 5000 байтов в среднем, и это 25 ГБ памяти. Вам нужно подумать о том, что вы просите.

Если вы хотите, чтобы он кешировался, вы должны сделать это самостоятельно или, лучше, просто использовать результаты, когда они у вас есть. Если вы хотите получить доступ к данным на основе памяти, вам следует обратить внимание на технологию, специально предназначенную для этого. http://www.ehcache.org/ кажется популярным, но это зависит от вас, и вы должны быть уверены, что сначала понимаете свой вариант использования.

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

...