HATEOAS PresentationModelAssembler, POST с телом JSON возвращает 500: IllegalArgumentException: недостаточно значений переменных, доступных для расширения 'id'

Я следил за учебником Spring по созданию REST с использованием HATEOAS по адресу: https://spring.io/guides/tutorials/rest/ и смешал его с JPA и MySQL DB (Maven). Когда я запускаю приложение, я могу видеть первые 2 таблицы в рабочей среде MySQL в порядке (хотя третья появляется из ниоткуда?). Если я выполняю GET / player, все работает нормально. Когда я выполняю запрос POST (http: // localhost: 8080 / Players) в Postman с телом JSON: {"playerName": "Pedro"}, я получаю статус 500, а Spring выдает ошибку: "Недостаточно значений переменных, доступных для разверните 'id'] с основной причиной ... «Я хотел бы добиться полных операций CRUD. Здесь возникает много сомнений, учитывая, что playerId - это автоинкремент, а параметр registrariondate - это TIMESTAMP. Это второстепенно, поскольку я предполагаю, что проблема возникает из-за использования в моем приложении PresentationModelAssembler, но я не совсем уверен, как обрабатывать ответы и петиции.

Вот структура проекта: HATEOAS PresentationModelAssembler, POST с телом JSON возвращает 500: IllegalArgumentException: недостаточно значений переменных, доступных для расширения 'id'

Журнал ошибки:

Hibernate: 
    insert 
    into
        player
        (player_name, registration_date) 
    values
        (?, ?)
2021-04-02 11:20:03.781 ERROR 2312 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: java.lang.IllegalArgumentException: Not enough variable values available to expand 'id'] with root cause

java.lang.IllegalArgumentException: Not enough variable values available to expand 'id'
    at org.springframework.web.util.UriComponents$VarArgsTemplateVariables.getValue(UriComponents.java:370) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.util.UriComponents.expandUriComponent(UriComponents.java:263) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.util.HierarchicalUriComponents$FullPathComponent.expand(HierarchicalUriComponents.java:917) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.util.HierarchicalUriComponents.expandInternal(HierarchicalUriComponents.java:434) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.util.HierarchicalUriComponents.expandInternal(HierarchicalUriComponents.java:52) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.util.UriComponents.expand(UriComponents.java:172) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.util.DefaultUriBuilderFactory$DefaultUriBuilder.build(DefaultUriBuilderFactory.java:403) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.hateoas.UriTemplate.expand(UriTemplate.java:272) ~[spring-hateoas-1.2.5.jar:1.2.5]
    at org.springframework.hateoas.Link.expand(Link.java:361) ~[spring-hateoas-1.2.5.jar:1.2.5]
    at org.springframework.hateoas.Link.toUri(Link.java:434) ~[spring-hateoas-1.2.5.jar:1.2.5]
    at RESTApiJWTAuthMySQL.controllers.PlayerController.createNewPlayer(PlayerController.java:66) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.3.5.jar:5.3.5]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894) ~[spring-webmvc-5.3.5.jar:5.3.5]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.5.jar:5.3.5]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.5.jar:5.3.5]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1060) ~[spring-webmvc-5.3.5.jar:5.3.5]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:962) ~[spring-webmvc-5.3.5.jar:5.3.5]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.5.jar:5.3.5]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.5.jar:5.3.5]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:652) ~[tomcat-embed-core-9.0.44.jar:4.0.FR]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.5.jar:5.3.5]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.44.jar:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.44.jar:9.0.44]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.5.jar:5.3.5]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.5.jar:5.3.5]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.5.jar:5.3.5]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.5.jar:5.3.5]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:143) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:374) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1707) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) ~[na:na]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
    at java.base/java.lang.Thread.run(Thread.java:832) ~[na:na]

Класс контроллера

@RestController
//@RequestMapping("/")
public class PlayerController {
    
    @Autowired
    private final PlayerRepository repository;
    @Autowired
    private final PlayerModelAssembler assembler;

    PlayerController(PlayerRepository repository, PlayerModelAssembler assembler) {
        this.repository = repository;
        this.assembler = assembler;
    }
        
    @GetMapping("/players/")
    public CollectionModel<EntityModel<Player>> all() {

      List<EntityModel<Player>> players = repository.findAll().stream().map
              (assembler::toModel).collect(Collectors.toList());

      return CollectionModel.of(players, linkTo(methodOn(PlayerController.class).all()).withSelfRel());
    }
    
    @GetMapping("/players/{id}") 
    public EntityModel<Player> one(@PathVariable Long playerId) {

      Player player = repository.findById(playerId).orElseThrow(() -> new PlayerNotFoundException(playerId));

      return assembler.toModel(player);
    }
    
    @PostMapping(path = "/players", consumes = "application/json") 
    public ResponseEntity<?> createNewPlayer(@RequestBody Player newPlayer) {

      EntityModel<Player> entityModel = assembler.toModel(repository.save(newPlayer));

      return ResponseEntity.created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri()).body(entityModel);
      
    }        
            
    @PutMapping("/players/{id}")
    public ResponseEntity<?> replacePlayer(@RequestBody Player newPlayer, @PathVariable Long playerId) {

        Player updatedPlayer = repository.findById(playerId) //
          .map(player -> {
            player.setPlayerName(newPlayer.getPlayerName());
            return repository.save(player);
          }) //
          .orElseGet(() -> {
              newPlayer.setPlayerId(playerId);
            return repository.save(newPlayer);
          });

      EntityModel<Player> entityModel = assembler.toModel(updatedPlayer);

      return ResponseEntity //
          .created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri()) //
          .body(entityModel);
    }
    
    @DeleteMapping("/players/{id}")
    public ResponseEntity<?> deleteEmployee(@PathVariable Long playerId) {

          repository.deleteById(playerId);

          return ResponseEntity.noContent().build();
    }
}

PlayerModelAssembler - класс

@Component
public class PlayerModelAssembler implements RepresentationModelAssembler<Player, EntityModel<Player>> {

  @Override
  public EntityModel<Player> toModel(Player player) {

      return EntityModel.of(player, //
            linkTo(methodOn(PlayerController.class).one(player.getPlayerId())).withSelfRel(),
            linkTo(methodOn(PlayerController.class).all()).withRel("players"));
  }
}

Класс сущности игрока

@Entity
@Table(name = "Player")
public class Player {

    @Id
    @GeneratedValue (strategy = GenerationType.IDENTITY)    
    private Long playerId;
    
    @Column (name = "player_name")
    private String playerName;
    
    @CreationTimestamp
    @Temporal(TemporalType.TIMESTAMP)   
    @Column (name = "registration_date", updatable = false)
    private Date registrationDate;
    
    @OneToMany (mappedBy = "player", cascade = CascadeType.ALL, orphanRemoval=true)
    private List<DiceRoll> diceRolls = new ArrayList<>();
    
    public Player() {
        
    }  
    
    public Player(Long playerId, String playerName, Date registrationDate) {
        this.playerId=playerId;
        this.playerName = playerName;
        this.registrationDate = registrationDate;
    }
    
    //getter&setters//  
    
    @Override
      public boolean equals(Object o) {

        if (this == o)
          return true;
        if (!(o instanceof Player))
          return false;
        Player player = (Player) o;
        return Objects.equals(this.playerId, player.playerId) && Objects.equals(this.playerName, player.playerName)
            && Objects.equals(this.registrationDate, player.registrationDate);
      }

      @Override
      public int hashCode() {
        return Objects.hash(this.playerId, this.playerName, this.registrationDate);
      }

      @Override
      public String toString() {
        return "Player{" + "id = " + this.playerId + ", name='" + this.playerName + '\'' + ", date of registration='" + this.registrationDate + '\'' + '}';
      }
}

POM файл

<dependencies>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
            <version>5.4.10.Final</version><!--$NO-MVN-MAN-VER$-->
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-hateoas</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.hateoas</groupId>
            <artifactId>spring-hateoas</artifactId>
        </dependency>
               
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

Schema.sql

USE `dicegame`;

DROP TABLE IF EXISTS `Player`;

CREATE TABLE IF NOT EXISTS `Player` (
  `player_id` BIGINT PRIMARY KEY  AUTO_INCREMENT,
  `player_name` VARCHAR(45) NOT NULL,
  `registration_date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP);

DROP TABLE IF EXISTS `DiceRoll`;

CREATE TABLE IF NOT EXISTS `DiceRoll` (
  `diceRoll_id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `d1` INT(55) NOT NULL,
  `d2` INT(55) NOT NULL,
  `result` VARCHAR(45) NOT NULL,
  `diceRoll_registration` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  `player_id` BIGINT NOT NULL);
  
ALTER TABLE DiceRoll
ADD constraint FK_PLAYER_ID  FOREIGN KEY (player_id) 
      REFERENCES Player (player_id);

Любая помощь или направление будут очень благодарны. Я вижу в учебнике, что петитон для завивки используется как: $ curl -v -X POST localhost: 8080 / Players -H 'Content-Type: application / json' -d '{"playerName": "Pedro"}', и он должен работать. Большое спасибо.

Настоятельно рекомендуется написать минимальное приложение Spring Boot, которое воспроизведет вашу проблему и затем загрузит его на Github, а не публиковать несколько фрагментов кода. Вот один пример. Он использует базу данных H2 в памяти и прецедент для демонстрации запроса.

yejianfengblue 03.04.2021 10:15

Спасибо за ответ: здесь вы можете найти весь проект, как сейчас. У меня нет dataloaderclass, так как мои исходные данные берутся из schema.sql и данных, вставленных в эти 2 таблицы.

Joan Coll 04.04.2021 12:55

В дополнение к моему ответу ниже см. Мой пул реквест о том, как использовать базу данных H2 в памяти, которая упрощает запуск на компьютере помощника. Не у всех помощников установлен MySql на компьютере.

yejianfengblue 04.04.2021 14:51
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для
Освоение архитектуры микросервисов с Laravel: Лучшие практики, преимущества и советы для
В последние годы архитектура микросервисов приобрела популярность как способ построения масштабируемых и гибких приложений. Laravel , популярный PHP...
Как построить CRUD-приложение в Laravel
Как построить CRUD-приложение в Laravel
Laravel - это популярный PHP-фреймворк, который позволяет быстро и легко создавать веб-приложения. Одной из наиболее распространенных задач в...
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
Освоение PHP и управление базами данных: Создание собственной СУБД - часть II
В предыдущем посте мы создали функциональность вставки и чтения для нашей динамической СУБД. В этом посте мы собираемся реализовать функции обновления...
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
Роли и разрешения пользователей без пакета Laravel 9
Роли и разрешения пользователей без пакета Laravel 9
Этот пост изначально был опубликован на techsolutionstuff.com .
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
Как установить LAMP Stack - Security 5/5 на виртуальную машину Azure Linux VM
В предыдущей статье мы завершили установку базы данных, для тех, кто не знает.
0
3
37
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Метод PlayerModelAssembler.toModel(Player player) использует PlayerController.one(@PathVariable Long playerId) для создания собственной ссылки.

Если атрибут name аннотации @PathVariable не указан, Spring ожидает, что имя параметра будет таким же, как имя, окруженное {} в @GetMapping.

В исходном коде имя параметра playerId отличается от id. Чтобы исправить это,

@GetMapping("/players/{id}") 
public EntityModel<Player> one(@PathVariable("id") Long playerId) {
}

или же

@GetMapping("/players/{id}") 
public EntityModel<Player> one(@PathVariable Long id) {
}

или же

@GetMapping("/players/{playerId}") 
public EntityModel<Player> one(@PathVariable Long playerId) {
}

Пошел на последний: @GetMapping ("/ Players / {playerId}") public EntityModel <Player> one (@PathVariable Long playerId) {} ​​Большое спасибо, у меня были некоторые ошибки в этом, еще раз спасибо

Joan Coll 04.04.2021 14:54

Другие вопросы по теме