{"id":30802327,"url":"https://github.com/comru/jpa-partial-load","last_synced_at":"2025-09-05T21:50:23.075Z","repository":{"id":246541398,"uuid":"817808074","full_name":"comru/jpa-partial-load","owner":"comru","description":"All methods of partial loading of JPA entities","archived":false,"fork":false,"pushed_at":"2024-08-14T12:37:14.000Z","size":295,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-08-14T16:26:48.714Z","etag":null,"topics":["hibernate","jpa","spring-data","spring-data-jpa"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/comru.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-20T13:34:57.000Z","updated_at":"2024-08-14T12:37:17.000Z","dependencies_parsed_at":"2024-06-28T15:45:28.056Z","dependency_job_id":"f3a7c3ac-5e0e-4def-b973-1d11ef86b09d","html_url":"https://github.com/comru/jpa-partial-load","commit_stats":null,"previous_names":["comru/jpa-partial-load"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/comru/jpa-partial-load","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comru%2Fjpa-partial-load","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comru%2Fjpa-partial-load/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comru%2Fjpa-partial-load/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comru%2Fjpa-partial-load/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/comru","download_url":"https://codeload.github.com/comru/jpa-partial-load/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/comru%2Fjpa-partial-load/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273827265,"owners_count":25175239,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-05T02:00:09.113Z","response_time":402,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hibernate","jpa","spring-data","spring-data-jpa"],"created_at":"2025-09-05T21:50:14.209Z","updated_at":"2025-09-05T21:50:23.060Z","avatar_url":"https://github.com/comru.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"## JPA Entity. Загрузи меня не полностью!\n\n![](/img/mem1.png)\n\n\n### Содержание\n\n- [О проекте](#о-проекте)\n- [Интересует ли эта проблема сообщество](#интересует-ли-эта-проблема-сообщество)\n- [Задача](#задача)\n- [Тестовые данные](#тестовые-данные)\n- [Тестирование](#тестирование)\n    - [Basic attributes](#basic-attributes)\n    - [ToOne](#to-one)\n    - [ToMany](#to-many)\n- [Выводы](#выводы)\n- [Getting Started](#getting-started)\n- [Built With](#built-with)\n- [TODO](#todo)\n\n### О проекте\n\nJPA часто предъявляют за невозможность загружать сущности частично, что на самом деле является большим заблуждением.\nSpring Data JPA \u0026 Hibernate включают в себя множество инструментов по частичной загрузке сущностей. В рамках исследования\nрассмотрим имеющиеся в Spring Data JPA инструменты, разберем их особенности и посмотрим на corner case.\nДавайте попробуем рассмотреть все способы такой частичной загрузки сущностей. Рассмотрим на примере основых способов\nвзаимодействия с Hibernate в Spring приложениях:\n\n- Spring Data JPA\n- EntityManager\n- Criteria API\n\nСуществует еще проект [Jakarta Data](https://github.com/jakartaee/data), который активно развивается. Пока вышла только\nпервая стабильная версия 1.0.0 и мы не будем его рассматривать в данном эксперименте.\n\n### Интересует ли эта проблема сообщество\nО да, еще как интересует! Для этого отправимся в путешествие на stackoverflow. Напишем в поиске тег [spring-data-jpa] и\nотсортируем по популярности вопросы. Мы увидим, что вопрос [Spring JPA selecting specific columns](https://stackoverflow.com/questions/22007341/spring-jpa-selecting-specific-columns)\nнаходится на шестом месте.\nИ в этом проекте мы постараемся ответить на этот вопрос максимально полно. Наш обзор будет касаться не только загрузки\nбазовых атрибутов, но мы также окунемся в мир ассоциаций JPA.\n\n### Задача\n\nВ данном проекте будет рассматриваться следующая модель данных:\n\n```mermaid\nclassDiagram\n    Post --\u003e User: author\n    Post --\u003e User: likeUsers\n    class Post {\n        Long id\n        String slug\n        String tittle\n        String body\n        Instant createAt\n        Instant updatedAt\n        User author\n        Se~User~ likeUsers\n    }\n    class User {\n        Long id\n        String email\n        String password\n        String token\n        String username\n        String bio\n        byte[] image\n    }\n```\n\nНаша задача для каждого способа частичной загрузки попробовать загрузить следующие данные:\n\n1. Несколько базовый полей из сущности Author - id, slug, tittle.\n2. Несколько базовый полей + ToOne ассоциация. В нашем случае это author: User - id, username\n3. Несколько базовый полей + ToMany ассоциация. Для likeUsers также будем загружать id, username.\n\nЗапрос буде простой - найти все статьи заголовок которых содержит заданный текст. Поиск не чувствительный к регистру,\nт.е. contains with ignore case.\n\nПроверять результат мы будем в соответствующих тестах, результат запроса будут видны в консоли. Специальный анализ\nлогов, который бы показывал что выбираются только те поля что мы хотим, я не писал. Просто смотрю в логи, какой sql\nгенерирует hibernate.\n\n### Тестовые данные\n\nСоздадим две записи Post c двумя подписчиками и автором. Сервис в котором создаются и удаляются данные [InitTestDataService](/src/test/java/io/amplicode/jpa/InitTestDataService.java)\n\n### Тестирование\n\nПод derived method будем понимать, запросы которые основаны на имени метода, т.е. без явного указания аннотации @Query.\nВ данном случае, у нас есть всего один способ как мы можем указать какие атрибуты мы хотим загрузить,\nэто [projection](https://docs.spring.io/spring-data/jpa/reference/repositories/projections.html). Но проекции бывают\nдвух видов, основанные на интерфейсах (Interface-based Projections) и на классах (Class-based Projections DTOs). Еще есть,\nтак\nназываемые [Open Projections](https://docs.spring.io/spring-data/jpa/reference/repositories/projections.html#projections.interfaces.open),\nгде значение гетеров интерфейсов могу высчитываться на основе SpEL выражения:\n\n```java\ninterface NamesOnly {\n    @Value(\"#{target.firstname + ' ' + target.lastname}\")\n    String getFullName();\n}\n```\n\nИх мы рассматривать не будет, т.к. в документации явно сказано, что для них оптимизация запроса производиться не будет.\nЭто действительно так, я проверил.\n\u003e Spring Data cannot apply query execution optimizations in this case, because the SpEL expression could use any\n\u003e attribute of the aggregate root.\n\nТакже, для загрузки ToOne ассоциаций мы хотим проверить два варианта с flatten(плоскими) атрибутами и с nested(\nвложенным) классом. Для ToMany все устроенно чуть сложнее, там также может быть кейс с nested и flat, но сам hibernate не умеет мапить вложенные коллекции, он вернет плоские записи и нам самостоятельно придется их помапить.\n\nВажно, в тестах будут приведены только рабочие варианты, т.е. те которые успешно справляются с поставленной задачей, т.е. частичной загрузкой.\nПримеры в которых можно загрузить данные, но они выгружают больше данных чем нужно, рассматриваться не будут.\n\n#### Basic attributes\nВсего я нашел 17 способов частичной загрузки для кейса когда нам надо загрузить только basic attributes.\nЭти способы включают в себя разные подходы написания запроса:\n\n- Repository derived methods\n- Repository query methods\n- Entity Manager\n- Criteria API\n\nТестовый класс в котором можно увидеть все тесты с\nкомментариями - [BasicAttributesTest](/src/test/java/io/amplicode/jpa/repository/BasicAttributesTest.java).\nПоскольку проект находится в стадии разработки, могут дополняться или убираться методы, но в конечном варианте должны\nсоответствовать следующему списку:\n\n1. Repository derived methods. Interface-based projections\n2. Repository derived methods. Class-based projections\n3. Repository query methods. Interface-based projections\n4. Repository query methods. Class-based projections\n5. Repository query methods. Object Array\n6. Repository query methods. Tuple\n7. Repository query methods. Map (select new map)\n8. Repository query methods. List (select new list)\n9. Entity Manager. Object Array\n10. Entity Manager. Tuple\n11. Entity Manager. Class-based Projections (select new)\n12. Entity Manager. Map (select new map)\n13. Entity Manager. List (select new list)\n14. Criteria API. Object Array\n15. Criteria API. Tuple\n16. Criteria API. Class-based Projections\n17. Criteria API. List\n\n#### To one\nТестовый класс в котором можно увидеть все тесты с\nкомментариями - [ToOneTest](/src/test/java/io/amplicode/jpa/repository/ToOneTest.java).\n\nРассматриваются следующие кейсы:\n\n1. Repository derived methods. Interface-based flat projections\n2. Repository derived methods. Interface-based nested projections. Это как раз тот случай когда запрос выполняется, но работает не правильно, т.е. загружает больше полей чем надо. \n3. Repository derived methods. Class-based flat projections\n4. Repository query methods. Interface-based flat projections\n5. Repository query methods. Interface-based with nested dto class projections.\n6. Repository query methods. Class-based flat projections\n7. Repository query methods. Class-based nested class projections\n8. Repository query methods. Class-based nested map projections\n9. Repository query methods. Tuple\n10. Repository query methods. Map (select new map)\n11. Repository query methods. Object Array\n12. Repository query methods. List (select new list)\n13. Entity Manager. Class-based Projections (select new)\n14. Entity Manager. Tuple\n15. Entity Manager. Map (select new map)\n16. Entity Manager. Object Array\n17. Entity Manager. List (select new list)\n18. Criteria API. Tuple\n19. Criteria API. Class-based Projections\n20. Criteria API. Object Array\n21. Criteria API. List (list)\n\n#### To many\n\nТестовый класс в котором можно увидеть все тесты с\nкомментариями - [ToManyTest](/src/test/java/io/amplicode/jpa/repository/ToManyTest.java).\n\nРассматриваются следующие кейсы:\n\n1. Repository derived methods. Interface-based projections\n2. Repository derived methods. Class-based projections\n3. Repository query methods. Interface-based nested projections\n4. Repository query methods. Class-based flat projections\n5. Repository query methods. Class-based nested projections\n6. Repository query methods. Object Array\n7. Repository query methods. Tuple\n8. Repository query methods. Map (select new map)\n9. Repository query methods. List (select new list)\n10. Entity Manager. Class-based Projections (select new)\n11. Entity Manager. Tuple\n12. Entity Manager. Map (select new map)\n13. Entity Manager. Object Array\n14. Entity Manager. List (select new list)\n15. Criteria API. Tuple\n16. Criteria API. Object Array\n17. Criteria API. Class-based Projections\n18. Criteria API. List (select new list)\n\n### Выводы\n\n1. Если мы пишем HQL/JPQL query, то мы контролируем запрос и возвращаем только то что мы хотим. Вопрос только в том как мапить.\n2. Если мы пишем HQL/JPQL всегда можно вернуть tuple или Map и помапить с него на dto.\n3. Использовать ли repository derived method, решает каждый сам. В простых случаях и HQL будет простым,\n   в сложных длинна имени метода будет стремиться выйти за приделы нашей солнечной системы. В рамках решаемых задач для этого проекта, конкретно частичная выгрузка данных, repository derived method выглядит самым не безопасным. Мы не контролируем запрос, HQL может поменяться из-за изменения DTO/Projection или самой Entity.\n4. Когда мы работаем с Tuple очень удобно подключить библиотеку hibernate-jpamodelgen и использовать автогенеренные\n   константы. В последней документации hibernate данный способ используется во всех примерах, можно сказать, что это тихая\n   рекомендация. Также, используя эти константы легко создавать jakarta.persistence.criteria.Path для Criteria API.\n5. Когда мы пишем Query в spring data и используем DTO, по дефолту будут валидироваться выражение с DTO,\n   будут проверены как типы, так и количество аргументов в конструкторе. А самое главное ни какой прокси магии.\n6. Не знаете что вернуть, верните Tuple. Это очень удобно. \n7. HQL + Class-Based Projection, он же DTO, он же select new class конструкция работают всегда. \n8. Для ToMany, при выгрузки данных в виде Projection/DTO/Tuple нам придется решать вопрос о мердже дублирующихся данных.  \n\n### Built With\n\n- Spring Boot\n- Java 21\n- Spring Data JPA\n- Hibernate\n- H2\n- PostgreSQL\n- Junit\n\n### Getting Started\n\n1. Clone project\n2. Go to project directory\n3. Run project tests\n\n```shell\n./gradlew test\n```\n\n### TODO\n- Надо ли рассматривать Embedded?\n- Надо ли рассматривать JPA Specification и его проблемы?\n- Надо ли рассматривать Pagination?\n- Кастом мапинг, кажется вообще не работает. Надо ли об этом говорить?\n  org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from\n  type [org.springframework.data.jpa.repository.query.AbstractJpaQuery$TupleConverter$TupleBackedMap] to\n  type [io.amplicode.jpa.projection.PostBasicDto]\n  В классе org.springframework.data.repository.query.ResultProcessor.ProjectingConverter в котором и происходит\n  конвертация resultType\n  есть ConversionService, но всегда инициализируется как DefaultConversionService.getSharedInstance() и его не очень то\n  и пополнишь(\n- Как правильно называется способ писать запрос на основе имени метода - \"Repository derived methods\"? ","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcomru%2Fjpa-partial-load","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcomru%2Fjpa-partial-load","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcomru%2Fjpa-partial-load/lists"}