В новом переводе от команды Spring АйО разберем, почему stringly-typed API со временем становятся хрупкими, чем помогают метамодели вроде Querydsl и JPA Criteria, и как новый механизм в Spring Data даёт более лёгкую и естественную альтернативу без лишней инфраструктуры сборки.
Если вы уже некоторое время работаете с доступом к данным в Java и особенно со Spring Data, то наверняка знакомы с различными программными моделями Query и Update. Вы пишете код доступа к данным. Вы переименовываете свойство при рефакторинге. Запускаете тесты. Они падают. А ваши @Query запросы? Всё ещё указывают на старое имя свойства — потому что строки не рефакторятся.
Sort.by("firstName", "lastName"); where("address.country").is(…);
Построение запросов часто включает обращение к свойствам доменной модели через строки — будь то предикаты, сортировка или навигация по пути. Такой подход прост и намеренно «лёгкий». Он не требует дополнительной настройки и естественно встраивается в прикладной код. Для многих сценариев это разумный вариант по умолчанию. Однако у строковых ссылок на свойства есть ограничения, которые становятся заметнее по мере того, как приложения развиваются со временем.
Как строковые ссылки на свойства работают на практике
Использование строк для обращения к свойствам — прагматичное проектное решение. Оно не требует дополнительной настройки, генерации кода и изменений в процессе сборки. Но за эту простоту приходится платить врождённой хрупкостью, перекладывая часть ответственности на рантайм.
Имена свойств, заданные строками, не проверяются компилятором. Опечатки, устаревшие имена после рефакторинга или несоответствия между доменной моделью и определениями запросов успешно компилируются и, как правило, проявляются только при выполнении кода. В некоторых случаях тесты позволяют обнаружить такие проблемы рано, но это возможно не всегда.
Строки дают мало контекста для рефакторинга. IDE умеют надёжно обновлять ссылки на методы и поля, но строковые литералы лишены семантического контекста — по сути это просто текст. Когда между точками выполнения и объявлениями запросов появляется заметная «дистанция», инструментам всё сложнее корректно связать их с соответствующими доменными типами. В больших кодовых базах это может приводить к ошибкам, которые легко пропустить.
Эти проблемы не новы и не уникальны для Spring Data. Это естественное следствие API, построенных на «строковой типизации» (stringly-typed).
Подходы на основе метамодели
В Java-экосистеме есть несколько альтернатив, основанных на метамоделях. Такие подходы представляют доменные модели или схемы базы данных в структурированном, типобезопасном виде.
Фреймворки вроде Querydsl и JPA Metamodel Generator опираются на обработку аннотаций для генерации классов метамодели. Использовать их в прикладном коде довольно удобно, что иллюстрируют следующие примеры, отражающие базовую доменную модель:
Querydsl:
QPerson person = QPerson.person; query.where(person.firstName.eq("John"));
JPA Criteria API:
CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery query = cb.createQuery(Person.class); Root person = query.from(Person.class); query.where(cb.equal(person.get(Person_.firstName), "John"));
Сгенерированные метамодели обеспечивают проверку на этапе компиляции. Одновременно они обычно добавляют генерацию кода на этапе сборки и нередко — дополнительные зависимости. При рефакторинге сгенерированный код должен последовательно пересоздаваться, что может становиться источником трений.
Такие метамодели лучше всего работают в рамках своего «родного» контекста: метамодель JPA естественно интегрируется с Criteria API и поддержкой Specification в Spring Data JPA. За пределами этих сценариев требуются дополнительные слои интеграции. Spring Data предоставляет адаптеры вроде JpaSort (для выражений путей метамодели JPA) или QSort (для спецификаторов сортировки Querydsl), но доступность и покрытие возможностей различаются между модулями.
Другие библиотеки, например jOOQ, больше фокусируются непосредственно на схеме базы данных. Плагины jOOQ могут помочь с генерацией метамодели на этапе сборки, отражающей таблицы и столбцы:
Result result = create.select() .from(PERSON) .where(PERSON.FIRST_NAME.eq("John")) .fetch();
Каждый из этих подходов предполагает компромиссы между безопасностью, гибкостью и операционной сложностью.
Соображения по инфраструктуре сборки
Генерация метамодели обычно использует процессоры аннотаций или специализированные плагины сборки. В более зрелых проектах, где доменные модели меняются довольно редко, повторная генерация приводит к одному и тому же сгенерированному коду метамодели — ценой времени сборки.
Поддержка сгенерированных исходников в IDE со временем заметно улучшилась, что в целом сделало работу комфортнее, но поведение различается между средами и может требовать явной настройки. Ничто из этого не является непреодолимым препятствием, однако каждый такой нюанс добавляет проекту сложности в тех областях, к которым обычно возвращаются редко. А значит, сложность повышает затраты на диагностику и устранение проблем по сравнению с теми частями проекта, с которыми сопровождающие работают значительно чаще.
Комментарий от Михаила Поливаха
К тому же, проблема metamodel-based подоходов в том, что даже при генерации метамодели во время сборки с помощью annotation processing-а рефакторинг остаётся усложнённым.
Например, если мы условно поменяем имя проперти в нашей JPA Entity, то те части, которые использовали метамодель доступа к этой проперти просто вызовут ошибку компиляции.
Опять же, есть Intelij/Open IDE, есть "cntrl + shift + R" и массивная автозамена, но это всё борьба с последствиями.
Типобезопасные ссылки на свойства
До сих пор мы обсуждали существующие подходы, их негативное влияние на сопровождаемость и то, как увеличивающаяся «дистанция» порождает классы потенциальных ошибок. В идеальном мире современный код доступа к данным должен уметь опираться на примитивы языка, чтобы ссылаться на свойства типобезопасно и с сохранением рефакторинг-безопасности. И в Spring Data 2026.0.0-M1 мы с гордостью представляем первоклассную поддержку типобезопасных ссылок на свойства.
Вернёмся к самому первому примеру:
Sort.by("firstName", "lastName");
Теперь преобразуем его в типобезопасный вариант с использованием ссылок на методы:
Sort.by(Person::getFirstName, Person::getLastName);
Один вариант использует строковые литералы. Другой — это код. Обратите внимание, насколько естественно ссылки на методы выражают намерение.
Компилятор проверяет, что getFirstName() существует в Person. IDE может перейти к определению метода. Инструменты рефакторинга работают ожидаемо. Это типобезопасно, лаконично и не требует дополнительной инфраструктуры.
Для вложенных свойств композиция выглядит столь же естественно, хотя читается чуть более многословно:
Sort.by(PropertyPath.of(Person::getAddress).then(Address::getCountry));
Каждый шаг в пути проверяется системой типов. Неверные пути «падают» быстро — как правило, это сразу подсвечивает IDE, но в любом случае ошибка проявляется на этапе компиляции, а не в рантайме. Кроме того, вынесение понятия «владельца типа» в интерфейс позволяет методам накладывать разумные ограничения. Например, определение сортировки может гарантировать, что все свойства происходят из одного и того же доменного типа:
Sort.by(Person::getFirstName, Person::getLastName);
При большем контексте всю работу делает система типов:
Sort.by(Person::getFirstName, Order::getOrderDate); // Ошибка компиляции: несовместимые типы-владельцы согласно: // Sort by(TypedPropertyPath<T, ?>... properties)
Преимущество здесь не только в безопасности — но и в ясности. Намерение сразу очевидно, а система типов принуждает к корректности.
Поддержка Kotlin
Kotlin уже предоставляет первоклассную поддержку ссылок на свойства. Spring Data постепенно адаптировала Kotlin-дружелюбные API, используя перегрузку операторов для оператора div (/) при навигации по путям свойств. Такие приложения могут эффективно применять ссылки KProperty в различных реализациях Criteria — для всех методов, принимающих TypedPropertyPath, — следуя естественным идиомам Kotlin:
Sort.by(Person::address) Sort.by(Person::address / Address::city)
Комментарий от Михаила Поливаха
Для парней на Kotlin подобный типобезопасный способ референса поддерживался уже относительно давно. Как минимум Spring Data MongoDB активно его развивала, за другие модули не скажу, не знаю.
Типобезопасные пути свойств дают унифицированную абстракцию, при этом уважая конвенции каждого языка.
Путь миграции
Существующие API на основе строк остаются неизменными и полностью поддерживаются. Они являются и будут оставаться активным аспектом дизайна новой функциональности. Типобезопасные пути свойств имеют «золотую середину» применения и, как и любой другой подход, — свои ограничения. Для любых сценариев, где имена свойств должны быть динамическими в рантайме, совершенно нормально продолжать использовать строки со всеми упомянутыми ограничениями (например, когда имя свойства приходит из веб-запроса).
Типобезопасные пути свойств — строго аддитивная возможность: их можно внедрять выборочно там, где их преимущества наиболее заметны. Например, рассмотрите следующие примеры рядом с их улучшенными типобезопасными альтернативами:
// Существующий код where("firstName").is("…") where("address.country").is("…") Sort.by("firstName", "lastName") // Типобезопасные варианты where(Person::getFirstName).is("…") where(PropertyPath.of(Person::getAddress).then(Address::getCountry)).is("…") Sort.by(Person::getFirstName, Person::getLastName)
Переход может быть постепенным — с фокусом на тех участках, где рефакторинг-безопасность или ясность особенно важны. С точки зрения производительности ссылки на методы интроспектируются один раз и кэшируются для последующего использования, что в итоге даёт приятную базовую производительность.
Взгляд вперёд
Мы считаем, что разработчики должны тратить время на создание приложений, а не на отладку опечаток в именах свойств. Инструменты должны помогать, а не мешать. Поддержка типобезопасных путей свойств — это больше, чем набор новых интерфейсов: это согласование с тем, как современные разработчики на Java и Kotlin ожидают работать. Языки дают мощные средства типобезопасности и рефакторинга, и наши фреймворки должны использовать эти средства, а не обходить их.
Типобезопасные пути свойств устраняют целый класс ошибок, повышают уверенность при рефакторинге и делают намерение в коде более явным. Они позволяют разработчикам больше полагаться на компилятор и меньше — на конвенции и дисциплину. В дальнейшем возможны и другие улучшения, позволяющие явно проявлять типобезопасные отношения внутри запроса, но это будет частью продолжающейся исследовательской работы.
Мы поставили типобезопасные пути свойств в Spring Data 2026.0.0-M1, чтобы вы могли изучить новые API и дать обратную связь. Нам интересно увидеть, как вы будете использовать их в своих проектах, и как они будут развиваться благодаря вашим отзывам.

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