Здравствуй, Хаброчитатель!
Разрабатывая учебный проект по Spring Boot 2 решил поэкспериментировать с @Param
в запросах Spring Data JPA, а точнее c их отсутствием:
@Transactional(readOnly = true)
public interface UserRepository extends JpaRepository<User, Integer> {
@Query("SELECT u FROM User u WHERE LOWER(u.email) = LOWER(:email)")
Optional<User> findByEmailIgnoreCase(@Param("email") String email);
List<User> findByLastNameContainingIgnoreCase(@Param("lastname") String lastName);
}
(про магию, как работает второй метод есть в старой публикации По следам Spring Pet Clinic).
Убрав @Param
можно убедится, что Spring прекрасно работает и без них. Я слышал про параметр в компиляции, который позволяет не дублировать названия в аннотациях, но я ничего не специального не делал, поэтому решил покопать поглубже подебажить.
Если Вы еще пользуетесь аннотациями из заголовка статьи, Spring Boot и JDK 8, прошу под кат:
UPDATE: аннотации @PathVariable
и @RequestParam
все еще часто нужны, чтобы приложение работало корректно. Но их атрибуты value/name
уже не обязательны: соответствие ищется по именам переменных.
Первое, что я попробовал- поменять имя в параметре (
mail
вместоemail
):
@Query("SELECT u FROM User u WHERE LOWER(u.email) = LOWER(:email)") Optional<User> findByEmailIgnoreCase(String mail);
Получаю эксепшен и место для брекпойнта:
Caused by: java.lang.IllegalStateException: Using named parameters for method public abstract java.util.Optional ru.javaops.bootjava.restaurantvoting.repository.UserRepository.findByEmailIgnoreCase(java.lang.String) but parameter 'Optional[mail]' not found in annotated query 'SELECT u FROM User u WHERE LOWER(u.email) = LOWER(:email)'! at org.springframework.data.jpa.repository.query.JpaQueryMethod.assertParameterNamesInAnnotatedQuery(JpaQueryMethod.java:125) ~[spring-data-jpa-2.1.3.RELEASE.jar:2.1.3.RELEASE]
Далее находим место, где определяется имя параметра метода:
Видно, что используются 2 стратегии: StandardReflectionParameterNameDiscoverer
и LocalVariableTableParameterNameDiscoverer
. Первая использует JDK8 JEP 118: Access to Parameter Names at Runtime. Согласно SPR-9643, если не получается определить имена параметров по первой стратегии, Spring пробует использовать "ASM-based debug symbol analysis".
- В интернете много информации по Java 8 Parameter Names, необходима компиляция с флагом
-parameters
. Иду в настройки Spring Boot проекта IDEA:
Да, действительно включена… А что, если я соберу и запущу проект через Maven?
Результат тот же!
Включаю в настройках Maven вывод debug, компилирую проект и вижу:
[DEBUG] Goal: org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile) ... <parameters default-value="false">true</parameters>
Похоже, что
maven-compiler-plugin
уже настроен вspring-boot-starter-parent
, откуда по умолчанию наследуются проектыspring-boot
при генерации через SPRING INITIALIZR. Переходим туда и (только для Spring Boot 2) точно, плагин там настроен:
<plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <parameters>true</parameters> </configuration> </plugin>
Наконец мы можем у себя в проекте переопределить конфигурацию
maven-compiler-plugin
, где выставим этот флаг вfalse
. Проверяем — проект запустился. И при попытке дернуть метод получаем:
Unable to detect parameter names for query method ru.javaops.bootjava.restaurantvoting.repository.UserRepository.findByEmailIgnoreCase! Use @Param or compile with -parameters on JDK 8.
Это означает, что:
- наши рассуждения верны
- на основе второй стратегии ASM информацию достать не удалось (хотя я запускался через Debug)
ИТОГ: флаг -parameters
в Spring Boot 2 включен по умолчанию, поэтому, если вы наследуетесь от spring-boot-starter-parent
, то имена параметров определяются в рантайме и @Param
, @RequestParam
, @PathVariable
больше не требуются. Меньше кода, меньше ошибок.
Для Spring Boot 1.x флаг компиляции можно включить принудительно, см. выше.
P.S.: при исследовании использовал JDK 8, JDK 11 и Spring Boot 2.1.1
UPDATE 2: интересно, что для для @RequestParam
и @PathVariableвторая
работает вторая стратегия LocalVariableTableParameterNameDiscoverer
на основе информации, полученной ASM из байткода. В том числе и для обычного Spring (без Boot) и без параметра компиляции.
Спасибо за внимание!