Spring Bootで複数のOR Mapperを用いる際の挙動
前提
- OR(O/R) Mapperという考えの良し悪しは触れない
- この記事は、技術選定に失敗または前任者から引き継いだソースコード(Java)が、自分にとって使いづらい・運用しづらいOR Mapperを使用していて、徐々に切り替えていきたいと考えている方向けです
- MyBatisは厳密にはSQL Mapperという区分に入ると考えるが、本文ではOR Mapperとして扱う
前書き
JavaやSpringのプロジェクトでは、RDBへのアクセスプログラムを書くとき、OR Mapperを用いることが多い。 最近だと、筆者は下記のライブラリの使用経験がある。
- MyBatis
- JPA
- Doma2
MyBatisを主に使用していたが、最近引き継いだプロジェクトではJPAが使用されていたため、仕様をちょこっと読んだりしている。 Doma2に関して、前述のプロジェクトの周辺プログラムを書くときにメンバーに合わせて書いている。
最近では、Doma2がとっつきやすくて気に入っている。昔はs2daoを触っていたからか? 個人の所感では、JPAはEntity Managerなどの設定もあって、原因のわかりにくいバグが発生したりとあまり良い印象がない。個人の主観であり、引き継いだソースコードでのJPAの扱い方が悪いとも言えるので、一概には言えないが。
OR Mapperの移行
よっぽど致命的な障害が発生したならともかく、既に運用しているプロジェクトでいきなりOR Mapperを変更しましょうという提案が通ることはまずないと考えている。 ただ、使いづらいライブラリをそのまま使っていると、開発工数増加やモチベーション低下などにも繋がるので、徐々に移行できないか考えた。 新規テーブルや新機能開発時に、新しいライブラリを導入・開発して、機能改善の都度、既存ソースコードを改善していくことは比較的可能ではないか。
OR Mapperの共存
複数のOR Mapperを導入する場合、懸念点は多い。
- そもそも共存できるか?複雑な設定とならないか?
- コネクションプールやトランザクションはどうなる?
今回の記事では、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も試した方が良いとは思う。
- JPAでのINSERT,SELECT
- MyBatisでのINSERT,SELECT
- 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接続周りの挙動