惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
The GitHub Blog
The GitHub Blog
博客园 - 【当耐特】
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Recent Announcements
Recent Announcements
D
Docker
GbyAI
GbyAI
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
The Cloudflare Blog
雷峰网
雷峰网
A
About on SuperTechFans
小众软件
小众软件
博客园 - Franky
博客园 - 聂微东
F
Full Disclosure
大猫的无限游戏
大猫的无限游戏
C
Check Point Blog
MongoDB | Blog
MongoDB | Blog
G
Google Developers Blog
Microsoft Azure Blog
Microsoft Azure Blog
U
Unit 42
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
V2EX
Engineering at Meta
Engineering at Meta
宝玉的分享
宝玉的分享
aimingoo的专栏
aimingoo的专栏
量子位
P
Proofpoint News Feed
Hugging Face - Blog
Hugging Face - Blog
博客园_首页
罗磊的独立博客
Martin Fowler
Martin Fowler
D
DataBreaches.Net
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Secure Thoughts
Project Zero
Project Zero
L
LangChain Blog
阮一峰的网络日志
阮一峰的网络日志
C
Cybersecurity and Infrastructure Security Agency CISA
T
Tailwind CSS Blog
S
Schneier on Security
Blog — PlanetScale
Blog — PlanetScale
The Hacker News
The Hacker News
Spread Privacy
Spread Privacy
Security Latest
Security Latest
NISL@THU
NISL@THU
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
C
CXSECURITY Database RSS Feed - CXSecurity.com
J
Java Code Geeks

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром T-TOPS: Как распутать гордиев узел проекта после выхода в прод (меч не понадобится) Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
JPA 4. Переосмысление Hibernate
2026-04-13 · via Все публикации подряд на Хабре

Уровень сложностиПростой

Время на прочтение12 мин

Охват и читатели9.5K

Обзор

Перевод

Данная статья является переводом анонса Гевина Кингa о релизе первого milestone JPA 4 спецификации и общего вектора развития JPA.

Полноценная 4-ая версия JPA спецификации должна выйти чуть-чуть позже в этом году. Тем не менее, то, что сейчас в Milestone это основа, которая, возможно, с небольшими доработками, но уже пойдёт в релиз в этом году.

От себя скажу, что данное обновление будет довольно крупное. Я оставил свои комментарии там, где посчитал уместным.

P.S: У нас в рамках Spring АйО Академии как раз есть программа посвящённая Hibernate. Набор мы на неё ведём до конца апреля.

В ней мы тоже затронем новую версию спецификации, в частности и работу со StatelessSession и EntityAgent на практике. Я думаю, что из всего апдейта, именно стандартизация работы без Persistence Context будет самым важным для прикладных разработчиков. Остальное тоже важно, но не так как работа с EntityAgent.

Приятного чтения!


Недавно мы выпустили первую milestone-сборку Jakarta Persistence 4.0.

Jakarta Persistence — чаще всего его называют JPA — задаёт отраслевой стандарт управления persistence и object/relational mapping в Java. Это самое распространённое решение для persistence в экосистеме Java и без сравнения самый успешный API object/relational mapping в любом языке программирования. JPA 4 — без преувеличения самая значимая переработка спецификации со времён выхода JPA 2.0 в декабре 2009 года.

Эта milestone-сборка предназначена для более широкого обзора сообществом с целью собрать отзывы пользователей и реализаторов. Помните: сейчас самое время присылать feedback. JPA 4 ещё не завершён по функциям, и ни одно из описанных ниже изменений не «высечено в гранит». Не ждите финала спецификации позже в этом году, чтобы попробовать.

Посмотрим, что нового в этом релизе.

EntityAgent

Мы обещали это годами — и наконец это здесь.

Типичные жалобы на object/relational mapping по сути сводятся к одному: для меньшинства разработчиков и для некоторых видов программ managed entities и stateful persistence context «не заходят». Иногда людям нужен более прямой контроль над взаимодействием с базой данных.

Если фундаментальные операции EntityManager (persist, remove, merge, detach, lock, flush) — это операции над persistence context и лишь косвенно затрагивают базу, то фундаментальные операции EntityAgent (insert, update, delete, upsert) обходят persistence context и напрямую меняют базу. Такая модель программирования проще для понимания и рассуждений о ней.

var book = factory.callInTransaction(EntityAgent.class, agent -> {
    return agent.get(Book.class, isbn); // book is immediately detached
});

book.title = "Hibernate in Action"; // change it

factory.runInTransaction(EntityAgent.class, agent -> {
    agent.update(book); // update the database immediately
});
Комментарий

Очень важно, что book в примере выше сразу же "DETACHED". Это означает, что dirty-checking не сработает, что логично, т.к. работа идёт в обход Session.

Как ни странно, на моей практике, это в продуктовой разработке чаще плюс, чем минус, т.к. часто люди как раз не надеются на dirty-check, а вызывают условный repository.save() у Spring Data JPA репозитория напрямую. Нечто похожее теперь придётся делать и в случае использования EntityAgent (собственно, в примере Гевин это и делает).

Ещё один важный момент. Имея сущность Book, у которой есть коллекция авторов, в традиционных Spring Data JPA приложениях разработчик немного побаивается вызвать что-то типа book.authors().size(), т.к. это может привести либо к (в лучшем случае) LIE, либо, что на самом деле хуже, к N + 1. В случае EntityAgent же отвественность на загрузку того или иного relation-a ляжет на приложение. И я думаю, что появление возможности такого контроля это явно плюс.

В общем, если подытожить, я думаю, что Гевин и команда делают однозначно нужное движение в сторону предоставления большего контроля над выполняемыми запросами в целом, делая разработчика ближе к БД.

Тем не менее, подождём реакции Spring Data JPA на эту спецификацию. В целом Spring Framework уже начал реализовывать поддержку EntityAgent. Эта поддержка будет заслуживать отдельной статьи.

Вместе с EntityAgent появился целый зоопарк новых низкоуровневых lifecycle events вроде @PreInsert, @PostUpsert, @PreDelete и т.д.

«Static» queries

Новые @StaticQuery и @StaticNativeQuery, а также @ReadQueryOptions и @WriteQueryOptions проектировались с прицелом на Jakarta Data на первом месте.

@Repository
interface Library {

     @StaticQuery("from Book where isbn = :isbn")
     @ReadQueryOptions(lockMode = PESSIMISTIC_READ)
     Book getBookWithIsbn(String isbn);

     @StaticQuery("from Book where title like :title")
     @ReadQueryOptions(cacheStoreMode = BYPASS)
     List<Book> findBooksByTitle(String title);

     @StaticQuery("delete from Trash")
     @WriteQueryOptions(timeout = 30_000)
     int emptyTrash();
 }

Annotation processor вроде Hibernate Processor может делать тайп чекинг любого статического запроса относительно:

  • entities в persistence unit и

  • сигнатуры метода, который аннотируется.

Ошибки в запросах вы узнаёте сразу, на этапе компиляции, без запуска кода.

Эти аннотации не привязаны к Jakarta Data. С помощью JPA static metamodel static query можно вызывать напрямую через EntityManager или EntityAgent типобезопасным способом.

var books = agent
             .createQuery(Library_.findBooksByTitle("%Jakarta%"))
             .getResultList();
int deleted = agent
                .createStatement(Library_.emptyTrash())
                .execute();

Конечно, static queries не обязаны возвращать entities.

 record Summary(String title, String isbn, LocalDate date) {}

 @StaticQuery("""
     select title, isbn, pubDate
     from Book
     where title like = ?1 and pubDate > ?2
 """)
 List<Summary> retrieveSummaries(String title, LocalDate fromDate);
import static org.example.Library_.retrieveSummaries;

var summaries =
        manager.createQuery(retrieveSummaries("%JPA%",
                                LocalDate.of(2006,5,11)))
               .getResultList();

Static queries открывают гораздо большую часть возможностей JPA, чем обычно доступно при repository-подобной абстракции. О static queries можно сказать ещё много, но нужно двигаться дальше.

Programmatic result set mappings

Аннотация @SqlResultSetMapping существует с JPA 1.0. Она помогает выразить более сложное отображение native SQL result set на Java-объекты. Но аннотации для этой задачи всегда казались мне не идеальной парой, и я годами обещал альтернативу. Она наконец пришла в виде API ResultSetMapping.

В следующем примере явный result set mapping на самом деле не нужен — это только иллюстрация идеи.

var authorResultSetMapping =
         entity(Author.class,
                 field(Author_.ssn, "auth_ssn"),
                 embedded(Author_.name,
                         field(Name_.first, "auth_first_name"),
                         field(Name_.last, "auth_last_name")));
var query =
         """select
                 ssn as auth_ssn,
                 fn as auth_first_name,
                 ln as auth_last_name
              from authors""";
var authors =
        agent.createNativeQuery(query, authorResultSetMapping)
             .getResultList();

Конечно, native SQL query не обязан возвращать entities.

 var constructorMapping =
         constructor(Summary.class,
                 column("isbn", String.class),
                 column("title", String.class),
                 column("author", String.class));
var query =
        "select b.isbn, b.title, a.name"
        + " from books b"
        + " join book_author ba on ba.isbn = b.isbn"
        + " join authors a on ba.ssn = a.ssn";
var summaries =
        manager.createNativeQuery(query, constructorMapping)
               .getResultList();

Новый API приятен и типобезопасен — снова благодаря static metamodel.

С другой стороны, предположим, у нас уже есть result set mapping, заданный через почтенную аннотацию @SqlResultSetMapping.

@SqlResultSetMapping(
        name = "orderResults",
        entities = @EntityResult(
            entityClass = Order.class,
            fields = {
                @FieldResult(name = "id", column = "order_id"),
                @FieldResult(name = "total", column = "order_total"),
                @FieldResult(name = "item", column = "order_item")
            }
        ),
        columns = @ColumnResult(name = "item_name")
)
@Entity
class Order { ... }

Новый API позволяет обращаться к таким mappings типобезопасно — опять же через static metamodel.

var orders =
        entityManager.createNativeQuery(
                """
                  SELECT o.id AS order_id,
                         o.total AS order_total,
                         o.item_id AS order_item,
                         i.desc_name AS item_name
                  FROM orders o, order_items i
                  WHERE order_total > 25 AND order_item = i.id
                """,
                // a typesafe reference to the result set mapping
                Order_._orderResults
        ).getResultList();

Default fetch type для to-one associations

Самая серьёзная ошибка в дизайне JPA 1.0 — сделать для ассоциаций @ManyToOne и @OneToOne значение по умолчанию fetch=EAGER. Страшно думать, сколько миллионов долларов (вполне правдоподобно — миллиард) эта одна плохая настройка по умолчанию стоила индустрии с мая 2006 года. Когда кто-то жалуется, что Hibernate или JPA генерируют запросы с кучей JOIN-ов, мне хочется крикнуть в монитор: «нет, вы должны выставить LAZY!» И я знаю: почти полностью это моя вина — я молчал и соглашался с тем, что знал неверным.

Теперь можно сделать LAZY значением по умолчанию на уровне persistence unit:

<default-to-one-fetch-type>LAZY<default-to-one-fetch-type>

или:

persistenceConfiguration.defaultToOneFetchType(FetchType.LAZY)

Пожалуйста, делайте так в каждом новом проекте.

Комментарий

Тут два момента.

  1. Обратите внимание, что дефолт для @ManyToOne / @OneToOneне поменяется (с целью упрощения миграции, т.к. иначе оргомное кол-во приложений, кто надеялись на EAGER просто сломаются)

  2. И второе - вводитcя новый FetchType.DEFAULT. Делается это опять же с целью, чтобы миграция была планомерной. Этот change binary-compatible и behaviorly backward compatible тоже.

    Но вот это мне кажется страшно, т.к. теперь, смотря на код, Вы не сможите понять, какой же в итоге фетч тайп-то?! EAGER или LAZY? Вам теперь надо будет понимать, какой дефолт выставлен в Persistence Unit, который конечно же Вы руками не настраиваете, вы надеятесь на Spring Boot авто-конифгурацию для JPA. Короче вот тут посмотрим что получится.

Statement и TypedQuery

С этим изменением мы долго боролись.

JPA 1.0 проектировался до появления generics в Java. Поддержку generics «достроили» в JPA 2.0, и, к сожалению, мы ошиблись, оставив Query, возвращающий raw List, и введя TypedQuery как generic-подтип. Из-за этого вызовы Query.getResultList() дают предупреждения компилятора и риск "unchecked casts". Тогда мотивом была обратная совместимость с клиентами под JPA 1.0, но честно — это можно было решить лучше. Конечно, тогда никто по-настоящему не понимал Java generics — всё было новым, такие ошибки были почти неизбежны.

К тому же у JPA не было отдельного API для выполнения update и delete statements, и со временем мы пришли к мысли, что оно должно быть.

С учётом этого сделано следующее:

  • введён интерфейс Statement для выполнения запросов без результата;

  • использование Query для прямого выполнения statements и queries помечено как deprecated;

  • Мы более четко прописали, что настоящие queries — это statement-ы, возвращающие результаты. Их следует выполнять через TypedQuery (вы кстати и так должны были так делать, чтобы избежать предупреждений компилятора).

К счастью, путь миграции с deprecated-модели простой.

Было:

List<Book> books = // ouch, unchecked cast
        em.createQuery("from Book where extract(year from publicationDate) > :year")
                .setParameter("year", Year.of(2000)) // now deprecated
                .setMaxResults(10) // now deprecated
                .setCacheRetrieveMode(CacheRetrieveMode.BYPASS) // now deprecated
                .getResultList(); // ouch, compiler warning

Достаточно одной строки:

List<Book> books =
        em.createQuery("from Book where extract(year from publicationDate) > :year")
                .ofType(Book.class)  // just add this
                .setParameter("year", Year.of(2000))
                .setMaxResults(10)
                .setCacheRetrieveMode(CacheRetrieveMode.BYPASS)
                .getResultList();

Или для statement:

int updated =
        em.createQuery("delete from Temporary where timestamp > ?1")
                .setParameter(1, cutoffDateTime) // now deprecated
                .executeUpdate(); // now deprecated

Две строки:

int updated =
        em.createQuery("delete from Temporary where timestamp > ?1")
                .asStatement()
                .setParameter(1, cutoffDateTime)
                .execute();

Или просто:

int updated =
        em.createStatement("delete from Temporary where timestamp > ?1")
                .setParameter(1, cutoffDateTime)
                .execute();

Мы понимаем, что подстраивать старый код под такие изменения раздражает. Но делать это срочно не обязательно: в жизненном цикле JPA 4 мы ничего не будем реально удалять. Такие правки довольно прямолинейно автоматизировать, например, чем-то вроде OpenRewrite.

get(), findMultiple() и getMultiple()

Операция find() у EntityManager или EntityAgent загружает entity по primary key и возвращает null, если entity не найдена. Иногда это полезно, но чаще отсутствие entity — ошибка, которую нужно сигнализировать через EntityNotFoundException. Именно это делает новый метод get(). Используйте get() вместо find(), если только ваш код явно не обрабатывает null от find().

Новые методы findMultiple() и getMultiple() по смыслу аналогичны find() и get() соответственно, но принимают списки primary keys и позволяют эффективно получить «пакет» entities.

List<Book> getBooks(List<String> isbns) {
    return agent.getMultiple(Book.class, isbns,
                             CacheRetrieveMode.BYPASS,
                             LockModeType.OPTIMISTIC);
}

getResultCount()

Давно запрашиваемый новый метод TypedQuery.getResultCount() возвращает общее число результатов query.

setParameter() и setConvertedParameter()

Иногда полезно явно указать тип query parameter при передаче значения. Для JPQL queries это обычно не нужно, но встречается в native SQL statements и queries, где тип parameter нельзя вывести из самого запроса.

manager.createNativeStatement("update books set pub_date = :date where isbn = :ISBN")
       .setParameter("date", optionalPublicationDate, LocalDate.class)
       .setParameter("ISBN", isbn)
       .execute();

Optional select new

Язык запросов в JPA 4 (его переопределяют как Jakarta Query) больше не требует синтаксиса select new для queries, возвращающих проекции через простой Java record. Вместо этого record-ы можно указать как query result class.

record Summary(String title, String isbn, LocalDate date) {}

var summaries =
        agent.createQuery(
                """
                   select title, isbn, pubDate
                   from Book
                   where title like = ?1 and pubDate > ?2
                """)
            .ofType(Summary.class)
            .setParameter(1, titlePattern)
            .setParameter(2, minDate)
            .getResultList();

Пример мы уже видели при разговоре о статических запросах выше.

@ExcludedFromVersioning

Иногда полезно разрешить изменять отдельное поле entity без optimistic version check. Это означает допустимость lost updates для этого поля и может повысить concurrency. Реализации позволяли это давно; теперь это стандартизовано аннотацией @ExcludedFromVersioning.

StoredProcedureQuery

Интерфейс StoredProcedureQuery получил ряд существенных улучшений для более типобезопасного вызова stored procedures. Здесь я их не разбираю.

Programmatic registration слушателей entity lifecycle callbacks

Теперь можно зарегистрировать listener для типа entity lifecycle event во время выполнения.

factory.addListener(Book.class, PostPersist.class,
        book -> System.out.println("Book persisted: " + book.title));

Это особенно интересно разработчикам библиотек и фреймворков.

Для прикладных разработчиков, возможно, важнее другое: у одного entity listener class теперь может быть несколько методов-слушателей одного типа callback — например, несколько @PrePersist для разных типов entities.

Named entity graphs

Раньше named entity graph всегда задавался сложными вложенными аннотациями, которые по имени ссылались на поля entity, входящие в graph. Так по-прежнему можно, но в JPA 4 есть альтернатива: аннотировать включаемые поля graph’ом, к которому они относятся.

 @NamedEntityGraph(name = "EmployeeWithProjectTasksAndEmployer")
 @Entity // root entity of graph
 public class Employee {
     ...
     // fetched attribute node
     @NamedEntityGraphAttributeNode(graph = "EmployeeWithProjectTasksAndEmployer")
     @ManyToOne(fetch=LAZY) Employer employer;

     // reference to subgraph defined in Project class
     @NamedEntityGraphSubgraph(graph = "EmployeeWithProjectTasksAndEmployer")
     @ManyToMany Set<Project> projects;
 }

 @NamedEntityGraph(name = "EmployeeWithProjectTasksAndEmployer")
 @Entity // root entity of subgraph
 public class Project {
     ...
     // reference to subgraph defined in Task class
     @NamedEntityGraphSubgraph(graph = "EmployeeWithProjectTasksAndEmployer")
     @OneToMany List<Task> tasks;
 }

Named entity graphs по-прежнему далеки от того, чтобы стать моей любимой фичей JPA, но для тех, кто ими пользуется, это, пожалуй, заметное улучшение API.

Data loading и Schema Export

В SchemaManager добавлен метод populate() — чтобы удобно заполнять schema данными из DML script.

Метод PersistenceConfiguration.exportSchema() позволяет выполнять действия по управлению schema до инстанцирования EntityManagerFactory.

Pessimistic lock scopes

PessimisticLockScope появился в JPA 2.1 и позволял просить реализацию JPA взять pessimistic lock не только на entity, но и на всё связанное состояние в join tables и collection tables. Честно говоря, я так и не до конца понял эту фичу и не представляю, когда бы ею пользовался. Для всех, кроме самых тривиальных entities, это блокирует слишком много, включая строки, которые вы даже не читаете.

Выбирая между простым deprecation этой фичи и превращением её во что-то полезнее, мы пошли по второму пути. Новая опция PessimisticLockScope.FETCHED блокирует строки join tables и collection tables только если вы реально читаете их и подтягиваете их состояние в рамках операции, которая получает lock.

JDBC fetch size и batch size

Свойства jakarta.persistence.jdbc.fetchSize и jakarta.persistence.jdbc.batchSize теперь формально описаны спецификацией как стандартный способ управлять fetch size и batch size соответственно. Это важно, потому что у одного JDBC driver-а для одной очень важной базы плохое значение по умолчанию.

Изменения в контракте container/provider

Мы изменили SPI, регулирующие контракт между Persistence Provider-ом (т.е. Hibernate) и контейнером Jakarta EE, чтобы:

  • контейнер мог полностью взять на себя discovery классов, связанных с Persistence, в persistence unit — он делает это эффективнее, чем Persistence Provider;

  • разделить процесс class loading и enhancement от инстанцирования EntityManagerFactory, решая колючую проблему интеграции между CDI BeanManager и PersistenceProvider.

Specification

Сама спецификация — критически важная документация и для пользователей, и для реализаций. В рамках постоянной работы над читаемостью spec-и переработаны и переписаны большинство разделов первых двух глав и отдельные фрагменты по всему документу.

Связь с Jakarta Data и Jakarta Query

Важно поставить работу над Jakarta Persistence 4 в контекст. Описанное здесь — часть более крупного усилия, охватывающего три спецификации: Persistence, Query и Data. Компоненты спроектированы так, чтобы идеально стыковаться и давать бесшовный переход между более декларативной repository-моделью Jakarta Data и более программной моделью Jakarta Persistence без потери type safety.

Надеюсь, вам это понравится!

Присоединяйтесь к русскоязычному сообществу разработчиков на Spring Boot в телеграм — Spring АйО, чтобы быть в курсе последних новостей из мира разработки на Spring Boot и всего, что с ним связано.