Почему проекции интерфейса намного медленнее, чем проекции конструктора и проекции сущностей в Spring Data JPA с Hibernate?
Мне было интересно, какой тип проекций я должен использовать, поэтому я провел небольшой тест, который охватил 5 типов проекций (на основе документов: https://docs.spring.io/spring-data/jpa/docs/current/reference/html/):
1. Сущность проекции
Это просто стандарт findAll()
предоставляется репозитарием Spring Data. Здесь нет ничего особенного.
Обслуживание:
List<SampleEntity> projections = sampleRepository.findAll();
Сущность:
@Entity
@Table(name = "SAMPLE_ENTITIES")
public class SampleEntity {
@Id
private Long id;
private String name;
private String city;
private Integer age;
}
2. Конструктор проекции
Обслуживание:
List<NameOnlyDTO> projections = sampleRepository.findAllNameOnlyConstructorProjection();
Repository:
@Query("select new path.to.dto.NameOnlyDTO(e.name) from SampleEntity e")
List<NameOnlyDTO> findAllNameOnlyConstructorProjection();
Объект передачи данных:
@NoArgsConstructor
@AllArgsConstructor
public class NameOnlyDTO {
private String name;
}
3. Интерфейс проекции
Обслуживание:
List<NameOnly> projections = sampleRepository.findAllNameOnlyBy();
Repository:
List<NameOnly> findAllNameOnlyBy();
Интерфейс:
public interface NameOnly {
String getName();
}
4. Коррекция проекций
Обслуживание:
List<Tuple> projections = sampleRepository.findAllNameOnlyTupleProjection();
Repository:
@Query("select e.name as name from SampleEntity e")
List<Tuple> findAllNameOnlyTupleProjection();
5. Динамическая проекция
Обслуживание:
List<DynamicProjectionDTO> projections = sampleRepository.findAllBy(DynamicProjectionDTO.class);
Repository:
<T> List<T> findAllBy(Class<T> type);
Объект передачи данных:
public class DynamicProjectionDTO {
private String name;
public DynamicProjectionDTO(String name) {
this.name = name;
}
}
Некоторая дополнительная информация:
Проект был построен с использованием загрузочного плагина Gradle Spring (версия 2.0.4), который использует Spring 5.0.8 под капотом. База данных: H2 в памяти.
Результаты:
Entity projections took 161.61 ms on average out of 100 iterations.
Constructor projections took 24.84 ms on average out of 100 iterations.
Interface projections took 252.26 ms on average out of 100 iterations.
Tuple projections took 21.41 ms on average out of 100 iterations.
Dynamic projections took 23.62 ms on average out of 100 iterations.
-----------------------------------------------------------------------
One iteration retrieved (from DB) and projected 100 000 objects.
-----------------------------------------------------------------------
Заметки:
Понятно, что получение сущностей занимает некоторое время. Hibernate отслеживает эти объекты на предмет изменений, отложенной загрузки и так далее.
Проекции конструктора очень быстрые и не имеют ограничений на стороне DTO, но требуют ручного создания объекта в @Query
аннотаций.
Проекция интерфейса оказалась очень медленной. Смотри вопрос.
Проекции кортежей были самыми быстрыми, но не самыми удобными для игры. Им нужен псевдоним в JPQL, и данные должны быть получены путем вызова .get("name")
вместо .getName()
,
Динамические проекции выглядят довольно круто и быстро, но должны иметь ровно один конструктор. Не больше, не меньше. В противном случае Spring Data выдает исключение, потому что не знает, какой использовать (для определения данных, которые нужно извлечь из БД, нужны параметры конструктора).
Вопрос:
Почему проекции интерфейса занимают больше времени, чем извлечение объектов? Каждая возвращаемая проекция интерфейса на самом деле является прокси. Это так дорого, чтобы создать этот прокси? Если так, разве это не противоречит главной цели проекций (поскольку они должны быть быстрее, чем объекты)? Другие прогнозы выглядят потрясающе. Мне бы очень хотелось немного разобраться в этом. Спасибо.
РЕДАКТИРОВАТЬ: Вот тестовый репозиторий: https://github.com/aurora-software-ks/spring-boot-projections-test если вы хотите запустить его самостоятельно. Это очень легко настроить. Readme содержит все, что вам нужно знать.
2 ответа
Я столкнулся с похожим поведением со старой версией Spring Data, и это был мой выбор: https://blog.arnoldgalovics.com/how-much-projections-can-help/
Я поговорил с Оливером Гирке (лидером Spring Data), и он внес некоторые улучшения (вот почему вы получаете такие "хорошие" результаты:-)), но в основном всегда будет стоить наличие абстракций по сравнению с кодированием вручную.
Это компромисс, как и все остальное. С одной стороны, вы получаете гибкость, более простую разработку, меньше обслуживания (надеюсь), с другой стороны, вы получаете полный контроль, немного более уродливую модель запросов.
У каждого есть свои плюсы и минусы:
Проекция интерфейса: допускается вложенная, динамическая и открытая проекция, но Spring генерирует прокси во время выполнения.
Проекция DTO: быстрее, но не допускается вложенная, динамическая и открытая проекция.