MyBatisによるDBアクセス
この章では、Spring Bootでデータベースにアクセスする方法として MyBatis を学ぶ。 Webアプリケーションはデータを保存・取得するためにDBとのやりとりが不可欠であり、 MyBatisを使うことで JavaのクラスとSQLを結びつけた安全で整理されたDBアクセス を実現できる。
学習のゴール
- MyBatisが「O/Rマッパー」であることを理解できる
application.propertiesによるDB接続設定を行えるschema.sqlとdata.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 に記述する。
この章では 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が自動的に読み込む という意味。
DB接続設定が間違っていると、アプリ起動時にエラーが発生する。よくあるミス:
spring.datasource.urlのホスト名・ポート・DB名が間違っているspring.datasource.username/passwordがPostgreSQLの設定と一致していないspring.sql.init.mode=alwaysが抜けていて初期化SQLが実行されないmybatis.mapper-locationsのパスがUserMapper.xmlの実際の配置場所と一致していない
エラーメッセージに Connection refused や FATAL: password authentication failed が出た場合は、接続情報を見直す。
👉 新人研修では「この設定は先輩が用意したものをコピペすればOK」と考えてよい。 大切なのは、 このファイルにDB接続やMyBatisの設定を書く ということ。
schema.sql と data.sql
Spring Bootでは、resources フォルダに schema.sql と data.sql を置くと、起動時に自動実行される。
schema.sql: テーブル定義を記述するdata.sql: 初期データを挿入する
例:
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100),
age INT
);
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 クラスの id、name、age フィールドとして表現する。
これにより、SQLの結果をそのままJavaのオブジェクトとして扱えるようになる。
package com.example.demo.entity;public class User {private int id;private String name;private int age;// getter / setterpublic 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(最も簡単な方法)
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;@Mapperpublic 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と対応させる)
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 を忘れると、MyBatisはそのインターフェースを認識せず、
起動時に No qualifying bean of type 'UserMapper' などのエラーが発生する。
Mapperインターフェースには必ず @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 には @Mapper を付けたインターフェースの 完全修飾クラス名 を指定する必要がある。
パッケージ名を間違えると、MyBatisがMapperインターフェースとXMLを対応付けられず、
Invalid bound statement (not found) というエラーが発生する。
👉 これにより、Java側の UserMapper.findAll() を呼ぶとSQLが実行され、結果が User のリストとして返る。
コントローラで利用する
作成した UserMapper インターフェースをコントローラから呼び出してみよう。
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;@Controllerpublic 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マッパーファイル
<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)を呼ぶと、usersとpostsテーブルを結合した結果が返る- コントローラでは
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.properties に mybatis.configuration.map-underscore-to-camel-case=true を設定すると、
user_name → userName のようにスネークケースからキャメルケースへ自動変換される。
本章のまとめ
- MyBatisは O/Rマッパー のひとつで、SQLを自分で書けるのが特徴
application.propertiesでPostgreSQLへのDB接続を設定するspring.sql.init.mode=alwaysを設定することで、起動時にschema.sql/data.sqlが自動実行されるschema.sqlとdata.sqlでテーブルと初期データを準備できる- エンティティ(Javaクラス)を作り、テーブルの1行をオブジェクトとして扱える
- MapperインターフェースとXMLマッパーファイルで、JavaメソッドとSQLを結びつける
@Mapperを付けると、MyBatisが自動で実装クラスを生成してくれる- コントローラでは、Mapperのメソッドを呼ぶだけでDBアクセスが可能になる
- 次章では、リクエストをまたいでデータを保持するための セッション と ログインの仕組み を学ぶ