Spring Boot 2 и JDK 8: Вы все еще используете аннотации @Param, @RequestParam и @PathVariable? Тогда статья для Вас

gtk82bshzogszwj3iexi0jqi-oi.png

Здравствуй, Хаброчитатель!

Разрабатывая учебный проект по Spring Boot 2 решил поэкспериментировать с @Param в запросах Spring Data JPA, а точнее c их отсутствием:

@Transactional(readOnly = true)
public interface UserRepository extends JpaRepository {

    @Query("SELECT u FROM User u WHERE LOWER(u.email) = LOWER(:email)")
    Optional findByEmailIgnoreCase(@Param("email") String email);

    Optional findByLastNameContainingIgnoreCase(@Param("lastname") String lastName);
}

(про магию, как работает второй метод есть в старой публикации По следам Spring Pet Clinic).

Убрав @Param можно убедится, что Spring прекрасно работает и без них. Я слышал про параметр в компиляции, который позволяет не дублировать названия в аннотациях, но я ничего не специального не делал, поэтому решил покопать поглубже подебажить.

Если Вы еще пользуетесь аннотациями из заголовка статьи, Spring Boot и JDK 8, прошу под кат:


  • Первое, что я попробовал- поменять имя в параметре (mail вместо email):

    @Query("SELECT u FROM User u WHERE LOWER(u.email) = LOWER(:email)")
    Optional 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]
    

  • Далее находим место, где определяется имя параметра метода:


qhpvfl2s3jwcj-fixscec63cdom.png

Видно, что используются 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:

_mm2igh7_m_2p4fc_jxcsrdwmow.png

Да, действительно включена… А что, если я соберу и запущу проект через Maven?
Результат тот же!


  • Включаю в настройках Maven вывод debug, компилирую проект и вижу:

    [DEBUG] Goal:          org.apache.maven.plugins:maven-compiler-plugin:3.8.0:compile (default-compile)
    ...
    true
    

    Похоже, что maven-compiler-plugin уже настроен в spring-boot-starter-parent, откуда по умолчанию наследуются проекты spring-boot при генерации через SPRING INITIALIZR. Переходим туда и (только для Spring Boot 2!) точно, плагин там настроен:

                
                    maven-compiler-plugin
                    
                        true
                    
                
    

  • Наконец мы можем у себя в проекте переопределить конфигурацию 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.
    

Это означает, что:


  1. наши рассуждения верны
  2. на основе второй стратегии 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
P.S. S.: если у вас есть информация, когда работает вторая стратегия LocalVariableTableParameterNameDiscoverer — поделитесь плз в комментариях.

Спасибо за внимание!

© Habrahabr.ru