だらだらと思いついたこととか書くブログ

エンジニア的なネタとか備忘録とかを書いていく予定

springアプリケーションのORマッパーの比較メモ

Kotlin + spring-bootのアプリケーションでのDBアクセスをどのライブラリを使って行うが良いかという課題があり、

簡単なコードを書いていくつか試してみたのでそれについてのメモ

Java ORマッパーの選定ポイントのスライドを参考にさせていただき、

の3つについて比較を、1:Nの2テーブルのみの簡単なCRUD APIを使って実施してみた

spring-data-jpa

spring-bootのORマッパー=JPAというイメージもあり、ちょっとしたwebアプリを書く際などに今まで利用していたのがこちら。

以前はspring-jdbcでゴリゴリSQLを書いていたことも合ったので「それに比べてDBアクセスが楽になった」という印象がある反面、アノテーションマッピングの仕方、JPQLの使い方など使うにあたって学習が必要なものが多いことや、複雑なクエリを書こうとしてN+1問題などにぶち当たったりと使っていく上での課題も多く、自分が使いこなせるようになるにはハードルが高いなという感じがあった。

感想

  • (利用経験での慣れが大きい気もするが)1:NやN:Nなどのマッピングアノテーションで対応できる(ただし方法がいくつかあり、どういった場面でどれを使うのが良いのかの選択が難しい)
  • ページングの実現も簡単
  • (DDDを使っている場合)集約ルートから、関連エンティティをgetter経由で取得できるのでコード量が少なくできる
  • N+1問題や3つ以上のテーブルのJOIN FETCHができない。意図していないクエリが生成されることもマチマチという課題がつきまとう

エンティティのコード例

1側

@Entity
data class Team(

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val teamId: Long? = null,

    val name: String,

    val type: String,

    @Version @NotNull val version: Int = 0

) : Serializable {

    @OneToMany(mappedBy = "teamId", cascade = [CascadeType.ALL])
    var members: MutableList<Member> = mutableListOf()
}

N側

@Entity
data class Member(

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    val memberId: Long? = null,

    val firstName: String,

    val lastName: String,

    val teamId: Long,

    @Version @NotNull val version: Int = 0

) : Serializable {
}

spring-data-jdbc

spring-data-jpaと似たような書き方で かつシンプルに使えそうという印象を持ったので試してみたのがこちら。

アノテーションマッピングの仕方がほとんど同じことや、CrudRepositoryが使えるためfindByIdやfindAll,saveなどはそのまま使えるので新しく覚えることも少なくコード量もjpa同様に少なくて済みそうな感じだった。

感想

  • spring-data-jpaと同じような感じで使えるため心理的ハードルが低い(個人的に)
  • マッピングの仕方が違うことやLAZYフェッチが出来ないため、JPAに慣れている場合はそこの注意が必要になりそう
  • LAZYフェッチがないなどJPAに比べてシンプルなためN+1問題などに悩まされることはなくせるor減らせそう
  • (手元では試せていないが)ページングについては対応できていないような記述、記事を見かけたのでページングを手軽に使うことは出来無さそう
  • 情報もまだ少なく、上記のページングの話も含めてまだ、これから機能や安定性が整ってくるという印象を受けた(シンプルなアプリであれば充分だとは思うが)

エンティティのコード例

1側

@Data
data class Team(

    @Id
    val teamId: Long? = null,

    val name: String,

    val type: String,

    @Version @NotNull val version: Int = 0

) : Serializable {
    
    @MappedCollection(idColumn="team_id", keyColumn="team_id")
    var members: MutableList<Member> = mutableListOf()
}

N側

@Data
data class Member(

    @Id
    val memberId: Long? = null,

    val firstName: String,

    val lastName: String,

    val teamId: Long,

    @Version @NotNull val version: Int = 0

) : Serializable {
}

jooq

参考のスライド内では「クエリビルダー型」と分類されていたライブラリの1つ。

spring-data-jpaとかに比べると記述は増えるが、よりSQLに近い書き方ができる一方で、JDBCのように生のSQLを書く必要はないという感じなので、バランスがよくJPAで抱えていた課題を解決しつつ、エンティティの自動生成などを活用することでDBへの変更にも追随しやすい、というメリットがありそうと考えて試してみた。

DDD X CQRS - 更新系と三焦経で異なるORMを併用して上手く行った話などを見る限り、DDDにも活用できそうなことも試してみたいと思った理由の1つ。

感想

  • spring-data-jpaに比べると記述量は多くなるが、クエリで条件や取得したい情報を明確に定義できるのは良いと思った
  • エンティティの自動生成などプラグインを活用することで、開発や運用もだいぶラクにできそうという印象を持てた
  • (自分の学習不足が大きいが)2つ以上のテーブルを結合する場合などの書き方がよく分からなかったし、それ専用のDTOクラスのようなものを用意する必要がありそうに感じた
  • 1:NのテーブルでNの関連をどう定義するばよいのかが分からなかった

エンティティのコード例

1側(関連エンティティの表現はたぶん間違っている)

@Data
data class Team(
    
    val teamId: Long? = null,

    val name: String,

    val type: String,
    
    @NotNull val version: Int = 0

) : Serializable {
    
    var members: MutableList<Member> = mutableListOf()
}

N側

@Data
data class Member(
    
    val memberId: Long? = null,

    val firstName: String,

    val lastName: String,

    val teamId: Long,
    
    @NotNull val version: Int = 0

) : Serializable {
}