0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Spring入門】第7章 MyBatis 入門② Mapper と SQL

0
Posted at

第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 パッケージを作る

  1. 起点となる com.example.employeeapp パッケージを右クリックし、新規 → パッケージ を選びます。

  2. 「名前」には、最初は右クリックした親のパッケージ名 com.example.employeeapp が入っています。末尾に .mapper を付け足して com.example.employeeapp.mapper にし、完了 をクリックします。

    パッケージ名の末尾に .mapper を付け足す

    図7-1 最初は親のパッケージ名が入っている(上)ので、末尾に .mapper を付け足す(下)

DepartmentMapper を作る

Mapper は、クラスではなく インターフェース として作ります。

  1. com.example.employeeapp.mapper パッケージを右クリックし、新規 → インターフェース を選びます。

    mapper パッケージを右クリックして新規→インターフェース

    図7-2 mapper パッケージを右クリック → 新規 → インターフェース

  2. 「パッケージ」に com.example.employeeapp.mapper が自動で入っていることを確認し、「名前」に DepartmentMapper と入力して 完了 をクリックします。

    新規 Java インターフェース(DepartmentMapper)

    図7-3 パッケージを確認し、名前に DepartmentMapper と入力

  3. 次のコードを補完を使って入力し、保存します。

src/main/java/com/example/employeeapp/mapper/DepartmentMapper.java
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);

}

DepartmentMapper のエディター

図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章で使います)。

新規 Java インターフェース(EmployeeMapper)

図7-5 パッケージを確認し、名前に EmployeeMapper と入力

src/main/java/com/example/employeeapp/mapper/EmployeeMapper.java
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 ファイルを読み込みます。

application.properties(第4章で書いたもの。抜粋)
mybatis.mapper-locations=classpath:mapper/*.xml

mapper フォルダーを作る

  1. src/main/resources を右クリックし、新規 → フォルダー を選びます。

    src/main/resources を右クリックして新規→フォルダー

    図7-6 src/main/resources を右クリック → 新規 → フォルダー

  2. 「親フォルダー」に employee-app/src/main/resources が入っていることを確認し、「フォルダー名」に mapper と入力して 完了 をクリックします。

    新規フォルダー(mapper)

    図7-7 親フォルダーを確認し、フォルダー名に mapper と入力

【ご注意】Java のパッケージの中に作らない

mapper フォルダーは、src/main/java の mapper パッケージとは別のものです。XML ファイルは、必ず src/main/resources/mapper に作ってください。src/main/java の中に置くと、MyBatis に読み込まれません。

DepartmentMapper.xml を作る

  1. 作成した mapper フォルダーを右クリックし、新規 → ファイル を選びます。

    mapper フォルダーを右クリックして新規→ファイル

    図7-8 mapper フォルダーを右クリック → 新規 → ファイル

  2. 「親フォルダー」に employee-app/src/main/resources/mapper が入っていることを確認し、「ファイル名」に DepartmentMapper.xml と入力して 完了 をクリックします。

    新規ファイルの作成(DepartmentMapper.xml)

    図7-9 親フォルダーを確認し、ファイル名に DepartmentMapper.xml と入力

  3. エディターは「デザイン」の画面で開きます。エディターの下にある ソース タブをクリックして、XML を直接書く画面に切り替えます。

    デザインの画面で開いた XML のエディター

    図7-10 「デザイン」の画面で開くので、下の「ソース」タブをクリックする

  4. 次のように入力して保存します。

src/main/resources/mapper/DepartmentMapper.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.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>

DepartmentMapper.xml のエディター

図7-11 入力した DepartmentMapper.xml(「ソース」の画面。右にはみ出た部分は横にスクロールすると見える)

【知識メモ】最初の2行は毎回同じ

1行目の <?xml …?> と2行目の <!DOCTYPE …> は、「このファイルは MyBatis の Mapper の XML である」ことを表す決まった書き方で、どの Mapper の XML でも同じです。つづりを間違えると読み込みでエラーになるので、よく見て入力してください。

EmployeeMapper.xml を作る

同じ手順で、mapper フォルダーに EmployeeMapper.xml を作り、「ソース」タブに切り替えて、次のように入力して保存します。

src/main/resources/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コマンドが正しく終了されていません)になります。

ここまでで、プロジェクトの構成は次のようになっています。

entity・mapper のパッケージと mapper フォルダーの中身が見えるパッケージ・エクスプローラー

図7-12 entity と mapper のパッケージ、mapper フォルダーに、それぞれ2つずつファイルができた

パッケージ・エクスプローラー(src/main/java と src/main/resources)
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

ch07-mybatis-1.png

図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 に結果を入れるクラスをパッケージ名から書きました。

DepartmentMapper.xml(抜粋)
<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> に書きます。

ch07-mybatis-2.png

図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を払い出します。

EmployeeMapper.xml(抜粋)
<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>
  1. order="BEFORE" なので、INSERT の前に <selectKey> の SQL が実行され、SEQ_EMP の次の番号が取り出されます。
  2. 取り出した番号は、keyProperty="empId" で指定した、引数の employee の empId フィールドに入ります。
  3. 続いて 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 のプロジェクトには最初から組み込まれています。

テストクラスを作る

  1. パッケージ・エクスプローラーで src/test/java を開き、その中の起点となる com.example.employeeapp パッケージを右クリックします。
    (src/main/java ではないので注意してください)

  2. 新規 → クラス を選びます。

    src/test/java の com.example.employeeapp を右クリックして新規→クラス

    図7-15 src/test/java の com.example.employeeapp を右クリック → 新規 → クラス

  3. 「ソース・フォルダー」が employee-app/src/test/java、「パッケージ」が com.example.employeeapp になっていることを確認し、「名前」に MapperCheckTest と入力して 完了 をクリックします。

    新規 Java クラス(MapperCheckTest)

    図7-16 ソース・フォルダーが src/test/java であることを確認して、名前に MapperCheckTest と入力

  4. 次のコードを補完を使って入力し、保存します。

src/test/java/com/example/employeeapp/MapperCheckTest.java
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つの値が等しいかを確かめる。等しくなければテストは失敗になる

テストを実行する

  1. パッケージ・エクスプローラーで MapperCheckTest.java を右クリックします。

  2. 実行 → JUnit テスト を選びます。

    MapperCheckTest を右クリックして実行→JUnit テスト

    図7-17 MapperCheckTest.java を右クリック → 実行 → JUnit テスト

  3. Spring Boot が起動し、テストが実行されます。終わると「JUnit」ビューが表示されます。

    JUnit ビューに緑のバーと実行 3/3 が表示されている

    図7-18 テストがすべて成功した(赤枠:JUnit ビューの緑のバーと「実行: 3/3 エラー: 0 失敗: 0」)

JUnit ビューのバーの色で、結果がわかります。

バーの色 意味
緑 すべてのテストが成功した。「実行: 3/3 エラー: 0 失敗: 0」と表示される
赤 失敗したテストがある。失敗したメソッドをクリックすると、下の「障害トレース」に原因が表示される

コンソールの出力を確認する

「コンソール」ビューを開くと、次のような出力が見つかります。まず、MyBatis が実行した SQL です(第4章で logging.level.com.example.employeeapp.mapper=debug を設定したので表示されます)。

コンソール(SQL の出力例。行の先頭の日時などは省略しています)
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 した内容も表示されます。

コンソール(System.out の出力)
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:

EmployeeMapper.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 フィールドに入ります。


前へ

第6章 MyBatis 入門① データのクラス

次へ

第8章 社員一覧画面

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?