Skip to main content

MyBatisによるDBアクセス

この章では、Spring Bootでデータベースにアクセスする方法として MyBatis を学ぶ。 Webアプリケーションはデータを保存・取得するためにDBとのやりとりが不可欠であり、 MyBatisを使うことで JavaのクラスとSQLを結びつけた安全で整理されたDBアクセス を実現できる。

学習のゴール

  • MyBatisが「O/Rマッパー」であることを理解できる
  • application.properties によるDB接続設定を行える
  • schema.sqldata.sql でテーブル作成と初期データ登録を行える
  • テーブルと対応するエンティティクラス(Javaクラス)を定義できる
  • Mapperインターフェースにアノテーションを定義して、SQLとメソッドを結びつけられる
  • MapperインターフェースとXMLマッパーファイルを対応付け、SQLとメソッドを結びつけられる
  • @Mapper により、MyBatisが自動で実装クラスを生成する仕組みを理解できる
  • resultMap<collection> を用いて1対多のリレーションを扱える
  • コントローラからMapperを呼び出し、DBのデータを画面表示まで連携できる

MyBatisとは?

MyBatisは O/Rマッパー のひとつである。

  • O/Rマッピング(Object-Relational Mapping) とは

    • Javaのオブジェクト(クラスのインスタンス)と、RDB(リレーショナルデータベース)のレコードを対応付ける仕組み
    • 例:users テーブルの1行を、User クラスのインスタンスとして扱える
    • 「JavaとDBは本来別世界のもの」だが、テーブルの行をオブジェクトにできた方が便利という発想
  • MyBatisの特徴

    • SQLを自分で書くスタイルのO/Rマッパー
    • SQLを自動生成するHibernateに比べて、学習や業務では理解しやすく、自由度が高い
    • SQLは アノテーション でも XMLファイル でも書ける
    • 実務では 複雑なSQLをXMLで管理するのが一般的

👉 初学者にとっては、「SQLを書くのは自分。実装はMyBatisが自動でやってくれる」 という理解でよい。


DB接続の設定(application.properties)

Spring Bootでは、DB接続の設定を application.properties に記述する。

この章を進める前に:pom.xml への追加が必要

この章では PostgreSQL を使用するため、pom.xml に以下の依存関係を追加すること。

<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
spring.datasource.url=jdbc:postgresql://localhost:5432/demo
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.sql.init.mode=always
mybatis.mapper-locations=classpath*:mapper/*.xml
  • spring.datasource.url : DBの接続先
    • localhost … 接続先のホスト(自分のPC上のPostgreSQLサーバー)
    • 5432 … ポート番号(PostgreSQLのデフォルト)
    • demo … 接続するデータベース名
  • spring.datasource.driver-class-name : JDBCドライバの指定(PostgreSQL用ドライバクラス)
  • spring.sql.init.mode=always : アプリ起動時に schema.sql / data.sql を自動実行する設定
    • PostgreSQL使用時はこの設定が必要。省略すると初期化SQLが実行されない。
  • mybatis.mapper-locations : MyBatisのマッパーファイル(SQLを書くXMLファイル)の場所
    • classpath*: … 「クラスパス上のフォルダを探してね」という意味
    • mapper/*.xml … mapper フォルダにある .xml ファイルを全部読み込む、という指定
    • 具体的には src/main/resources/mapper/UserMapper.xml のようなファイルが対象になる 👉 つまり、resources/mapper フォルダに置いたすべてのXMLファイルを、MyBatisが自動的に読み込む という意味。
application.properties の設定ミスはエラーの原因になる

DB接続設定が間違っていると、アプリ起動時にエラーが発生する。よくあるミス:

  • spring.datasource.url のホスト名・ポート・DB名が間違っている
  • spring.datasource.username / password がPostgreSQLの設定と一致していない
  • spring.sql.init.mode=always が抜けていて初期化SQLが実行されない
  • mybatis.mapper-locations のパスが UserMapper.xml の実際の配置場所と一致していない

エラーメッセージに Connection refusedFATAL: password authentication failed が出た場合は、接続情報を見直す。

👉 新人研修では「この設定は先輩が用意したものをコピペすればOK」と考えてよい。 大切なのは、 このファイルにDB接続やMyBatisの設定を書く ということ。


schema.sql と data.sql

Spring Bootでは、resources フォルダに schema.sqldata.sql を置くと、起動時に自動実行される。

  • schema.sql : テーブル定義を記述する
  • data.sql : 初期データを挿入する

例:

schema.sql
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
age INT
);
data.sql
INSERT INTO users (name, age) VALUES ('Taro', 20);
INSERT INTO users (name, age) VALUES ('Hanako', 25);

👉 この仕組みにより、アプリを起動したときに自動的にテーブルと初期データが準備される。


エンティティの作成

エンティティ(Entity) とは、データベースのテーブルと1対1で対応するJavaクラスのことを指す。 テーブルの「1行(レコード)」をJavaの「1つのオブジェクト」として扱う役割を持つ。

例えば users テーブルには「id」「name」「age」というカラム(列)があるが、これをJava側では User クラスの idnameage フィールドとして表現する。 これにより、SQLの結果をそのままJavaのオブジェクトとして扱えるようになる。

User.javajava
package com.example.demo.entity;
public class User {
private int id;
private String name;
private int age;
// getter / setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}
  • フィールド名はテーブルのカラム名と一致している必要がある
  • getter / setter を必ず用意する

👉 これで、users テーブルの1行を User クラスのインスタンスとして扱えるようになる。


Mapperインターフェースとマッパーファイル

MyBatisでは、SQLを呼び出すために Mapperインターフェース を定義し、そこに記述したメソッドとSQLを対応付ける。 SQLはアノテーションで直接書くこともできるし、XMLファイルに分離して管理することもできる。

アノテーションを使ったMapper(最も簡単な方法)

UserMapper.javajava
package com.example.demo.mapper;
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface UserMapper {
@Select("SELECT id, name, age FROM users")
List<User> findAll();
@Select("SELECT id, name, age FROM users WHERE id = #{id}")
User findById(int id);
}
  • メリット:Javaファイルだけで完結するので手軽
  • デメリット:SQLが増えるとJavaコードが読みにくくなり、複雑なJOINや改行の多いSQLには不向き

👉 実務では SQLをXMLに分離して管理する方法 が推奨される。


Mapperインターフェース(XMLと対応させる)

UserMapper.javajava
package com.example.demo.mapper;
import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper // MyBatisが自動で実装クラスを生成
public interface UserMapper {
List<User> findAll(); // 全件取得
User findById(int id); // idで検索
}
  • @Mapper を付けるだけで、実装クラスは不要
  • 実際のSQLは マッパーファイル(XML) に記述する
@Mapper アノテーションを付け忘れると?

@Mapper を忘れると、MyBatisはそのインターフェースを認識せず、 起動時に No qualifying bean of type 'UserMapper' などのエラーが発生する。 Mapperインターフェースには必ず @Mapper を付けること。

マッパーファイル(UserMapper.xml)

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

<mapper namespace="com.example.demo.mapper.UserMapper">

<!-- Userを全件取得 -->
<select id="findAll" resultType="com.example.demo.entity.User">
SELECT id, name, age FROM users
</select>

<!-- idで1件検索 -->
<select id="findById" parameterType="int" resultType="com.example.demo.entity.User">
SELECT id, name, age FROM users WHERE id = #{id}
</select>

</mapper>
  • namespaceインターフェースの完全修飾名 に一致させる必要がある
  • <select id="findAll">id は、インターフェースのメソッド名と一致させる
  • resultType で返り値のクラスを指定
namespace の値を間違えると動作しない

namespace には @Mapper を付けたインターフェースの 完全修飾クラス名 を指定する必要がある。 パッケージ名を間違えると、MyBatisがMapperインターフェースとXMLを対応付けられず、 Invalid bound statement (not found) というエラーが発生する。

👉 これにより、Java側の UserMapper.findAll() を呼ぶとSQLが実行され、結果が User のリストとして返る。


コントローラで利用する

作成した UserMapper インターフェースをコントローラから呼び出してみよう。

UserListController.javajava
package com.example.demo.controller;
import com.example.demo.mapper.UserMapper;
import com.example.demo.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@Controller
public class UserListController {
private final UserMapper userMapper;
// コンストラクタインジェクション
public UserListController(UserMapper userMapper) {
this.userMapper = userMapper;
}
@GetMapping("/users")
public String list(Model model) {
List<User> users = userMapper.findAll();
model.addAttribute("users", users);
return "user-list";
}
}
  • @Mapper を付けた UserMapper は、Springが自動でBean登録してくれる
  • @Autowired を明示しなくてもコンストラクタインジェクションで利用可能
  • userMapper.findAll() を呼ぶと、UserMapper.xml のSQLが実行される

👉 コントローラ側はSQLを意識せず、メソッド呼び出しだけでDBアクセスができる。


1対多のリレーションを扱う(UserとPost)

User に関連する Post を一緒に取得する例を見てみよう。 ここでは、User(1人のユーザー)に対して複数のPost(投稿)が存在する という関係を扱う。

エンティティの定義

まずはエンティティクラスを定義する。 User エンティティには List<Post> フィールドを持たせ、ユーザーに紐づく投稿を保持できるようにする。

package com.example.demo.entity;

import java.util.List;

public class User {
private int id;
private String name;
private int age;

// 1対多のリレーション(ユーザーが複数の投稿を持つ)
private List<Post> posts;

// getter / setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public int getAge() { return age; }
public void setAge(int age) { this.age = age; }

public List<Post> getPosts() { return posts; }
public void setPosts(List<Post> posts) { this.posts = posts; }
}
package com.example.demo.entity;

public class Post {
private int id;
private int userId;
private String content;

// getter / setter
public int getId() { return id; }
public void setId(int id) { this.id = id; }

public int getUserId() { return userId; }
public void setUserId(int userId) { this.userId = userId; }

public String getContent() { return content; }
public void setContent(String content) { this.content = content; }
}

👉 これで、User エンティティは posts フィールドを通じて、自分に紐づく投稿一覧を持てるようになる。


Mapperインターフェース

package com.example.demo.mapper;

import com.example.demo.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
User findUserWithPosts(int id);
}

XMLマッパーファイル

src/main/resources/mapper/UserMapper.xml
<mapper namespace="com.example.demo.mapper.UserMapper">

<resultMap id="UserWithPostsResultMap" type="com.example.demo.entity.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<collection property="posts" ofType="com.example.demo.entity.Post">
<id property="id" column="post_id"/>
<result property="userId" column="user_id"/>
<result property="content" column="content"/>
</collection>
</resultMap>

<select id="findUserWithPosts" parameterType="int" resultMap="UserWithPostsResultMap">
SELECT u.id, u.name, u.age,
p.id AS post_id, p.user_id, p.content
FROM users u
LEFT JOIN posts p ON u.id = p.user_id
WHERE u.id = #{id}
</select>
</mapper>
  • resultMap を使うと、JOIN結果をエンティティにマッピングできる
  • <collection> によって、List<Post>User に格納できる

コントローラで利用する(1対多)

package com.example.demo.controller;

import com.example.demo.mapper.UserMapper;
import com.example.demo.entity.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class UserListController {

private final UserMapper userMapper;

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

@GetMapping("/user-detail")
public String detail(Model model) {
User user = userMapper.findUserWithPosts(1);
model.addAttribute("user", user);
return "user-detail";
}
}
  • findUserWithPosts(1) を呼ぶと、usersposts テーブルを結合した結果が返る
  • コントローラでは user.getPosts() で投稿リストにアクセスできる

👉 まとめ

  • 簡単なSQLはアノテーションで書ける
  • 実務ではSQLをXMLに分離し、Mapperインターフェースと対応付ける
  • resultMap<collection> を使うと、リレーションを持つデータも扱える

よくある質問

Q. アノテーション方式とXML方式はどちらを使えばよいですか?

A. 簡単なCRUDには アノテーション方式 が手軽だが、実務では XML方式 が一般的である。 XMLに分離することで、SQLを読みやすく管理でき、複雑なJOINや動的SQLも書きやすい。 本研修ではXML方式を中心に学ぶ。


Q. #{id}${id} の違いは何ですか?

A. #{id} はプリペアドステートメント(パラメータバインド)として扱われ、SQLインジェクションを防止する。 ${id} は文字列として直接埋め込まれるため、SQLインジェクションのリスクがある。 ユーザー入力値には必ず #{...} を使うこと。


Q. カラム名とフィールド名が異なる場合はどうすればよいですか?

A. XMLの resultMap<result> タグを使って対応付けを指定する。

<resultMap id="UserResultMap" type="com.example.demo.entity.User">
<id property="id" column="user_id"/> <!-- user_id カラム → id フィールド -->
<result property="name" column="user_name"/> <!-- user_name カラム → name フィールド -->
</resultMap>

または application.propertiesmybatis.configuration.map-underscore-to-camel-case=true を設定すると、 user_nameuserName のようにスネークケースからキャメルケースへ自動変換される。


本章のまとめ

  • MyBatisは O/Rマッパー のひとつで、SQLを自分で書けるのが特徴
  • application.properties でPostgreSQLへのDB接続を設定する
  • spring.sql.init.mode=always を設定することで、起動時に schema.sql / data.sql が自動実行される
  • schema.sqldata.sql でテーブルと初期データを準備できる
  • エンティティ(Javaクラス)を作り、テーブルの1行をオブジェクトとして扱える
  • MapperインターフェースとXMLマッパーファイルで、JavaメソッドとSQLを結びつける
  • @Mapper を付けると、MyBatisが自動で実装クラスを生成してくれる
  • コントローラでは、Mapperのメソッドを呼ぶだけでDBアクセスが可能になる
  • 次章では、リクエストをまたいでデータを保持するための セッションログインの仕組み を学ぶ

O/Rマッパーの説明として、最も適切なものを選べ。

正解

B. データベースのテーブル(リレーション)とJavaのオブジェクトを自動的に対応付けてデータアクセスを簡略化するツールのこと

解説

O/Rマッパー(Object-Relational Mapper)とは、データベースのリレーショナルデータ(テーブルの行)とJavaのオブジェクトを自動的に変換する仕組みである。

O/Rマッパーなし(JDBC直接使用):

  • SQLの実行、ResultSetの取得、Javaオブジェクトへの変換をすべて手動で記述
  • 冗長なコードが多い

O/Rマッパーあり(MyBatisなど):

  • SQLとJavaオブジェクトのマッピングを自動化
  • コード量が減り、開発効率が上がる

代表的なO/Rマッパー:MyBatis、Hibernate(JPA)

MyBatisの役割として、正しいものを選べ。

正解

D. SQLをそのまま記述しつつ、JavaオブジェクトとDBの行を自動変換するO/Rマッパー

解説

MyBatisはSQLマッパーフレームワークであり、以下の特徴を持つ。

  • SQLを直接記述: 開発者がSQLを完全にコントロールできる
  • 自動マッピング: SQLの結果(ResultSet)をJavaオブジェクトに自動変換する
  • アノテーション方式XMLマッパー方式 の2つの記述方法がある

JPA/Hibernateとの違い:

  • MyBatis: SQLを自分で書く → 複雑なSQLも書ける
  • JPA/Hibernate: SQLを自動生成する → シンプルなCRUDは楽だが複雑なSQLは苦手

MyBatisのアノテーション方式とXMLマッパー方式の違いとして、正しいものを選べ。

正解

A. アノテーション方式はJavaコードにSQLを直接書き、XML方式はSQLを別ファイルに分離して記述する

解説

アノテーション方式

  • Javaのインターフェースメソッドに @Select@Insert などでSQLを直接記述する
  • シンプルなSQLに向いている
  • SQLがJavaコードに混在するため、長いSQLは読みにくくなる

XML方式

  • SQLを別のXMLファイル(Mapperファイル)に記述する
  • 複雑なSQL(動的SQL、長いSQL)に向いている
  • SQLとJavaコードが分離されるため、保守性が高い

実際のプロジェクトでは両方を混在させることもある。

MyBatisの resultMap の役割として、最も適切なものを選べ。

正解

C. データベースの列名とJavaオブジェクトのフィールド名を明示的に対応付ける定義

解説

resultMap はデータベースのテーブルの列名とJavaオブジェクトのフィールド名を対応付ける定義である。

列名とフィールド名が一致していれば自動マッピングされるが、異なる場合や1対多リレーションがある場合は resultMap を使う。

<resultMap id="productResultMap" type="Product">
  <id column="product_id" property="productId"/>
  <result column="product_name" property="productName"/>
  <collection property="reviews" ofType="Review">
    <id column="review_id" property="reviewId"/>
  </collection>
</resultMap>

商品データへのアクセスを担当するマッパーインターフェースを作成せよ。

public interface ProductMapper { }

解答例
@Mapper public interface ProductMapper { }
解説

@Mapper アノテーションを付けることでMyBatisがそのインターフェースをマッパーとして認識し、SQLを実行できるようにする。

MyBatisはインターフェースの実装クラスを自動生成するため、自分で実装クラスを書く必要はない。

@Mapper が付いたインターフェースはSpringのDIコンテナにも登録されるため、@Autowired で注入できる。

products テーブルの全件を取得するマッパーメソッドを完成させよ。

List<Product> findAll();

解答例
@Select("SELECT * FROM products") List<Product> findAll();
解説

@Select アノテーションにSQL文を記述することで、SELECT文を実行するメソッドを定義できる。

MyBatisはメソッドの戻り値の型に合わせてSQLの結果を自動変換する。

パラメータを使う場合:

@Select("SELECT * FROM products WHERE id = #{id}")
Product findById(Long id);

#{変数名} の形式でメソッドの引数をSQLに渡すことができる。

ProductMapper インターフェースの findAll() メソッドに対応するXMLマッパーを完成させよ。

<mapper
> <select
> SELECT * FROM products </select> </mapper>

解答例
<?xml version="1.0"?> <!DOCTYPE mapper ...> <mapper namespace="com.example.mapper.ProductMapper"> <select id="findAll" resultType="com.example.model.Product"> SELECT * FROM products </select> </mapper>
解説

XMLマッパーの基本構造:

  • namespace: 対応するMapperインターフェースの完全修飾名
  • <select id="メソッド名">: Mapperインターフェースのメソッドと対応するSELECT文
  • resultType: 結果をマッピングするJavaクラスの完全修飾名

XMLマッパーは src/main/resources 配下にMapperインターフェースと同じパッケージ構造で配置する。

products テーブルを作成するschema.sqlを完成させよ。

products ( id BIGINT
AUTO_INCREMENT, name VARCHAR(100) NOT NULL );

解答例
CREATE TABLE products ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL );
解説

src/main/resources/schema.sql に記述したSQLは、Spring Boot起動時に自動実行される(H2などの組み込みDBを使う場合)。

テーブル定義に使う主な制約:

  • PRIMARY KEY: 主キー
  • AUTO_INCREMENT: 自動採番
  • NOT NULL: NULL禁止
  • VARCHAR(n): 可変長文字列

application.propertiesspring.sql.init.mode=always を設定すると常に実行される。

products テーブルに初期データを挿入するdata.sqlを完成させよ。

products (name)
('商品A');

解答例
INSERT INTO products (name) VALUES ('商品A');
解説

src/main/resources/data.sql に記述したSQLは、schema.sqlの実行後に自動実行される。

テーブルの初期データ(マスターデータや開発用テストデータ)の投入に使う。

複数レコードを一度に挿入する場合:

INSERT INTO products (name) VALUES ('商品A'), ('商品B'), ('商品C');

product_name 列を productName フィールドにマッピングするXMLマッパーを完成させよ。

<resultMap id="productResultMap" type="Product"> <id column="product_id" property="productId"/> <result
/> </resultMap> <select id="findAll"
> SELECT product_id, product_name FROM products </select>

解答例
<resultMap id="productResultMap" type="Product"><id column="product_id" property="productId"/><result column="product_name" property="productName"/></resultMap>
解説

resultMap はテーブルの列名とJavaオブジェクトのフィールド名が異なる場合に使う。

  • <id>: 主キーのマッピング
  • <result>: 通常の列のマッピング
  • column: データベースの列名(スネークケース)
  • property: Javaオブジェクトのフィールド名(キャメルケース)

列名とフィールド名が一致している場合は自動マッピングされるが、明示的に resultMap で定義する方が安全である。

Product が複数の Review を持つ1対多リレーションのresultMapを完成させよ。

<resultMap id="productResultMap" type="Product"> <id column="id" property="id"/> <result column="name" property="name"/> <
> <id column="review_id" property="reviewId"/> <result column="content" property="content"/> </
> </resultMap>

解答例
<collection property="reviews" ofType="Review"><id column="review_id" property="reviewId"/></collection>
解説

1対多リレーション(1つの商品に複数のレビューがある場合)は <collection> タグを使う。

<resultMap id="productResultMap" type="Product">
  <id column="id" property="id"/>
  <result column="name" property="name"/>
  <collection property="reviews" ofType="Review">
    <id column="review_id" property="reviewId"/>
    <result column="content" property="content"/>
  </collection>
</resultMap>

property: Javaオブジェクトのコレクション型フィールド名 ofType: コレクションの要素の型

H2インメモリデータベースへの接続設定を完成させよ。

=jdbc:h2:mem:testdb
=sa spring.datasource.password=

解答例
spring.datasource.url=jdbc:h2:mem:testdb spring.datasource.username=sa spring.datasource.password=
解説

application.properties でデータベース接続情報を設定する。

H2(インメモリDB)の例:

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true

MySQLの例:

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