Computer

JPAエンティティとJava 8の日時APIでUnsupportedOperationExceptionが発生するときの対処

2020年3月7日

概要

データベースから取得したオブジェクトに対して、Java 8のDate and Time APIを使おうとしたらUnsupportedOperationException が発生して、原因がわかるまで時間がかかったのでメモしておきます。

LocalDateの場合

カラムの定義はこちらで

    @Temporal(TemporalType.DATE)
    @Column(name = "start_date")
    private Date startDate;

これを下のような感じで

LocalDate startDate = 
    data.getStartDate().toInstant().atZone(JST).toLocalDate();

LocalDateに変換しようとすると以下のエラーが発生します

java.lang.UnsupportedOperationException
  at java.sql.Date.toInstant(Date.java:304)

LocalDateTimeへの変換でも発生

LocalDateだけでなく、LocalTimeへの変換でも発生します。

@Temporal(TemporalType.TIME)
@Column(name = "start_time")
private Date startTime;

こんなフィールドも、

LocalDateTime startTime = 
    LocalDateTime.ofInstant(data.getStartTime().toInstant(), JST);

でLocalDateTimeに変換しようとすると以下のエラーが発生します。

java.lang.UnsupportedOperationException
  at java.sql.Time.toInstant(Time.java:291)

ユニットテストでは検出できず

この問題は、サーバー上で実行するまで問題に気づきませんでした。

原因は、ユニットテスト時はjava.util.Date 型のデータでテストしたが、実際のデータベースからデータを取得すると、java.sql.Dateや java.sql.Timeといったjava.utilではない型が返ってくるためでした。

Java 8 Date and Time APIの変換は、java.sql.Datejava.sql.Timeからは変換できず、java.util.Date にキャストしてからでないとUnsupportedOperationExceptionが発生します。

java.sql.Datejava.sql.Timeも、java.util.Dateのサブクラスなので、コードだけ見ても問題には気づきません。

対処策

原因がわかってしまえば対処策は簡単でした。

以下のユーティリティメソッドを使って、java.util.Dateに変換するようにしたらエラーは発生しなくなりました。

    private Date convertToJavaUtilDate(Date date) {
        if (date != null && (date instanceof java.sql.Date || date instanceof java.sql.Time)) {
            return new Date(date.getTime());
        }
        return date;
    }

またテストクラスでも、あえてjava.sql.Datejava.sql.Time型のデータを作ってテストをするようにしました。

原因がわかってしまうとあっけないですが、結構ハマってしまいました。レガシーなシステムではない場合、Java 8のDateTime APIを素直に使うのがよさそうですね。

-Computer

Copyright© dawaan , 2020 All Rights Reserved.