Spring Bootで複数のOR Mapperを用いる際の挙動

Dev/Java/Spring Boot
Spring Boot
JPA
MyBatis
Doma2
OR Mapper

更新日時 : 2021/02/15 12:52
投稿日時 : 2021/02/13 07:57

前提

  • OR(O/R) Mapperという考えの良し悪しは触れない
  • この記事は、技術選定に失敗または前任者から引き継いだソースコード(Java)が、自分にとって使いづらい・運用しづらいOR Mapperを使用していて、徐々に切り替えていきたいと考えている方向けです
  • MyBatisは厳密にはSQL Mapperという区分に入ると考えるが、本文ではOR Mapperとして扱う

前書き

JavaやSpringのプロジェクトでは、RDBへのアクセスプログラムを書くとき、OR Mapperを用いることが多い。 最近だと、筆者は下記のライブラリの使用経験がある。

  1. MyBatis
  2. JPA
  3. Doma2

MyBatisを主に使用していたが、最近引き継いだプロジェクトではJPAが使用されていたため、仕様をちょこっと読んだりしている。 Doma2に関して、前述のプロジェクトの周辺プログラムを書くときにメンバーに合わせて書いている。

最近では、Doma2がとっつきやすくて気に入っている。昔はs2daoを触っていたからか? 個人の所感では、JPAはEntity Managerなどの設定もあって、原因のわかりにくいバグが発生したりとあまり良い印象がない。個人の主観であり、引き継いだソースコードでのJPAの扱い方が悪いとも言えるので、一概には言えないが。

OR Mapperの移行

よっぽど致命的な障害が発生したならともかく、既に運用しているプロジェクトでいきなりOR Mapperを変更しましょうという提案が通ることはまずないと考えている。 ただ、使いづらいライブラリをそのまま使っていると、開発工数増加やモチベーション低下などにも繋がるので、徐々に移行できないか考えた。 新規テーブルや新機能開発時に、新しいライブラリを導入・開発して、機能改善の都度、既存ソースコードを改善していくことは比較的可能ではないか。

OR Mapperの共存

複数のOR Mapperを導入する場合、懸念点は多い。

  1. そもそも共存できるか?複雑な設定とならないか?
  2. コネクションプールやトランザクションはどうなる?

今回の記事では、1を中心に動作確認することを目的とする。

対象のOR Mapper

  • JPA
  • MyBatis

必要なライブラリ

JPAとMyBatisは https://start.spring.io/ で初期プロジェクトファイル作成時に選択可能。

pom.xml

<!-- webは必須ではない -->
<dependency>
  <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>2.1.4</version>
</dependency>

<!-- ローカル環境のみで確認したいので、H2DBを利用 -->
<dependency>
	<groupId>com.h2database</groupId>
	<artifactId>h2</artifactId>
	<scope>runtime</scope>
</dependency>

必要な設定

デフォルトだと、JPAがテーブル定義を再作成しようとして不便だったので、明示的に無効とした

application.properties

# デフォルトだとテーブル再作成を自動で実施するため、無効化
spring.jpa.hibernate.ddl-auto=none

# Entityクラスでフィールド名をキャメルケースにする場合に必要
mybatis.configuration.map-underscore-to-camel-case=true

logging.level.org.hibernate.SQL=debug

各クラスの設定

結論から言うとRepositoryクラスの特殊設定や追加でBeanを作成するなどの設定は必要なかった。 はまったところは@GeneratedValue(strategy = GenerationType.AUTO)にしたら、シーケンス発行に失敗したので、GenerationType.IDENTITYに変更したぐらい。

@Entity
public class User {
    public User() {
    }

    public User(String name, String createdBy) {
        this.name = name;
        this.createdBy = createdBy;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    private String name;
    private String createdBy;

動作検証

次のパターンで動作検証し、データの不整合などは起きなかった。 もっともかなりシンプルなパターンしか試していないので、UPDATEやDELETEも試した方が良いとは思う。

  1. JPAでのINSERT,SELECT
  2. MyBatisでのINSERT,SELECT
  3. JPAでのINSERT, MyBatisでのINSERT,SELECT
$ curl http://localhost:8080/jpa/hanako
[{"id":1,"name":"Taro","createdBy":"data.sql"},{"id":2,"name":"hanako","createdBy":"JPA"}]
$ curl http://localhost:8080/mybatis/takashi
[{"id":1,"name":"Taro","createdBy":"data.sql"},{"id":2,"name":"hanako","createdBy":"JPA"},{"id":3,"name":"takashi","createdBy":"MyBatis"}]
$ curl http://localhost:8080/multiple/vagivagi
[{"id":1,"name":"Taro","createdBy":"data.sql"},{"id":2,"name":"hanako","createdBy":"JPA"},{"id":3,"name":"takashi","createdBy":"MyBatis"},{"id":4,"name":"vagivagi","createdBy":"JPA"},{"id":5,"name":"vagivagi","createdBy":"MyBatis"}]

サンプルコード

https://github.com/vagivagi/demo-multiple-mapper

まとめ

ハマるところはそこまでなく、同じトランザクションで2つのOR Mapperの動作検証できた。 次回は下記事項を検証したい。

  • Doma2の追加
  • コネクションプールやトランザクション制御などDB接続周りの挙動