第6章で作ったデータのクラスを使って、データベースを操作する Mapper を作ります。Mapper は、Java から呼び出すメソッドを宣言したインターフェースと、そのメソッドで実行する SQL を書いた XML ファイルの組み合わせです。この章で、社員の一覧・1件の取得・登録・変更・削除の SQL をすべて書きます。
【知識メモ】この章は MyBatis コースです
第6章と第7章は、JPA コースと MyBatis コースに分かれています。JPA コースで進めている場合は、第7章 JPA 入門② Repository を読んでください。
この章で学ぶこと
- Mapper インターフェースの作り方
- Mapper の XML ファイルに SQL を書く方法(検索・登録・変更・削除)
- 結合(JOIN)した結果を、社員と部署のオブジェクトに入れる方法
- JUnit テストで Mapper の動きを確かめる方法(任意)
7.1 Mapper を作る
データのクラスができたので、次はデータベースを操作する Mapper(マッパー)を作ります。第5章では、データベースを操作する部品を「Repository」と呼んで説明しました。MyBatis コースでは、その部品が Mapper です。Controller はこの Mapper を使ってデータを取り出したり保存したりします。
mapper パッケージを作る
-
起点となる
com.example.employeeappパッケージを右クリックし、新規 → パッケージ を選びます。 -
「名前」には、最初は右クリックした親のパッケージ名
com.example.employeeappが入っています。末尾に.mapperを付け足してcom.example.employeeapp.mapperにし、完了 をクリックします。図7-1 最初は親のパッケージ名が入っている(上)ので、末尾に .mapper を付け足す(下)
DepartmentMapper を作る
Mapper は、クラスではなく インターフェース として作ります。
-
com.example.employeeapp.mapperパッケージを右クリックし、新規 → インターフェース を選びます。図7-2 mapper パッケージを右クリック → 新規 → インターフェース
-
「パッケージ」に
com.example.employeeapp.mapperが自動で入っていることを確認し、「名前」にDepartmentMapperと入力して 完了 をクリックします。図7-3 パッケージを確認し、名前に DepartmentMapper と入力
-
次のコードを補完を使って入力し、保存します。
package com.example.employeeapp.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.employeeapp.entity.Department;
@Mapper
public interface DepartmentMapper {
/** 全部署を部署IDの昇順で取得する */
List<Department> findAllByOrderByDeptIdAsc();
/** 部署IDで部署を1件取得する(見つからないときは null) */
Department findByDeptId(Integer deptId);
}
図7-4 入力した DepartmentMapper
【ご注意】@Mapper は org.apache.ibatis.annotations.Mapper を選ぶ
@Mapp まで打って Ctrl + Space で補完するときは、候補から Mapper - org.apache.ibatis.annotations を選びます。MyBatis のパッケージ名は、古い名前の「iBATIS」のまま org.apache.ibatis になっています。
EmployeeMapper を作る
同じ手順で、com.example.employeeapp.mapper パッケージを右クリックして EmployeeMapper インターフェースを作ります。社員は、一覧・1件の取得に加えて、登録・変更・削除のメソッドも宣言します(登録は第11章、変更は第12章、削除は第9章で使います)。
図7-5 パッケージを確認し、名前に EmployeeMapper と入力
package com.example.employeeapp.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.employeeapp.entity.Employee;
@Mapper
public interface EmployeeMapper {
/** 全社員を社員IDの昇順で取得する(部署名も一緒に取得する) */
List<Employee> findAllByOrderByEmpIdAsc();
/** 社員IDで社員を1件取得する(見つからないときは null) */
Employee findByEmpId(Integer empId);
/** 社員を登録する(社員IDは SEQ_EMP から払い出して employee に入れる) */
void insert(Employee employee);
/** 社員の情報を変更する(変更する行は employee の社員IDで決める) */
void update(Employee employee);
/** 社員IDを指定して社員を削除する */
void deleteById(Integer empId);
}
メソッドの名前は、JPA コースの Repository と同じにしています。そのため、第8章以降の Controller のコードは、Repository を Mapper に読み替えるだけで、ほとんど同じになります。
Mapper のインターフェースには、メソッドの宣言だけを書きます。中身の処理(実行する SQL)は、次の 7.2 で XML ファイルに書きます。
7.2 SQL を書く
Mapper のメソッドで実行する SQL を、XML ファイルに書きます。XML ファイルは、src/main/resources の中に mapper フォルダーを作って、その中に置きます。第4章で application.properties に書いた次の設定で、MyBatis はこのフォルダーの XML ファイルを読み込みます。
mybatis.mapper-locations=classpath:mapper/*.xml
mapper フォルダーを作る
-
src/main/resourcesを右クリックし、新規 → フォルダー を選びます。図7-6 src/main/resources を右クリック → 新規 → フォルダー
-
「親フォルダー」に
employee-app/src/main/resourcesが入っていることを確認し、「フォルダー名」にmapperと入力して 完了 をクリックします。図7-7 親フォルダーを確認し、フォルダー名に mapper と入力
【ご注意】Java のパッケージの中に作らない
mapper フォルダーは、src/main/java の mapper パッケージとは別のものです。XML ファイルは、必ず src/main/resources/mapper に作ってください。src/main/java の中に置くと、MyBatis に読み込まれません。
DepartmentMapper.xml を作る
-
作成した
mapperフォルダーを右クリックし、新規 → ファイル を選びます。図7-8 mapper フォルダーを右クリック → 新規 → ファイル
-
「親フォルダー」に
employee-app/src/main/resources/mapperが入っていることを確認し、「ファイル名」にDepartmentMapper.xmlと入力して 完了 をクリックします。図7-9 親フォルダーを確認し、ファイル名に DepartmentMapper.xml と入力
-
エディターは「デザイン」の画面で開きます。エディターの下にある ソース タブをクリックして、XML を直接書く画面に切り替えます。
図7-10 「デザイン」の画面で開くので、下の「ソース」タブをクリックする
-
次のように入力して保存します。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.employeeapp.mapper.DepartmentMapper">
<!-- 全部署を部署IDの昇順で取得する -->
<select id="findAllByOrderByDeptIdAsc" resultType="com.example.employeeapp.entity.Department">
SELECT DEPT_ID, DEPT_NAME
FROM DEPARTMENT
ORDER BY DEPT_ID
</select>
<!-- 部署IDで部署を1件取得する -->
<select id="findByDeptId" resultType="com.example.employeeapp.entity.Department">
SELECT DEPT_ID, DEPT_NAME
FROM DEPARTMENT
WHERE DEPT_ID = #{deptId}
</select>
</mapper>
図7-11 入力した DepartmentMapper.xml(「ソース」の画面。右にはみ出た部分は横にスクロールすると見える)
【知識メモ】最初の2行は毎回同じ
1行目の <?xml …?> と2行目の <!DOCTYPE …> は、「このファイルは MyBatis の Mapper の XML である」ことを表す決まった書き方で、どの Mapper の XML でも同じです。つづりを間違えると読み込みでエラーになるので、よく見て入力してください。
EmployeeMapper.xml を作る
同じ手順で、mapper フォルダーに EmployeeMapper.xml を作り、「ソース」タブに切り替えて、次のように入力して保存します。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.employeeapp.mapper.EmployeeMapper">
<!-- 社員と部署を結合した結果を、Employee と、その中の department(Department)に入れる -->
<resultMap id="employeeWithDepartment" type="com.example.employeeapp.entity.Employee">
<id property="empId" column="EMP_ID"/>
<result property="name" column="NAME"/>
<result property="gender" column="GENDER"/>
<result property="age" column="AGE"/>
<result property="birthDate" column="BIRTH_DATE"/>
<result property="address" column="ADDRESS"/>
<association property="department" javaType="com.example.employeeapp.entity.Department">
<id property="deptId" column="DEPT_ID"/>
<result property="deptName" column="DEPT_NAME"/>
</association>
</resultMap>
<!-- 全社員を社員IDの昇順で取得する(部署名も一緒に取得する) -->
<select id="findAllByOrderByEmpIdAsc" resultMap="employeeWithDepartment">
SELECT e.EMP_ID, e.NAME, e.GENDER, e.AGE, e.BIRTH_DATE, e.ADDRESS, d.DEPT_ID, d.DEPT_NAME
FROM EMPLOYEE e
INNER JOIN DEPARTMENT d ON e.DEPT_ID = d.DEPT_ID
ORDER BY e.EMP_ID
</select>
<!-- 社員IDで社員を1件取得する -->
<select id="findByEmpId" resultMap="employeeWithDepartment">
SELECT e.EMP_ID, e.NAME, e.GENDER, e.AGE, e.BIRTH_DATE, e.ADDRESS, d.DEPT_ID, d.DEPT_NAME
FROM EMPLOYEE e
INNER JOIN DEPARTMENT d ON e.DEPT_ID = d.DEPT_ID
WHERE e.EMP_ID = #{empId}
</select>
<!-- 社員を登録する。登録の前に SEQ_EMP から番号を取り出し、employee の empId に入れてから INSERT する -->
<insert id="insert">
<selectKey keyProperty="empId" resultType="java.lang.Integer" order="BEFORE">
SELECT SEQ_EMP.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO EMPLOYEE (EMP_ID, NAME, GENDER, AGE, BIRTH_DATE, DEPT_ID, ADDRESS)
VALUES (#{empId}, #{name}, #{gender}, #{age}, #{birthDate}, #{department.deptId}, #{address})
</insert>
<!-- 社員の情報を変更する -->
<update id="update">
UPDATE EMPLOYEE
SET NAME = #{name},
GENDER = #{gender},
AGE = #{age},
BIRTH_DATE = #{birthDate},
DEPT_ID = #{department.deptId},
ADDRESS = #{address}
WHERE EMP_ID = #{empId}
</update>
<!-- 社員IDを指定して社員を削除する -->
<delete id="deleteById">
DELETE FROM EMPLOYEE
WHERE EMP_ID = #{empId}
</delete>
</mapper>
【ご注意】SQL の最後にセミコロン(;)を付けない
SQL Developer では SQL の最後に ; を付けましたが、Mapper の XML に書く SQL には付けません。付けると、実行したときに Oracle のエラー(ORA-00933: SQLコマンドが正しく終了されていません)になります。
ここまでで、プロジェクトの構成は次のようになっています。
図7-12 entity と mapper のパッケージ、mapper フォルダーに、それぞれ2つずつファイルができた
src/main/java
└─ com.example.employeeapp
├─ EmployeeAppApplication.java
├─ component
│ └─ GreetingMessage.java (第5章)
├─ controller
│ └─ HelloController.java (第4・5章)
├─ entity
│ ├─ Department.java ← 第6章で作成
│ └─ Employee.java ← 第6章で作成
└─ mapper
├─ DepartmentMapper.java ← この章で作成
└─ EmployeeMapper.java ← この章で作成
src/main/resources
├─ mapper
│ ├─ DepartmentMapper.xml ← この章で作成
│ └─ EmployeeMapper.xml ← この章で作成
├─ static
├─ templates
└─ application.properties
7.3 Mapper の解説
インターフェースと XML の結び付け
Mapper のインターフェースには、メソッドの中身がありません。それでも動くのは、アプリの起動時に MyBatis が、インターフェースを実装したクラスを自動で作ってくれる からです。作られたクラスのメソッドを呼び出すと、XML に書いた SQL が実行されます。作られたオブジェクトは Bean として DI コンテナに登録されるので、第5章で学んだとおり、Controller のフィールドに @Autowired を付けて受け取れます。
インターフェースのメソッドと XML の SQL は、次の2つの名前で結び付きます。
| XML に書くもの | 合わせる相手 | EmployeeMapper の例 |
|---|---|---|
<mapper> の namespace
|
インターフェースの名前(パッケージ名から書く) | com.example.employeeapp.mapper.EmployeeMapper |
<select> などの id
|
メソッドの名前 | findByEmpId |
図7-13 インターフェースと XML は名前で結び付く
名前が1文字でも違うと、メソッドを呼び出したときにエラーになります(7.5 よくあるエラー)。
【知識メモ】@Mapper を付ける理由
@Mapper は、「このインターフェースは MyBatis の Mapper である」ことを表すアノテーションです。MyBatis Spring Boot Starter は、起動時に @Mapper の付いたインターフェースを探して実装を作ります。付け忘れると、Controller で @Autowired したときに「Bean が見つからない」というエラーになります。
SQL の種類と XML の要素
SQL の種類ごとに、囲む要素を使い分けます。
| 要素 | 書く SQL | この章のメソッド | 使う章 |
|---|---|---|---|
<select> |
SELECT |
findAllByOrderByEmpIdAsc・findByEmpId・findAllByOrderByDeptIdAsc・findByDeptId
|
第8章から (部署の2つは第10章から) |
<insert> |
INSERT | insert |
第11章 |
<update> |
UPDATE | update |
第12章 |
<delete> |
DELETE | deleteById |
第9章 |
#{} で引数の値を渡す
SQL の中の #{…} の部分には、メソッドの引数の値が入ります。
| メソッドの引数 | 書き方 | 入る値 |
|---|---|---|
findByEmpId(Integer empId) のように、数値や文字列を1つ受け取る |
#{empId} |
引数の値(findByEmpId(1) なら 1) |
insert(Employee employee) のように、オブジェクトを受け取る |
#{name} |
そのオブジェクトのフィールドの値(employee.getName() の値) |
| 同上(フィールドの中のフィールド) | #{department.deptId} |
employee.getDepartment().getDeptId() の値 |
#{…} の部分は、SQL を実行するときに ? に置き換えられ、値は SQL とは別に Oracle に渡されます。そのため、値の中に '(シングルクォーテーション)などが含まれていても、SQL の文が壊れることはありません。文字列の値でも、'#{name}' のように ' で囲む必要はありません。
社員の部署は Department 型の department フィールドに入っているので(第6章)、登録・変更の SQL では #{department.deptId} と書いて部署IDを取り出し、DEPT_ID 列に入れています。
resultType:結果をそのままクラスに入れる
DepartmentMapper.xml の <select> には、resultType に結果を入れるクラスをパッケージ名から書きました。
<select id="findByDeptId" resultType="com.example.employeeapp.entity.Department">
MyBatis は、結果の1行ごとに Department オブジェクトを作り、第6章で説明したとおり、列名に対応するフィールドに値を入れます(DEPT_ID → deptId)。
戻り値の型が List<Department> のメソッドでは、全行分のオブジェクトが入った List が返ります。戻り値の型が Department のメソッドでは、1件のオブジェクトが返り、該当する行が無ければ null が返ります。
resultMap と association:結合した結果を入れる
社員の SELECT では、部署テーブルと結合して部署名も取り出しています。結果の列のうち、DEPT_ID と DEPT_NAME は Employee のフィールドではなく、その中の department フィールドの Department オブジェクトに入れたいものです。この入れ方は列名との対応だけでは決まらないので、<resultMap> に書きます。
図7-14 resultMap で、部署の列を department フィールドの Department オブジェクトに入れる
| 書き方 | 意味 |
|---|---|
<resultMap id="employeeWithDepartment" type="…Employee"> |
結果の入れ方に employeeWithDepartment という名前を付ける。結果は Employee オブジェクトに入れる |
<id property="empId" column="EMP_ID"/> |
主キーの列。EMP_ID 列の値を empId フィールドに入れる |
<result property="name" column="NAME"/> |
主キー以外の列。NAME 列の値を name フィールドに入れる |
<association property="department" javaType="…Department"> |
Department オブジェクトを作って department フィールドに入れる。中に書いた <id>・<result> の列は、その Department オブジェクトに入る |
<select … resultMap="employeeWithDepartment"> |
この SELECT の結果を、employeeWithDepartment の入れ方で入れる(resultType の代わりに書く) |
resultMap を使う SELECT が2つ(一覧と1件)あるので、<resultMap> は1回だけ書いて、名前で使い回しています。
selectKey:シーケンスの番号を受け取って登録する
社員の登録では、第3章で作ったシーケンス SEQ_EMP から社員IDを払い出します。
<insert id="insert">
<selectKey keyProperty="empId" resultType="java.lang.Integer" order="BEFORE">
SELECT SEQ_EMP.NEXTVAL FROM DUAL
</selectKey>
INSERT INTO EMPLOYEE (EMP_ID, NAME, …)
VALUES (#{empId}, #{name}, …)
</insert>
-
order="BEFORE"なので、INSERT の前に<selectKey>の SQL が実行され、SEQ_EMPの次の番号が取り出されます。 - 取り出した番号は、
keyProperty="empId"で指定した、引数のemployeeのempIdフィールドに入ります。 - 続いて INSERT が実行され、
#{empId}にその番号が入ります。
登録が終わった後、insert を呼び出した側でも、employee.getEmpId() で払い出された社員IDを取り出せます。
【知識メモ】JPA コースの save() との違い
JPA コースでは、save() の1つのメソッドが、社員IDが null なら登録(INSERT)、値が入っていれば変更(UPDATE)を行います。MyBatis では、実行する SQL を自分で書くので、登録の insert() と変更の update() を別のメソッドにしています。
7.4 任意:JUnit テストで Mapper を呼び出す
この節は任意です。Mapper が正しく動くかどうかは、第8章で画面を作れば確かめられます。初めて学ぶ人は、この節を飛ばして 7.6 まとめと確認問題 に進んでかまいません。あとで余裕ができたときに、戻って試してください。
まだ画面を作っていないので、Mapper が正しく動くかどうかを JUnit テスト で確かめます。JUnit は Java のプログラムをテストするための仕組みで、Spring Boot のプロジェクトには最初から組み込まれています。
テストクラスを作る
-
パッケージ・エクスプローラーで
src/test/javaを開き、その中の起点となるcom.example.employeeappパッケージを右クリックします。
(src/main/javaではないので注意してください) -
新規 → クラス を選びます。
図7-15 src/test/java の com.example.employeeapp を右クリック → 新規 → クラス
-
「ソース・フォルダー」が
employee-app/src/test/java、「パッケージ」がcom.example.employeeappになっていることを確認し、「名前」にMapperCheckTestと入力して 完了 をクリックします。図7-16 ソース・フォルダーが src/test/java であることを確認して、名前に MapperCheckTest と入力
-
次のコードを補完を使って入力し、保存します。
package com.example.employeeapp;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import com.example.employeeapp.entity.Department;
import com.example.employeeapp.entity.Employee;
import com.example.employeeapp.mapper.DepartmentMapper;
import com.example.employeeapp.mapper.EmployeeMapper;
@SpringBootTest
class MapperCheckTest {
@Autowired
private EmployeeMapper employeeMapper;
@Autowired
private DepartmentMapper departmentMapper;
/** 全社員を取得できること */
@Test
void findAllEmployees() {
List<Employee> list = employeeMapper.findAllByOrderByEmpIdAsc();
for (Employee employee : list) {
System.out.println(employee.getEmpId() + " " + employee.getName() + " "
+ employee.getDepartment().getDeptName());
}
assertEquals(3, list.size());
}
/** 社員IDを指定して社員を取得できること */
@Test
void findEmployee() {
Employee employee = employeeMapper.findByEmpId(1);
assertEquals("雷禅 海皇", employee.getName());
}
/** 全部署を取得できること */
@Test
void findAllDepartments() {
List<Department> list = departmentMapper.findAllByOrderByDeptIdAsc();
assertEquals(3, list.size());
}
}
登録・変更・削除のメソッドは、データを書き換えてしまうので、このテストでは呼び出しません。画面から使う章(第9・11・12章)で動きを確かめます。
テストのコードの解説
| コード | 意味 |
|---|---|
@SpringBootTest |
テストを実行する前に Spring Boot のアプリを起動し、DI コンテナを用意する。これにより Mapper が使えるようになる |
@Autowired |
DI コンテナから Bean を受け取ってフィールドに入れる |
@Test |
このメソッドがテストであることを表す。1つのメソッドが1つのテストになる |
assertEquals(期待値, 実際の値) |
2つの値が等しいかを確かめる。等しくなければテストは失敗になる |
テストを実行する
-
パッケージ・エクスプローラーで
MapperCheckTest.javaを右クリックします。 -
実行 → JUnit テスト を選びます。
図7-17 MapperCheckTest.java を右クリック → 実行 → JUnit テスト
-
Spring Boot が起動し、テストが実行されます。終わると「JUnit」ビューが表示されます。
図7-18 テストがすべて成功した(赤枠:JUnit ビューの緑のバーと「実行: 3/3 エラー: 0 失敗: 0」)
JUnit ビューのバーの色で、結果がわかります。
| バーの色 | 意味 |
|---|---|
| 緑 | すべてのテストが成功した。「実行: 3/3 エラー: 0 失敗: 0」と表示される |
| 赤 | 失敗したテストがある。失敗したメソッドをクリックすると、下の「障害トレース」に原因が表示される |
コンソールの出力を確認する
「コンソール」ビューを開くと、次のような出力が見つかります。まず、MyBatis が実行した SQL です(第4章で logging.level.com.example.employeeapp.mapper=debug を設定したので表示されます)。
c.e.e.m.E.findAllByOrderByEmpIdAsc : ==> Preparing: SELECT e.EMP_ID, e.NAME, e.GENDER, e.AGE, e.BIRTH_DATE, e.ADDRESS, d.DEPT_ID, d.DEPT_NAME FROM EMPLOYEE e INNER JOIN DEPARTMENT d ON e.DEPT_ID = d.DEPT_ID ORDER BY e.EMP_ID
c.e.e.m.E.findAllByOrderByEmpIdAsc : ==> Parameters:
c.e.e.m.E.findAllByOrderByEmpIdAsc : <== Total: 3
c.e.e.mapper.EmployeeMapper.findByEmpId : ==> Preparing: SELECT e.EMP_ID, e.NAME, e.GENDER, e.AGE, e.BIRTH_DATE, e.ADDRESS, d.DEPT_ID, d.DEPT_NAME FROM EMPLOYEE e INNER JOIN DEPARTMENT d ON e.DEPT_ID = d.DEPT_ID WHERE e.EMP_ID = ?
c.e.e.mapper.EmployeeMapper.findByEmpId : ==> Parameters: 1(Integer)
c.e.e.mapper.EmployeeMapper.findByEmpId : <== Total: 1
| 行 | 意味 |
|---|---|
==> Preparing: |
実行した SQL。XML に書いた SQL がそのまま実行されている。#{empId} の部分は ? になっている |
==> Parameters: |
? に入れた値と型。1(Integer) は、findByEmpId(1) の引数の 1 |
<== Total: |
結果の件数 |
さらに、テストの中で System.out.println した内容も表示されます。
1 雷禅 海皇 営業部
2 院照 心愛 総務部
3 林檎 星飛 開発部
社員の氏名と一緒に部署名も表示されていれば、resultMap の association による部署の入れ方も正しく動いています。
【ご注意】テストの実行には Oracle が必要です
@SpringBootTest のテストは、実際にデータベースに接続します。Oracle Database が起動していないとテストは失敗します。なお、Eclipse でアプリ(第4章の Hello World など)を起動したままでも、テストは別に実行できます。
7.5 よくあるエラー
7.4 のテストが赤になったときや、第8章以降でアプリの起動や画面の表示に失敗したときは、JUnit ビューの「障害トレース」やコンソールに表示されたメッセージを確認し、次の表を参考に直してください。データのクラスの書き間違いによるものは、第6章 6.4 にまとめています。
| メッセージ(一部) | 主な原因 | 対処 |
|---|---|---|
Invalid bound statement (not found): com.example.employeeapp.mapper.EmployeeMapper.findByEmpId |
メソッドに対応する SQL が見つからない | XML の namespace がインターフェースの名前と、id がメソッドの名前と合っているかを確認する。XML ファイルが src/main/resources/mapper にあるかも確認する |
No qualifying bean of type 'com.example.employeeapp.mapper.EmployeeMapper' |
Mapper の実装が作られていない | インターフェースに @Mapper が付いているかを確認する |
There is no getter for property named 'xxx' in 'class com.example.employeeapp.entity.Employee' |
#{…} の中の名前が、データのクラスのフィールド名と違う |
XML の #{…} の中のつづり(empId・birthDate・department.deptId など)を確認する |
Error parsing Mapper XML |
XML の書き方が間違っている | メッセージの続きに書かれたファイル名と行を見て、タグの閉じ忘れ(</select> など)や、" の付け忘れを確認する |
ORA-00942: 表またはビューが存在しません |
SQL のテーブル名が間違っている | XML の SQL のテーブル名(EMPLOYEE・DEPARTMENT)のつづりを確認する |
ORA-00904: "…": 無効な識別子です |
SQL の列名が間違っている | メッセージの "…" に書かれた列名のつづりを確認する |
ORA-00933: SQLコマンドが正しく終了されていません |
SQL の最後に ; が付いている |
XML の SQL から ; を消す |
NullPointerException(部署名を取り出すところ) |
社員の department が null のまま |
社員の <select> に resultMap="employeeWithDepartment" が書いてあるか(resultType になっていないか)、<association> の property が department になっているかを確認する |
expected: <3> but was: <0> |
データが0件になっている |
第3章でサンプルデータを入れたあと COMMIT したかを確認する。COMMIT していないデータは、他の接続(アプリ)からは見えない |
Failed to load ApplicationContext |
Spring Boot の起動に失敗した | メッセージの下の方にある Caused by: の行を読むと、本当の原因(上の表のエラーなど)が書かれている |
その他のエラーは、付録B「部品(Bean)やクラスが見つからない」 も参考にしてください。
【便利な使い方】エラーメッセージは下から読む
Java のエラーメッセージは長く表示されますが、本当の原因は最後の方の Caused by: に書かれていることが多いです。慌てずに、最後の Caused by: を探しましょう。エラーメッセージの読み方は、付録B「コンソールのエラーは『一番下の Caused by:』を見る」 にもまとめています。
7.6 まとめと確認問題
まとめ
-
Mapper は、メソッドを宣言したインターフェース(
@Mapper)と、SQL を書いた XML ファイルの組み合わせ。実装のクラスは MyBatis が自動で作る - XML の
namespaceはインターフェースの名前、<select>などのidはメソッドの名前と合わせる - SQL の中の
#{…}には、メソッドの引数の値(オブジェクトなら、そのフィールドの値)が入る - 結果をそのままクラスに入れるときは
resultType、結合した結果を社員と部署のオブジェクトに分けて入れるときはresultMapとassociationを使う - 登録の前に
selectKeyでシーケンスの番号を取り出し、社員IDにして INSERT する - (任意)
@SpringBootTestを付けた JUnit テストで、画面がなくても Mapper の動きを確かめられる
確認問題
問1 EmployeeMapper はインターフェースで、メソッドの中身を書いていません。それでも findByEmpId を呼び出すと SQL が実行されるのはなぜですか。
解答を見る
MyBatis が、起動時に @Mapper の付いたインターフェースを見つけ、実装クラスを自動で作って Bean として登録するからです。メソッドを呼び出すと、XML のうち namespace がインターフェースの名前、id がメソッドの名前と同じ SQL が実行されます。
問2 EmployeeMapper に、住所(ADDRESS 列)で社員を検索し、複数件を返すメソッド findByAddress を追加するとします。インターフェースのメソッドの宣言と、XML の SQL を書いてください(部署名も一緒に取り出します)。
解答を見る
インターフェース:List<Employee> findByAddress(String address);
XML:
<select id="findByAddress" resultMap="employeeWithDepartment">
SELECT e.EMP_ID, e.NAME, e.GENDER, e.AGE, e.BIRTH_DATE, e.ADDRESS, d.DEPT_ID, d.DEPT_NAME
FROM EMPLOYEE e
INNER JOIN DEPARTMENT d ON e.DEPT_ID = d.DEPT_ID
WHERE e.ADDRESS = #{address}
ORDER BY e.EMP_ID
</select>
複数件が返る可能性があるので、戻り値は List<Employee> にします。部署名も入れるので、resultMap には一覧と同じ employeeWithDepartment を使います。
問3 社員を登録する insert の SQL で、#{empId} に入る社員IDは、どこから来ますか。
解答を見る
<selectKey> の SQL(SELECT SEQ_EMP.NEXTVAL FROM DUAL)で、シーケンス SEQ_EMP から取り出した番号です。order="BEFORE" なので INSERT の前に実行され、取り出した番号は keyProperty="empId" により employee の empId フィールドに入ります。

















