Spring и JDK 8: Вы все еще используете @Param и name/value в Spring MVC аннотациях? Тогда статья для Вас

  • Tutorial


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


Разрабатывая учебный проект по 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.


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


  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


UPDATE 2: интересно, что для для @RequestParam и @PathVariableвторая работает вторая стратегия LocalVariableTableParameterNameDiscoverer на основе информации, полученной ASM из байткода. В том числе и для обычного Spring (без Boot) и без параметра компиляции.


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

  • +16
  • 7.8k
  • 7
Share post

Similar posts

AdBlock has stolen the banner, but banners are not teeth — they will be back

More
Ads

Comments 7

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

    без обид, но похоже вы были последним человеком кто так делал :)
    на сколько помню ещё со времён JDK7 и спринга версии 4 можно было не дублировать названия параметров.
      0

      Да не один, судя по примерам в google. Вот пример с @PathVariable всем известного проекта: https://github.com/spring-projects/spring-petclinic/blob/master/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java
      Так может поделитесь, как это в JDK7 работало?

        +1
        была какая-то библиотека, которая сохраняла названия параметров метода при компиляции. с JDK8 это стал делать javac
        0
        Вы удивитесь, но @RequestParam/@PathVariable все еще нужна чтобы отметить параметр как mandatory, что в свою очередь сгенерит ошибку, если вы не передаете параметр в запросе.

        {
        	"timestamp": "2019-02-15T13:42:53.511+0000",
        	"status": 400,
        	"error": "Bad Request",
        	"message": "Required long parameter 'id' is not present",
        	"path": "/"
        }
        
          +1

          Не удивлен. Но спасибо за замечание, написал UPDATE: аннотации @PathVariable и @RequestParam все еще часто нужны, чтобы приложение работало корректно. Но их атрибуты value/name уже не обязательны: соответствие ищется по именам переменных.

        0

        UPDATE 2: интересно, что для для @RequestParam и @PathVariableвторая работает вторая стратегия LocalVariableTableParameterNameDiscoverer на основе информации, полученной ASM из байткода. В том числе и для обычного Spring (без Boot) и без параметра компиляции.

          0

          Спасибо за статью. В свое время наткнулся на аналогичную проблему — в моем случае интерес вызвал тест, который успешно проходил в gradle test, но не через idea. Любопытство взяло верх и в итоге я все же нашел в недрах spring-boot плагина нечто аналогичное parent pom, что Вы дали в своем примере.
          В итоге стало ясно что нужно либо добавить значение атрибута value/name для аннотаций RequestParam/PathVariable, либо добавить настройку компиляции (-parameters) в idea.
          Что касается совета не использовать значение атрибута аннотаций, полагаясь на название локальной переменной — я все же придерживаюсь более традиционного решения (использовать). Во-первых, это работает независимо от настроек компиляции проекта (idea при импорте gradle spring-boot проекта не применяет параметр parameters=true к javac). Во-вторых, локальная переменная (аргумент метода) интуитивно рассматривается как что-то, что можно без последствий переименовать, как private-метод, например. А если по какой-то причине это делать нельзя, желательно либо оставлять пометки в коде вроде комментария, но здесь проще дать значение атрибуту. Либо подстраховываться интеграционным тестом, который этот самый параметр передает (ну тест это само собой must have). В общем, это IMHO конечно.

          Only users with full accounts can post comments. Log in, please.