MyBatis徹底解説:導入から動的SQL、Spring Boot連携まで

この記事から得られる知識

この記事を読むことで、以下の知識を習得し、MyBatisを実務で活用するための強固な基盤を築くことができます。


  • MyBatisの基本概念と、それがJDBCの課題をどのように解決するかの理解
  • XMLマッパーとアノテーションマッパーの基本的な使い方(CRUD操作)
  • MyBatisの強力な機能である「動的SQL」の具体的な記述方法と活用シーン
  • ResultMapを用いた複雑なオブジェクトマッピング(関連マッピングを含む)の実現方法
  • Spring BootとMyBatisを連携させ、効率的なデータベースアクセス層を構築する手順

はじめに:MyBatisとは何か?

MyBatisは、Javaアプリケーションでリレーショナルデータベースを扱うための永続性フレームワークです。 以前は「iBATIS」という名称で知られていました。 Javaにおけるデータベース接続の標準APIであるJDBCは、非常に低レベルなAPIであり、定型的なコード(ボイラープレートコード)が多くなりがちです。例えば、リソースの解放処理(Connection, Statement, ResultSetのクローズ)を忘れると、リソースリークの原因となります。

MyBatisは、こうしたJDBCの煩雑さを解消し、開発者がより本質的なビジネスロジックに集中できるように支援します。 最大の特徴は、SQL文とJavaのオブジェクト(POJO – Plain Old Java Object)をマッピングする点にあります。 このアプローチは「SQLマッパー」と呼ばれます。

JPA (Java Persistence API) に基づくHibernateなどのO/Rマッパー(ORM)が、SQLを自動生成してテーブルとオブジェクトを直接マッピングするのに対し、MyBatisは開発者が書いたSQLをそのまま活かすことができます。 これにより、複雑なクエリやパフォーマンスチューニングが必要な場面で、SQLを完全にコントロールできるという強力な柔軟性を提供します。


第1章: MyBatisの基本と環境構築

MyBatisを使い始めるには、まずプロジェクトにライブラリを追加し、データベースへの接続設定を行う必要があります。

1.1 依存関係の追加 (Maven)

Mavenプロジェクトの場合、pom.xmlに以下の依存関係を追加します。バージョンは適宜最新のものを確認してください。

<dependencies>
  <!-- MyBatis本体 -->
  <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.17</version> <!-- 2024年11月時点の例 -->
  </dependency>

  <!-- 使用するデータベースのJDBCドライバ (例: PostgreSQL) -->
  <dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.7.3</version>
  </dependency>
</dependencies>

1.2 MyBatis設定ファイル (mybatis-config.xml)

次に、MyBatisの動作全体を定義する設定ファイルを作成します。データベース接続情報やトランザクション管理、マッパーファイルの場所などを記述します。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="org.postgresql.Driver"/>
        <property name="url" value="jdbc:postgresql://localhost:5432/your_database"/>
        <property name="username" value="your_user"/>
        <property name="password" value="your_password"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <!-- 後述するマッパーXMLファイルのパスを指定 -->
    <mapper resource="com/example/mapper/UserMapper.xml"/>
  </mappers>
</configuration>

1.3 SqlSessionFactoryとSqlSession

MyBatisの操作は、SqlSessionFactorySqlSessionという2つの中心的なオブジェクトを介して行われます。

  • SqlSessionFactory:
    設定ファイルを読み込み、SqlSessionを生成するためのファクトリです。アプリケーションの起動時に一度だけ生成し、シングルトンとして管理するのが一般的です。
  • SqlSession:
    データベースに対するSQLの実行(SELECT, INSERT, UPDATE, DELETE)やトランザクション管理を行うためのオブジェクトです。スレッドセーフではないため、各スレッド(メソッド)内で生成・使用し、必ずクローズする必要があります。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

try (SqlSession session = sqlSessionFactory.openSession()) {
  // ここでデータベース操作を行う
}

第2章: XMLマッパーによるCRUD操作

MyBatisでは、Javaのインターフェース(Mapperインターフェース)とXMLファイルを組み合わせることで、直感的にデータベース操作を実装できます。

2.1 MapperインターフェースとXMLの関連付け

まず、実行したいSQLに対応するメソッドを定義したJavaインターフェースを作成します。

// User.java (データ格納用POJO)
public class User {
    private Integer id;
    private String name;
    private String email;
    // getter, setter ...
}

// UserMapper.java (Mapperインターフェース)
public interface UserMapper {
    User findById(Integer id);
    List<User> findAll();
    int insert(User user);
    int update(User user);
    int delete(Integer id);
}

次に、このインターフェースのメソッドと実際のSQL文を紐付けるXMLファイルを作成します。XMLのnamespace属性に、対応するインターフェースの完全修飾名を指定することが重要です。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<mapper namespace="com.example.mapper.UserMapper">

  <!-- SELECT (1件) -->
  <select id="findById" resultType="com.example.model.User">
    SELECT id, name, email FROM users WHERE id = #{id}
  </select>

  <!-- SELECT (複数件) -->
  <select id="findAll" resultType="com.example.model.User">
    SELECT id, name, email FROM users ORDER BY id
  </select>

  <!-- INSERT -->
  <insert id="insert" parameterType="com.example.model.User" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO users (name, email) VALUES (#{name}, #{email})
  </insert>

  <!-- UPDATE -->
  <update id="update" parameterType="com.example.model.User">
    UPDATE users SET name = #{name}, email = #{email} WHERE id = #{id}
  </update>

  <!-- DELETE -->
  <delete id="delete">
    DELETE FROM users WHERE id = #{id}
  </delete>
</mapper>

ポイント

  • id属性にはMapperインターフェースのメソッド名を指定します。
  • resultType属性には、検索結果をマッピングするクラスの完全修飾名を指定します。
  • parameterType属性には、引数として渡されるオブジェクトのクラス名を指定します(省略可能な場合も多い)。
  • パラメータは #{propertyName} という形式で埋め込みます。 これはJDBCのPreparedStatementにおける?に相当し、SQLインジェクションを防ぐ安全な方法です。
  • useGeneratedKeys="true" keyProperty="id"は、データベースで自動生成された主キー(ID)を、引数のUserオブジェクトのidプロパティにセットするための設定です。

2.2 Mapperの実行

SqlSessionからMapperインターフェースの実装を取得し、メソッドを呼び出すだけでSQLが実行されます。

try (SqlSession session = sqlSessionFactory.openSession()) {
    UserMapper mapper = session.getMapper(UserMapper.class);

    // 1件取得
    User user = mapper.findById(1);
    System.out.println(user.getName());

    // 登録
    User newUser = new User();
    newUser.setName("New User");
    newUser.setEmail("new@example.com");
    mapper.insert(newUser);
    
    session.commit(); // 変更を確定
}

このように、MyBatisを使うとJDBCで必要だった煩雑な定型処理が隠蔽され、非常にすっきりとしたコードでデータベースを操作できます。


第3章: 動的SQL – MyBatisの真骨頂

MyBatisが持つ最も強力な機能の一つが「動的SQL」です。 これは、引数の値に応じてSQL文の構造を動的に変更する機能で、複雑な検索条件などをJavaコードで文字列連結することなく、安全かつ可読性の高いXMLで記述できます。

動的SQLで使われる主なタグ

タグ 説明
<if> 指定した条件がtrueの場合にのみ、囲んだSQL片を有効にします。
<choose>, <when>, <otherwise> Javaのswitch文のように、複数の条件のうち最初に一致したものだけを適用します。
<where> 内部に有効なSQL片が存在する場合にのみWHERE句を生成します。また、先頭のANDORを自動で削除してくれます。
<set> UPDATE文で使用します。内部に有効なSQL片が存在する場合にのみSET句を生成し、末尾の不要なカンマを自動で削除します。
<foreach> コレクション(Listや配列など)をループ処理し、IN句のようなSQLを生成するのに便利です。
<bind> OGNL式の結果を変数として定義し、SQL内で利用できるようにします。

実践的な例:複数条件での動的検索

例えば、ユーザー名とメールアドレスで検索する機能を実装する場合を考えます。どちらの条件も任意で指定できるとします。

// Mapperインターフェースのメソッド
// @ParamアノテーションでXMLから参照する名前を明示
List<User> findByCriteria(@Param("name") String name, @Param("email") String email);
<!-- 動的SQLを使用したSELECT文 -->
<select id="findByCriteria" resultType="com.example.model.User">
  SELECT id, name, email
  FROM users
  <where>
    <if test="name != null and name != ''">
      AND name LIKE #{name}
    </if>
    <if test="email != null and email != ''">
      AND email = #{email}
    </if>
  </where>
  ORDER BY id
</select>

この例では、

  • <if>タグにより、nameemailがnullや空文字列でない場合にのみ、条件句がSQLに追加されます。
  • <where>タグが、不要なANDを自動で取り除き、有効なWHERE句を生成してくれます。例えば、nameのみが指定された場合、SQLはWHERE name LIKE ?となり、構文エラーを防ぎます。

このように動的SQLを使えば、Java側で複雑なif文によるSQL組み立てロジックを持つ必要がなくなり、コードの保守性が大幅に向上します。


第4章: アノテーションによるマッピング

MyBatisでは、XMLファイルの代わりにJavaのアノテーションを使ってSQLを記述することも可能です。 これにより、簡単なSQLであればXMLファイルを作成する手間を省き、Javaコード内で完結させることができます。

基本アノテーション

  • @Select: SELECT文を記述します。
  • @Insert: INSERT文を記述します。
  • @Update: UPDATE文を記述します。
  • @Delete: DELETE文を記述します。

これらのアノテーションはMapperインターフェースの各メソッドに直接付与します。

public interface UserAnnotationMapper {
    @Select("SELECT id, name, email FROM users WHERE id = #{id}")
    User findById(Integer id);

    @Insert("INSERT INTO users(name, email) VALUES(#{name}, #{email})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    int insert(User user);

    @Update("UPDATE users SET name=#{name}, email=#{email} WHERE id =#{id}")
    int update(User user);

    @Delete("DELETE FROM users WHERE id = #{id}")
    int delete(Integer id);
}

XMLとの使い分け

アノテーションはシンプルで短いSQLには便利ですが、いくつかのデメリットも存在します。

  • 複雑なSQL: 長くて複雑なSQLや、動的SQLを記述するには不向きです。アノテーション内で文字列としてSQLを組み立てるのは可読性や保守性を著しく低下させます。
  • SQLの分離: MyBatisの大きな利点の一つは、SQLをJavaコードから分離できる点です。 アノテーション方式ではこの利点が失われます。

一般的には、「単純なCRUDはアノテーション、複雑なクエリや動的SQLはXML」というように使い分けるのが良いプラクティスとされています。


第5章: Spring Bootとの連携

MyBatisはSpring Frameworkと非常に相性が良く、特にSpring Bootと組み合わせることで、設定を大幅に簡略化できます。 MyBatis-Springという専用の連携ライブラリが提供されています。

5.1 依存関係の追加

Spring Bootプロジェクトでは、mybatis-spring-boot-starterを追加するだけで、必要なライブラリが一括で導入されます。

<!-- pom.xml -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>3.0.3</version>
</dependency>

5.2 application.propertiesでの設定

データベース接続情報やMyBatisに関する設定は、application.properties(またはapplication.yml)ファイルに記述します。 これにより、mybatis-config.xmlは不要になります。

# データベース接続設定
spring.datasource.url=jdbc:postgresql://localhost:5432/your_database
spring.datasource.username=your_user
spring.datasource.password=your_password
spring.datasource.driver-class-name=org.postgresql.Driver

# MyBatis設定
# マッパーXMLファイルの場所を指定
mybatis.mapper-locations=classpath:mapper/*.xml
# resultTypeで指定するクラスのパッケージ名を指定しておくと、クラス名を省略できる
mybatis.type-aliases-package=com.example.model

5.3 MapperのスキャンとDI

Spring Bootでは、Mapperインターフェースに@Mapperアノテーションを付与するだけで、コンポーネントスキャンの対象となり、SpringのDIコンテナに登録されます。

import org.apache.ibatis.annotations.Mapper;

@Mapper // このアノテーションが重要
public interface UserMapper {
    // ... メソッド定義 ...
}

これにより、Serviceクラスなどで@Autowiredを使ってMapperをインジェクション(注入)できるようになります。SqlSessionFactorySqlSessionを直接扱うコードを書く必要がなくなり、非常にシンプルになります。

@Service
public class UserService {

    private final UserMapper userMapper;

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

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

Spring Bootとの連携により、トランザクション管理もSpringの@Transactionalアノテーションで宣言的に行えるようになるなど、多くの恩恵を受けられます。


第6章: 高度なマッピング – ResultMap

これまではresultTypeを使い、カラム名とプロパティ名が一致する場合のシンプルなマッピングを見てきました。しかし、実務ではより複雑なマッピングが必要になる場面が多々あります。そうした課題を解決するのがResultMapです。

ResultMapは、データベースの検索結果(ResultSet)をどのようにJavaオブジェクトにマッピングするかを明示的に定義するための要素です。

6.1 カラム名とプロパティ名の不一致を解決する

データベースのカラム名がスネークケース(例: user_id)、Javaのプロパティ名がキャメルケース(例: userId)であることは一般的です。ResultMapを使えば、この差異を吸収できます。

<resultMap id="userResultMap" type="com.example.model.User">
  <id property="id" column="id"/> <!-- 主キー -->
  <result property="userName" column="user_name"/>
  <result property="emailAddress" column="email_address"/>
</resultMap>

<select id="findUserWithCustomMapping" resultMap="userResultMap">
  SELECT id, user_name, email_address FROM users WHERE id = #{id}
</select>

selectタグのresultTypeの代わりにresultMap属性を使い、定義したresultMapのIDを指定します。

6.2 関連マッピング (Association & Collection)

ResultMapの真価は、リレーションを持つテーブルの検索結果を、ネストしたオブジェクト構造にマッピングする際に発揮されます。

  • <association>: 1対1の関連をマッピングします。(例: ユーザーと住所)
  • <collection>: 1対多の関連をマッピングします。(例: ユーザーとその注文履歴リスト)

例: 1対多のマッピング (ユーザーと投稿リスト)

一人のユーザーが複数の投稿(Post)を持つケースを考えます。

// User.java
public class User {
    private Integer id;
    private String name;
    private List<Post> posts; // 投稿のリストを持つ
    // ...
}

// Post.java
public class Post {
    private Integer id;
    private String title;
    private String content;
    // ...
}

JOINした結果を、このネスト構造にマッピングします。

<resultMap id="userWithPostsResultMap" type="User">
  <id property="id" column="user_id"/>
  <result property="name" column="user_name"/>
  <!-- 1対多の関連: postsプロパティにPostオブジェクトのリストをマッピング -->
  <collection property="posts" ofType="Post">
    <id property="id" column="post_id"/>
    <result property="title" column="post_title"/>
    <result property="content" column="post_content"/>
  </collection>
</resultMap>

<select id="findUserWithPosts" resultMap="userWithPostsResultMap">
  SELECT
    u.id AS user_id,
    u.name AS user_name,
    p.id AS post_id,
    p.title AS post_title,
    p.content AS post_content
  FROM
    users u
  LEFT JOIN
    posts p ON u.id = p.user_id
  WHERE
    u.id = #{id}
</select>

このResultMapにより、MyBatisはJOIN結果からユーザー情報と、そのユーザーに紐づく投稿情報のリストを適切に組み立て、Userオブジェクトを生成してくれます。これにより、いわゆる「N+1問題」を回避しつつ、効率的に関連データを取得できます。


まとめ

本記事では、JavaのSQLマッパーフレームワークであるMyBatisについて、基本的な概念から環境構築、CRUD操作、強力な動的SQL、アノテーション記法、Spring Bootとの連携、そして高度なマッピングを実現するResultMapまで、幅広く解説しました。

MyBatisは、SQLの柔軟性とJavaオブジェクトの利便性を両立させる優れたフレームワークです。SQLを直接記述できるため、パフォーマンスが求められる複雑なシステムのデータアクセス層において、強力な選択肢となります。

この記事が、あなたのMyBatis学習の一助となれば幸いです。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です