きり丸の技術日記

技術検証したり、資格等をここに残していきます。

DBからJavaのResultSetにLocalDateTimeやBigInteger等の基本型以外の型を直接設定する

TwitterやJavaのオープンチャットででたまーにResultSetから直接LocalDateTimeBigIntegerを取得できずに、一旦Stringjava.sql.Dateで取得した後に変換している、ということを見かけることがありました。直接LocalDateTimeやBigIntegerで取得することができます。

今回の記事では、DBからJavaのResultSetを駆使し、JdbcTemplate#queryにてLocalDateTimeBigIntegerを取得する方法を記載します。

環境

  • Java
    • 15
  • org.springframework.boot
    • 2.5.2
  • org.flywaydb:flyway-core
    • 7.7.3
  • com.h2database:h2
    • 2.1.2.200
  • org.springframework.boot:spring-boot-starter-test
    • 2.5.2
  • org.springframework.boot:spring-boot-starter-jdbc
    • 2.5.2
  • org.mybatis.spring.boot:mybatis-spring-boot-starter
    • 2.2.0
  • org.mybatis.spring.boot:mybatis-spring-boot-starter-test
    • 2.2.0
  • Gradle

ゴール

JavaのJdbcTemplate#queryにてRLocalDateTimeBigIntegerを取得します。

要約

ResultSetgetObjectの第二パラメータに必要なクラスを指定します。

方法

事前準備

テーブルとデータを準備します。

CREATE TABLE BOOK
(
    isbn         VARCHAR(13) PRIMARY KEY,
    money        INT,
    number       INT,
    generate_date TIMESTAMP
);

INSERT INTO BOOK VALUES ('9784798126708', 1000, 10, '2021-07-02 12:34:56')

取得用のDtoを作成する

jdbcTemplateから取得するための型を用意します。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BookDto implements Serializable {
  private String isbn;
  private int money;
  private BigInteger number;
  private LocalDateTime generateDate;
}

Javaで取得する

JdbcTemplate#queryを使用します。ResultSetgetObjectの第二パラメータに必要なクラスを指定します。

今回の場合は、BigInteger.class, LocalDateTime.classを指定することができます。

protected List<BookDto> findBookList() {
  return jdbcTemplate.query("SELECT * FROM BOOK", (rs, i) ->
      BookDto.builder()
          .isbn(rs.getString("isbn"))
          .money(rs.getInt("money"))
          .number(rs.getObject("number", BigInteger.class))
          .generateDate(rs.getObject("generate_date", LocalDateTime.class))
          .build());
}

備考

単純にラップした自作型に関してはマッピングできないようです。ISBNクラスを作成してマッピングしようとしましたが、エラーログが出力されました。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BookDto implements Serializable {
  private Isbn isbn;
  private int money;
  private BigInteger number;
  private LocalDateTime generateDate;
}

@Value
@AllArgsConstructor
public class Isbn {
  private final String isbn;
}

protected List<BookDto> findBookList() {
  return jdbcTemplate.query("SELECT * FROM BOOK", (rs, i) ->
      BookDto.builder()
          .isbn(rs.getObject("isbn", Isbn.class))
          .money(rs.getInt("money"))
          .number(rs.getObject("number", BigInteger.class))
          .generateDate(rs.getObject("generate_date", LocalDateTime.class))
          .build());
}
Caused by: org.h2.jdbc.JdbcSQLFeatureNotSupportedException: 機能はサポートされていません: "kirimaru.biz.domain.book.Isbn"
Feature not supported: "kirimaru.biz.domain.book.Isbn" [50100-200]

素直に構造に合わせたRowMapperを作成しましょう。

        .isbn(new Isbn(rs.getString("isbn"))

ソースコード

単体テストも行えるようにしています。セットアップのためのFlyway等は今回の記事では解説しません。

終わりに

地味ながら、低くない頻度で見かけるので記事にしました。

やってることは簡単なので、困っている人に届くと嬉しいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

f:id:nainaistar:20210702212124p:plain

Javaで比較するときはObjects.equalsを使うのがオススメ

小ネタ。正直、記述量が増えるので好き嫌いで言うと嫌いですが、NullPointerException(以後、NPE)が発生しなくなるので安全に処理したい時はオススメです。

環境

  • Java
    • 15

出現時期

  • java.util.Objects
    • Java1.7

Java7の時代からあります。分かりづらいですが、Objectではないです。Objectsです。

使い方

Objects.equals(Object a, Object b);

どの辺が便利か

安全に比較することができます。特にプリミティブ型のラッパークラス(Integer, Boolean)等はnullになりうります。もし、nullと比較してしまうと、NPEが発生してしまいます。

@Test
void test_01() {
  boolean param1 = true;
  Boolean param2 = null;
  try {
    var actual = param1 == param2;
    Assert.fail();
  } catch (NullPointerException e) {
  } catch (Exception e) {
    Assert.fail();
  }
}

Objects.equalsを使用すると、NPEが発生せずにfalseが返却されます。

@Test
void test_02() {
  boolean param1 = true;
  Boolean param2 = null;
  assertThat(
      Objects.equals(param1, param2)
  ).isEqualTo(false);
}

なお、当然ですがObjects.equalsに渡す前にNPEが発生する場合は、NPEになります。

static class Nested {
  public Boolean bool;
  public Nested(Boolean bool){
    this.bool = bool;
  }
}

@Test
void test_05() {
  boolean param1 = true;
  Nested param2 = null;
  try {
    Objects.equals(param1, param2.bool);
    Assert.fail();
  } catch (NullPointerException e) {
  } catch (Exception e) {
    Assert.fail();
  }
}

ソースコード

終わりに

Java7が出た時期は2011年7月ですので、もうこちらのクラスを知っている人は多いと思います。

地味ですが、NPEを回避できますのでぜひ使ってみて下さい。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

f:id:nainaistar:20210620141146p:plain

LombokでToString.IncludeとExclude、callSuperで便利にログ出力する

小ネタ。JavaにはLombokという便利なライブラリがあります。その便利なライブラリの中にあるToString、その中でもIncludeExclude, callSuperを駆使すると便利だったので、ぜひ覚えてください。

この記事では、ToStringInclude, Exclude, callSuperの3つを紹介します。

環境

  • Java
    • 15
  • lombok
    • 1.18.20

ユースケース

アプリケーション起動時に、どういう設定ファイルの値を読み込んでいるかをログに出力して確認したい。

※ アプリケーション起動時に処理させたい時にはPostConstructを使用すると良いです。PostConstructの紹介記事

書くこと

  • ToStirngの機能
    • Include
    • Exclude
    • callSuper

書かないこと

  • ToStringのその他の機能

普段私が使っておらずメリットがわからないので、特に紹介しません。ある程度Lombokも歴史があるので、他の方が纏めてくださっている記事はあるはずです。

今回使用するファイル

他システムに連携するための設定を抽象クラスExternalPropertiesにもち、抽象クラスを継承したFacebookPropertiesTwitterPropertiesを検証に使用する。

@Getter
@Setter
@Slf4j
@ToString
public abstract class ExternalProperties {
  private String host;
  private String protocol;
  private String port;
  private String endpoint;
  private String timeout;

  @ToString.Exclude // ログに出さない
  private String basicUser;
  @ToString.Exclude // ログに出さない
  private String basicPassword;

  @ToString.Include(name = "rootUrl") // デフォルトはメソッド名になる
  public URI getUri() {
    return URI.create(protocol + "://" + host + ":" + port + "/" + endpoint);
  }

  @PostConstruct
  public void display() {
    log.info(this.toString());
  }
}

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "external.facebook")
@ToString(callSuper = true)
public class FacebookProperties extends ExternalProperties {
}

@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "external.twitter")
@ToString(callSuper = true)
public class TwitterProperties extends ExternalProperties {
}

ToStringについて

クラスに@ToStringと付与することで、toString()の結果がハッシュ値ではなくインスタンスの所有する変数を出力するようになります。基本的にはToStringを単体で使用することは少なく、@Valueもしくは@Data@ToStringを包括しているので意識せずに使用していると思われます。

付与していないログ:

FacebookProperties(super=kirimaru.config.FacebookProperties$$EnhancerBySpringCGLIB$$f12ea043@8dfe921)

付与しているログ:

FacebookProperties(super=ExternalProperties(host=localhost, protocol=http, port=10080, endpoint=facebook, timeout=3, rootUrl=http://localhost:10080/facebook))

なお、普段の業務では出てきませんが、競技プログラミング等で循環参照しているデータをデバッグしようとしてログに出力すると、StackOverflowErrorが発生します。注意しましょう。

Include

@ToString.Includeを付与することで、メソッドの結果をtoString()で出力できるようになります。例えば、URIを構築する材料のhostやport等を持った設定ファイルがあったときに、hostがxxx, portがxxxという情報ではなく、そこから構築されるURLを表示したい、といったユースケースで有効活用することができます。

@ToString.Include(name = "rootUrl")
public URI getUri() {
  return URI.create(protocol + "://" + host + ":" + port + "/" + endpoint);
}
FacebookProperties(super=ExternalProperties(host=localhost, protocol=http, port=10080, endpoint=facebook, timeout=3, rootUrl=http://localhost:10080/facebook))

ToString.Includeはrank, nameの変数を所持しています。

  • rank
    • ToStringでの表示順を設定します。デフォルトでは0で、最も後ろに表示されます。この値を100等に設定すると、先頭に表示することもできます。
  • name
    • その名の通り、表示する名前を設定します。デフォルトだとメソッド名が表示されます。

昔はこの設定をofで設定できていたようです。今でもできますが、近いうちにDeperecatedになることがJavaDocに記載されており、ToString.Includeと一緒に扱おうとするとエラーとなります。

java: The old-style 'exclude/of' parameter cannot be used together with the new-style @Include / @Exclude annotations.

Exclude

@ToString.Excludeを変数付与することで、toStringで出力しなくなります。例えば、BASIC認証のID/PASS等は出力したくない、というときに使用できます。

@ToString.Exclude // ログに出さない
private String basicUser;
@ToString.Exclude // ログに出さない
private String basicPassword;

ofと同様に、昔はこの設定をexcludeで設定できていたようです。今でもできますが、近いうちにDeperecatedになることがJavaDocに記載されており、ToString.Excludeと一緒に扱おうとするとエラーとなります。

java: The old-style 'exclude/of' parameter cannot be used together with the new-style @Include / @Exclude annotations.

callSuper

デフォルトではfalseになっています。この設定を使用することで、継承元のToStringの結果を含めることができます。

継承しているファイルでtoString()を使用したい場合は、この設定を含めないと出力元のファイルが何かが分からずに苦労することになります。

callSuper=trueを付与している場合:

TwitterProperties(super=ExternalProperties(host=localhost, protocol=http, port=80, endpoint=twitter, timeout=3, rootUrl=http://localhost:80/twitter))
FacebookProperties(super=ExternalProperties(host=localhost, protocol=http, port=10080, endpoint=facebook, timeout=3, rootUrl=http://localhost:10080/facebook))

callSuper=falseを付与している場合:

ExternalProperties(host=localhost, protocol=http, port=80, endpoint=twitter, timeout=3, rootUrl=http://localhost:80/twitter)
ExternalProperties(host=localhost, protocol=http, port=10080, endpoint=facebook, timeout=3, rootUrl=http://localhost:10080/facebook)

ソースコード

終わりに

正直、Lombokが今後使われるかどうかが分かりません。Lombokはバージョンアップのたびに開発環境の不具合が起きやすいですし、その割にはソースコード的には劇的な変化はないですからね。

Java 16からrecordsという便利な仕組みができたので、Lombokを使用しないようにする、という動きは加速するかと思われます。

個人的にはBuilderクラス等を生成できるので、できればLombokはずっとサポートして欲しいのですが…。

今回学んだことが2021年以降も活かせると嬉しいです。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

類似記事

f:id:nainaistar:20210607002313p:plain