web-dev-qa-db-ja.com

LocalDateTime RequestParamをSpringで使用する方法は? 「文字列をLocalDateTimeに変換できませんでした」というメッセージが表示される

Spring Bootを使用し、Mavenにjackson-datatype-jsr310を含めました。

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.7.3</version>
</dependency>

Java 8 Date/TimeタイプでRequestParamを使用しようとすると、

@GetMapping("/test")
public Page<User> get(
    @RequestParam(value = "start", required = false)
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start) {
//...
}

次のURLでテストします。

/test?start=2016-10-8T00:00

次のエラーが表示されます。

{
  "timestamp": 1477528408379,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.method.annotation.MethodArgumentTypeMismatchException",
  "message": "Failed to convert value of type [Java.lang.String] to required type [Java.time.LocalDateTime]; nested exception is org.springframework.core.convert.ConversionFailedException: Failed to convert from type [Java.lang.String] to type [@org.springframework.web.bind.annotation.RequestParam @org.springframework.format.annotation.DateTimeFormat Java.time.LocalDateTime] for value '2016-10-8T00:00'; nested exception is Java.lang.IllegalArgumentException: Parse attempt failed for value [2016-10-8T00:00]",
  "path": "/test"
}
33
Kery Hu

TL; DR-@RequestParamだけで文字列としてキャプチャするか、Springでパラメータの@DateTimeFormatを介して文字列をJava日付/時間クラスにさらに解析することができますまあ。

@RequestParamは、=記号の後に指定する日付を取得するのに十分ですが、Stringとしてメソッドに入力されます。それがキャスト例外をスローしている理由です。

これを実現するにはいくつかの方法があります。

  1. 自分で日付を解析し、値を文字列として取得します。
@GetMapping("/test")
public Page<User> get(@RequestParam(value="start", required = false) String start){

    //Create a DateTimeFormatter with your required format:
    DateTimeFormatter dateTimeFormat = 
            new DateTimeFormatter(DateTimeFormatter.BASIC_ISO_DATE);

    //Next parse the date from the @RequestParam, specifying the TO type as 
a TemporalQuery:
   LocalDateTime date = dateTimeFormat.parse(start, LocalDateTime::from);

    //Do the rest of your code...
}
  1. 日付形式を自動的に解析して予期するSpringの機能を活用します。
@GetMapping("/test")
public void processDateTime(@RequestParam("start") 
                            @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) 
                            LocalDateTime date) {
        // The rest of your code (Spring already parsed the date).
}
19
Bwvolleyball

あなたはすべて正しかった:)。 ここ は、あなたが何をしているかを正確に示す例です。 JustRequestParamに@DateTimeFormatの注釈を付けます。コントローラで特別なGenericConversionServiceまたは手動変換を行う必要はありません。 これ ブログ投稿でそれについて書いています。

@RestController
@RequestMapping("/api/datetime/")
final class DateTimeController {

    @RequestMapping(value = "datetime", method = RequestMethod.POST)
    public void processDateTime(@RequestParam("datetime") 
                                @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateAndTime) {
        //Do stuff
    }
}

フォーマットに問題があると思います。私の設定では、すべてがうまくいきます。

49
d0x

コメントに書いたように、このメソッドを署名メソッドで使用することもできます:@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime start

13
Anna

私は回避策を見つけました こちら

Spring/Spring Bootは、BODYパラメーターで日付/日付時刻形式のみをサポートします。

この構成クラスは、QUERY STRINGの日付/日付時刻のサポートを追加します。

@Configuration
public class DateTimeFormatConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setUseIsoFormat(true);
        registrar.registerFormatters(registry);
    }
}

複数のリクエストパラメータを何らかのクラスにバインドした場合でも動作します(この場合、@DateTimeFormatアノテーションは無力です):

public class ReportRequest {
    private LocalDate from;
    private LocalDate to;

    public LocalDate getFrom() {
        return from;
    }

    public void setFrom(LocalDate from) {
        this.from = from;
    }

    public LocalDate getTo() {
        return to;
    }

    public void setTo(LocalDate to) {
        this.to = to;
    }
}

// ...

@GetMapping("/api/report")
public void getReport(ReportRequest request) {
// ...
11
Lu55

私は同じ問題に遭遇し、私の解決策を見つけました here (注釈を使用せずに)

...少なくとも文字列をコンテキストの[LocalDateTime] Converterに適切に登録する必要があります。これにより、入力として文字列を指定し、[LocalDateTime]を期待するたびにSpringが自動的にこれを行うことができます。 (多数のコンバーターは既にSpringによって実装されており、core.convert.supportパッケージに含まれていますが、[LocalDateTime]変換を必要とするものはありません)

したがって、あなたの場合、これを行うでしょう:

public class StringToLocalDateTimeConverter implements Converter<String, LocalDateTime> {
    public LocalDateTime convert(String source) {
        DateTimeFormatter formatter = DateTimeFormatter.BASIC_ISO_DATE;
        return LocalDateTime.parse(source, formatter);
    }
}

次に、Beanを登録します。

<bean class="com.mycompany.mypackage.StringToLocalDateTimeConverter"/>

注釈付き

conversionServiceに追加します。

@Component
public class SomeAmazingConversionService extends GenericConversionService {

    public SomeAmazingConversionService() {
        addConverter(new StringToLocalDateTimeConverter());
    }

}

最後に、ConversionServiceで@Autowireを実行します。

@Autowired
private SomeAmazingConversionService someAmazingConversionService;

この site で、スプリング(および書式設定)を使用した変換について詳しく読むことができます。広告には膨大な数があることをあらかじめご了承ください。しかし、私は間違いなくこのサイトが有用なサイトであり、トピックの入門書であることがわかりました。

3
Naruto Sempai

以下は、Spring Boot 2.1.6でうまく機能します。

コントローラー

@Slf4j
@RestController
public class RequestController {

    @GetMapping
    public String test(RequestParameter param) {
        log.info("Called services with parameter: " + param);
        LocalDateTime dateTime = param.getCreated().plus(10, ChronoUnit.YEARS);
        LocalDate date = param.getCreatedDate().plus(10, ChronoUnit.YEARS);

        String result = "DATE_TIME: " + dateTime + "<br /> DATE: " + date;
        return result;
    }

    @PostMapping
    public LocalDate post(@RequestBody PostBody body) {
        log.info("Posted body: " + body);
        return body.getDate().plus(10, ChronoUnit.YEARS);
    }
}

Dtoクラス:

@Value
public class RequestParameter {
    @DateTimeFormat(iso = DATE_TIME)
    LocalDateTime created;

    @DateTimeFormat(iso = DATE)
    LocalDate createdDate;
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PostBody {
    LocalDate date;
}

テストクラス:

@RunWith(SpringRunner.class)
@WebMvcTest(RequestController.class)
public class RequestControllerTest {

    @Autowired MockMvc mvc;
    @Autowired ObjectMapper mapper;

    @Test
    public void testWsCall() throws Exception {
        String pDate        = "2019-05-01";
        String pDateTime    = pDate + "T23:10:01";
        String eDateTime = "2029-05-01T23:10:01"; 

        MvcResult result = mvc.perform(MockMvcRequestBuilders.get("")
            .param("created", pDateTime)
            .param("createdDate", pDate))
          .andExpect(status().isOk())
          .andReturn();

        String payload = result.getResponse().getContentAsString();
        assertThat(payload).contains(eDateTime);
    }

    @Test
    public void testMapper() throws Exception {
        String pDate        = "2019-05-01";
        String eDate        = "2029-05-01";
        String pDateTime    = pDate + "T23:10:01";
        String eDateTime    = eDate + "T23:10:01"; 

        MvcResult result = mvc.perform(MockMvcRequestBuilders.get("")
            .param("created", pDateTime)
            .param("createdDate", pDate)
        )
        .andExpect(status().isOk())
        .andReturn();

        String payload = result.getResponse().getContentAsString();
        assertThat(payload).contains(eDate).contains(eDateTime);
    }


    @Test
    public void testPost() throws Exception {
        LocalDate testDate = LocalDate.of(2015, Month.JANUARY, 1);

        PostBody body = PostBody.builder().date(testDate).build();
        String request = mapper.writeValueAsString(body);

        MvcResult result = mvc.perform(MockMvcRequestBuilders.post("")
            .content(request).contentType(APPLICATION_JSON_VALUE)
        )
        .andExpect(status().isOk())
        .andReturn();

        ObjectReader reader = mapper.reader().forType(LocalDate.class);
        LocalDate payload = reader.readValue(result.getResponse().getContentAsString());
        assertThat(payload).isEqualTo(testDate.plus(10, ChronoUnit.YEARS));
    }

}
1
Michael Hegner

上記の答えは私にはうまくいきませんでしたが、私はここでやったものに失敗しました: https://blog.codecentric.de/en/2017/08/parsing-of-localdate-query-parameters- in-spring-boot / 優勝したスニペットはControllerAdviceアノテーションで、これはすべてのコントローラーにこの修正を適用する利点があります。

@ControllerAdvice
public class LocalDateTimeControllerAdvice
{

    @InitBinder
    public void initBinder( WebDataBinder binder )
    {
        binder.registerCustomEditor( LocalDateTime.class, new PropertyEditorSupport()
        {
            @Override
            public void setAsText( String text ) throws IllegalArgumentException
            {
                LocalDateTime.parse( text, DateTimeFormatter.ISO_DATE_TIME );
            }
        } );
    }
}
1
user2659207