「MyBatisって名前はよく見かけるけど、結局何なのか分からない」という人、意外と多いんじゃないでしょうか。検索してみても出てくるのはMapperの実装方法や動的SQLの書き方ばかりで、そもそもの立ち位置を説明してくれる記事は少ないですよね。

この記事では、MyBatisが何者なのか、JPA/Hibernateと何が違うのか、そしてSpring Bootでの最小構成までを一気に見ていきます。読み終わる頃には、自分のプロジェクトにMyBatisが合うかどうか判断できるようになっているはずです。

MyBatisとは何か

MyBatisは、SQLを自分で書くタイプのO/Rマッパーです。SQL文を実行して、その結果をJavaオブジェクトにマッピングする部分を肩代わりしてくれるライブラリだと考えると分かりやすいと思います。

もともとはApache Software Foundation傘下のiBatisというプロジェクトが前身で、のちにMyBatisという名前に改称されて現在の開発体制に移りました。改称の細かい経緯を知らなくても困ることはありませんが、「iBatis」という単語をどこかで見かけたら同じ系譜のものだと思ってもらって大丈夫です。

ポイントは、テーブル操作のSQL自体は開発者が書くという点です。これがJPAとの一番大きな違いになります。

JPA/Hibernateとの思想的な違い

JPA(Hibernateなどの実装を含む)は、エンティティクラスとテーブルの対応関係を定義しておけば、SQLは基本的にフレームワークが自動生成してくれます。

// Spring Data JPAの場合、SQLを書かなくてもfindByIdが動く
public interface UserRepository extends JpaRepository<User, Long> {
}

このfindByIdはJPA仕様そのものではなく、Spring Data JPAが提供するクエリ導出機能で、戻り値は Optional<User> になります。JPQLやEntityManagerを直接使う書き方でも、SQLではなくオブジェクト指向のクエリ言語でデータを取得するという点は変わりません。

一方MyBatisは、SQL文そのものをXMLやアノテーションで明示的に書きます。フレームワークが担当するのは、実行結果をJavaオブジェクトへマッピングする部分だけです。

// MyBatisの場合、SQL自体を自分で書く
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM users WHERE id = #{id}")
    User findById(Long id);
}

戻り値の型もJPAとは違い、こちらはUserを直接返します(Optionalではありません)。この違いは、単なる書き方の差ではなく思想の差です。JPAは「SQLを意識しなくていい世界」を目指していて、MyBatisは「SQLを完全にコントロールしたい人のための世界」を目指しています。どちらが優れているという話ではなく、何を自動化して何を手動にしたいかの選択なんですよね。

MyBatisが向いているケース・向いていないケース

実際に選ぶときの判断軸は、意外とシンプルです。

複数テーブルを跨ぐ複雑な集計SQLを書くことが多い、あるいは正規化が不十分だったりカラム名が独自ルールだったりするレガシーなDBスキーマを扱っていてエンティティにきれいに落とし込みにくい場合は、MyBatisのほうが素直に書けることが多いです。SQLをそのまま書けるので、実行計画を意識したチューニングもやりやすくなります。

逆に、会員管理やToDoアプリのようにテーブル構造とエンティティがほぼ1対1で対応するシンプルなCRUDが中心で、DBの種類を将来的に変える可能性がある、あるいはSQLを書く手間自体を減らしたいという場合は、JPAのほうが向いています。

なお、1つのプロジェクトでどちらか一方に固定しなければいけないわけではなく、参照系はJPA、複雑な集計だけMyBatisといった併用も実務ではよく見かけます。どちらを選ぶべきか判断軸をさらに詰めたい場合は、MyBatisとJPAの比較記事 で詳しく扱っています。

mybatis-spring-boot-starterで最小構成を作る

理屈だけだと掴みにくいので、実際に動かしてみましょう。Spring Bootには公式のスターターが用意されていて、最小限の設定ですぐに使い始められます。バージョンは執筆時点のものなので、導入時は最新版を確認してください。

まずは依存関係の追加です。mybatis-spring-boot-starterの3.x系はSpring Boot 3.x系に対応しています。

dependencies {
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.4'
    runtimeOnly 'com.mysql:mysql-connector-j'
}

DB接続の設定は、通常のSpring Bootと同じくapplication.propertiesに書くだけです。MyBatis独自の接続設定は特に必要ありません。パスワードを平文で書いているのはあくまでローカル検証用で、本番環境では環境変数やSecrets Managerなどの秘密情報管理サービスから注入するようにしましょう。

spring.datasource.url=jdbc:mysql://localhost:3306/sampledb
spring.datasource.username=root
spring.datasource.password=secret

あとはMapperインターフェースを1つ作れば準備完了です。

@Mapper
public interface UserMapper {
    @Select("SELECT id, name FROM users WHERE id = #{id}")
    User findById(Long id);
}

引数が1つだけなら@Paramは省略できますが、2つ以上のパラメータを渡すメソッドでは#{}に対応する名前を明示するために@Paramが必須になります。詳しい書き方は後述のMapper実装ガイドで扱っています。

このMapperは、他のBeanと同じようにサービスクラスへ注入して使えます。

@Service
public class UserService {
    private final UserMapper userMapper;

    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    public User getUser(Long id) {
        return userMapper.findById(id);
    }
}

mybatis-spring-boot-starterを使う場合、@Mapperを付けたインターフェースはSpring Bootのオートコンフィグレーションによって自動的に検出されるため、通常は@MapperScanを追加する必要はありません。Mapperを複数のパッケージに分けて配置するなど、スキャン範囲を明示的に指定したいときだけ@MapperScanを使うと考えておけば大丈夫です。

次に読むべき記事

ここまででMyBatisの立ち位置とJPAとの違いは掴めたと思います。次のステップとしては、目的に応じて以下の記事が参考になります。

まとめ

MyBatisは、SQLを自分で書きたい人のためのO/Rマッパーで、JPAはSQLを自動化したい人のためのO/Rマッパーです。この思想の違いさえ押さえておけば、どちらを選ぶべきかは「複雑なSQLを書きたいかどうか」でだいたい判断できます。

まずはmybatis-spring-boot-starterを入れて、1本のMapperを動かしてみるところから始めてみてください。実際に手を動かすと、SQLを直接コントロールする感覚がすぐに掴めると思います。