カジュアルな技術ノート

小難しい技術のお話をできるだけわかりやすく...

O/R マッパー比較・入門編にて

今週の jflute さん勉強会で O/R マッパーの比較・入門編をやっていただきました。
新卒入社してそろそろ1年ですが DBFlute しか使ったことがない僕としては、他の O/R マッパーに関してはほぼ知識ゼロ状態。。
初めて聞く話が多かったですが、その一部を書いていきます。

使用した資料

Masatoshi Tada さんのスライドをみながら jflute さん流の解説で進めていただきました。

www.slideshare.net

O/R マッパーの分類

上記資料から完全抜粋ですが O/R マッパーを4分類しています。

  • JDBC ラッパー型
    JDBC をゆるくラップしたタイプ。
    例) Spring JDBC
Employee employee = jdbcTemplate.queryForObject(
  "SELECT id, name, salary FROM employee e WHERE e.id = ?",
  new Object[] { 1 },
  new RowMapper<Employee> () {
    public Employee mapRow(ResultSet rs, int rowNum) throws SQLException {
      int id = rs.getInt("id");
      String name = rs.getString("name");
      BigDecimal salary = rs.getBigDecimal("salary");
      return new Employee(id, name, salary);
    }
  }
);
  • SQL マッパー型
    SQL とクラスの詰め替えに特化したタイプ。
    例) MyBatis
<mapper namespace="com.example.mapper.EmployeeMapper">
  <select id="findById" resultType="Employee">
    SELECT e.id AS id,
           e.name AS name,
           e.salary AS salary
    FROM employee e
    WHERE e.id = #{id}
  </select>
</mapper>
Employee employee = employeeManager.findById(1);
  • クエリビルダー型
    SQL を直接書かずに対応したクラスやメソッドでデータ取得するタイプ。
    例) jOOQ
Result<Record> employee = create.select(EMPLOYEE.ID, EMPLOYEE.NAME, EMPLOYEE.SALARY)
                                .from(EMPLOYEE)
                                .where(EMPLOYEE.ID.equal(1))
                                .fetch();
  • OR マッパー型
    RDB を全く意識させずにデータを取得するタイプ。
    例) JPA
Employee employee = entityManager.find(Employee.class, 1);

さて、これらの O/R マッパーの特徴を学習コストとコードの書き方という視点でまとめてみました。

f:id:shin-kinoshita:20190317141023p:plain

あくまでざっくりとですが、JDBC ラッパー型に近づくほど備わっている機能が少なく学習コストが低い。
OR マッパー型に近づくほどより SQL を意識せずにデータ取得ができ、その分学習コストが高いという特徴を持ちます。

開発者を問わない SQL マッパー型

SQL マッパー型の代表格が MyBatis ですが、Java の O/R マッパーの中でもかなりのシェアを獲得しています。
その大きな理由の一つが SQL マッパー型の特徴である学習コストの低さ です。
MyBatis でデータを取得する際、取得条件は外部の SQL ファイルに書くのが基本です。
つまり DB アクセスの基本である SQL を知っていればほとんど学習コストなく扱うことができます。

jflute さんによると 開発者を選ばずとりあえず DB アクセスをして動くものを作りたいという現場で使われる傾向が強い とのこと。
しかし結果的にですが、誰でも扱える分共通化などがされない傾向があるとのことです。
アプリケーション側の画面が違えばほとんど同じ SQL なのに共通化していない現場が多くなってしまいがちなのだとか。

クエリビルダー型と OR マッパー型の比較

クエリビルダー型も OR マッパー型も SQL を直接書くことはせず、メソッドのみでデータ取得する点は共通しています。
大きな違いは 開発者がテーブル構成を意識するメソッドになっているかどうか です。
クエリビルダー型は完全に SQL に則った書き方になっているのでテーブル構成も意識する必要が出てきます。
一方でテーブル構成を意識することなく短いコード量でデータ取得が可能なのが OR マッパー型のメリットです。

しかし OR マッパー型で DB アクセス時のパフォーマンスを気にする必要があるケースでは N + 1 問題などが発生しやすくなります
OR マッパー型でもこれを避けるための実装することはもちろんできますが、基本 RDB を意識しないためいきなりパフォーマンスを意識するのも難しそうだなと感じます(あくまでクエリビルダー型である DBFlute を普段使っている僕の意見ですが、、)。

ちなみにこの理由から DBFlute は LazyLoad の採用をやめたそうです。
DBFlute で関連テーブルからデータを取得するときは LoadReferrer を使うことで一度の SQL で一気に取得するという思想になっています。

dbflute.seasar.org

O/R マッパーとは?人によって認識が違う

最後にこれは jflute さんから聞いた話ですが O/R マッパーと聞いて思い浮かべるものには人によってズレがある とのことです。
O/R マッパーの本来の意味を調べてみると

「アプリケーションで扱うオブジェクトとリレーショナルデータベースのデータをマッピングするもの」

なので機能としては少なくても JDBC ラッパー型も立派な O/R マッパーということができます。
一方で O/R マッパーはオブジェクトとリレーショナルデータベースをマッピングするものだから、テーブル構成等を意識させない OR マッパー型こそ真の O/R マッパーだと認識している人もいるとのことです。
そのため O/R マッパーについて話をするときは、どんな O/R マッパーについて話をしているのか認識を合わせることが大切 ということです。