💥 Волшебные методы в Spring Data JPA: как создаются под капотом.В продолжение прошлого поста, рассмотрим как этот механизм работает под капотом. Spring Data JPA позволяет создавать методы в репозиториях, которые генерируют запросы на основе имени метода. Под капотом этот механизм строится на строгой интерпретации имени метода, динамическом создании запросов и использовании JPA Criteria API или JPQL.
Давайте разберём глубже, как это работает и что происходит, в момент вызова волшебного метода:
1️⃣ Парсинг имени метода: PartTreeКласс PartTree (пакет org.springframework.data.repository.query.parser) играет ключевую роль в анализе имени метода. Когда репозиторий загружается в контексте Spring, каждый метод проверяется на соответствие предопределённым шаблонам. Основной принцип парсинга — разбиение имени метода на логические части (поля сущности, операторы и ключевые слова).
Пример метода:
findByFirstNameAndLastName(String firstName, String lastName).
PartTree разбивает это имя на части:
findBy (операция),
FirstName (поле),
And (оператор),
LastName (второе поле).
В результате класс PartTree создаёт синтаксическое дерево условий, которые затем будут преобразованы в SQL.
2️⃣ Генерация SQL-запросов: QueryLookupStrategyПосле того как имя метода распознано, Spring Data JPA использует стратегию поиска запроса через QueryLookupStrategy (пакет org.springframework.data.repository.query). Этот класс отвечает за выбор подходящей стратегии для преобразования метода в SQL-запрос, что делает работу с запросами гибкой и удобной. Стратегии могут быть следующими:
- CREATE: Автоматически создаются запросы на основе имени метода (например, findBy, countBy и т.д.).
- USE_DECLARED_QUERY: Использует явно объявленные запросы (например, через аннотации @Query).
- CREATE_IF_NOT_FOUND: Попытка использовать явный запрос, а если он не найден — создать запрос динамически.
Если метод соответствует заранее определённым правилам (например, начинается с findBy), Spring Data JPA создаст запрос автоматически. Методы динамического создания запросов используют API JpaQueryCreator, который формирует запросы на основе дерева условий, построенного с помощью PartTree. Это дерево представляет собой структуру, которая разбивает имя метода на логические части (поля, операторы и условия) и преобразует их в SQL-запрос.
3️⃣ Конструирование запроса через JPA Criteria API или JPQL В зависимости от сложности запроса и доступных данных, Spring решает, использовать ли для запроса JPA Criteria API или JPQL. Более простые запросы генерируются с помощью JPQL, для более сложных выбирается Criteria API. Каждый метод в репозитории после парсинга передаётся в CriteriaBuilder, который строит запрос на основе условий.
Пример генерации запроса с использованием Criteria API:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<User> query = cb.createQuery(User.class);
Root<User> user = query.from(User.class);
Predicate firstNamePredicate = cb.equal(user.get("firstName"), firstName);
Predicate lastNamePredicate = cb.equal(user.get("lastName"), lastName);
query.where(cb.and(firstNamePredicate, lastNamePredicate));
List<User> result = entityManager.createQuery(query).getResultList();
Этот код создаёт SQL-запрос, эквивалентный:
SELECT * FROM users WHERE first_name = ? AND last_name = ?
▪️ Операторы и ключевые словаSpring Data JPA поддерживает большое количество операторов, таких как And, Or, GreaterThan, LessThan, Between, Like и другие. Каждый оператор обрабатывается в классе QueryCreator, где он преобразуется в соответствующее условие JPA Criteria API или JPQL.
Пример обработки оператора Between:
if (part.getType() == Part.Type.BETWEEN) {
predicates.add(cb.between(root.get(part.getProperty()), minValue, maxValue));
}
Это условие будет интерпретировано как:
SELECT * FROM users WHERE age BETWEEN ? AND ?
▪️ Работа с коллекциямиДля работы с коллекциями Spring Data JPA использует оператор IN. Например, метод findByRoleIn(List<String> roles) будет преобразован в запрос с IN оператором.
Пример кода: