По сути, это должно быть довольно простой задачей - загрузить целых таблиц одним запросом к каждой таблице и связать объекты, но 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% контроль над кешем, дальнейшие обращения к БД не выполняются, пока я не очищу его вручную.