第3章で作った社員テーブルと部署テーブルを、Java のプログラムから扱えるようにする準備として、テーブルの1行分のデータを入れるクラスを作ります。この章では、SQL を自分で書いてデータベースを扱う MyBatis の考え方と、テーブルとクラスの対応のさせ方を学びます。SQL を書いてデータを取り出すのは第7章です。
【知識メモ】この章は MyBatis コースです
第6章と第7章は、JPA コースと MyBatis コースに分かれています。どちらか一方だけを読めば、第8章以降に進めます。JPA コースで進める場合は、第6章 JPA 入門① Entity を読んでください。
この章で学ぶこと
- MyBatis の考え方(SQL は自分で書き、結果を Java のオブジェクトに入れてもらう)
- テーブルの1行分のデータを入れるクラスの作り方
- 社員と部署の関係(多対一)を、
Department型のフィールドで表す方法
6.1 MyBatis とは
データベースのデータを操作するには SQL を使います。第3章では、SQL Developer で SELECT や INSERT を実行しました。
Java のプログラムから SQL を実行することもできますが、「SQL を文字列で組み立てる」「実行結果を1列ずつ取り出して Java のオブジェクトに詰め替える」といった手間のかかる処理が必要になります。
そこで使われるのが MyBatis(マイバティス)です。MyBatis では、SQL は自分で書き、その SQL の実行と、結果を Java のオブジェクトに詰め替える処理を MyBatis が行います。SQL は Java のコードとは別の XML ファイルに書くので、Java のコードの中に SQL の文字列が混ざりません。
テーブルとクラスの対応
MyBatis でも、データベースのテーブルと Java のクラスを、次のように対応させて考えます。
| データベース | Java | この教材の例 |
|---|---|---|
| テーブル | クラス | EMPLOYEE テーブル ↔ Employee クラス |
| 行(1件のデータ) | オブジェクト(インスタンス) | 雷禅 海皇さんの行 ↔ Employee オブジェクト1つ |
| 列 | フィールド | NAME 列 ↔ name フィールド |
図6-1 SQL の結果とオブジェクトの対応
JPA との違い
Spring でデータベースを扱う方法には、MyBatis のほかに JPA があります(この教材の JPA コースで使っているものです)。どちらも現場で広く使われています。
| MyBatis | JPA | |
|---|---|---|
| SQL | 自分で書く(XML ファイル) | メソッド名などから自動で作られる |
| データのクラス | 普通のクラス(アノテーションは付けない) |
@Entity などのアノテーションを付ける |
| 結合(JOIN) | 結合の SQL を書き、結果の入れ方を XML に書く |
@ManyToOne などを付けると自動で結合される |
| 向いている場面 | 実行する SQL を細かく決めたいとき。SQL を読み書きできる人が多い現場 | 決まった形の検索・登録・変更・削除が多いとき |
MyBatis では、実行される SQL が XML ファイルにそのまま書いてあるので、何が実行されるかを読んで確かめられます。DB入門などで学んだ SQL の知識が、そのまま生かせます。
MyBatis と MyBatis Spring Boot Starter の関係
この章では、似た名前の部品がいくつか登場します。関係を整理しておきましょう。
| 名前 | 説明 |
|---|---|
| MyBatis | SQL を実行し、結果を Java のオブジェクトに詰め替えるライブラリ |
| MyBatis-Spring | MyBatis を Spring フレームワークで使えるようにするライブラリ |
| MyBatis Spring Boot Starter | 上の2つを Spring Boot で最小限の設定で使えるようにしたもの。第4章で pom.xml に書き足した mybatis-spring-boot-starter がこれ |
私たちが直接書くのは、データを入れるクラス(この章)、SQL を呼び出すためのインターフェース、SQL を書いた XML ファイル(第7章)の3つです。
6.2 データのクラスを作る
テーブルの1行分のデータを入れるクラスを作ります。部署テーブル用の Department と、社員テーブル用の Employee の2つです。この教材では、これらのクラスを entity パッケージに置きます(JPA コースと同じ名前にしているので、第8章以降のコードは、どちらのコースでもほとんど同じになります)。
まず、第1章・第3章で確認したテーブルの定義をもう一度見ておきましょう。
| テーブル | 列名 | 型 | Java のフィールド | Java の型 |
|---|---|---|---|---|
| DEPARTMENT | DEPT_ID | NUMBER(4) | deptId | Integer |
| DEPT_NAME | VARCHAR2(20 CHAR) | deptName | String | |
| EMPLOYEE | EMP_ID | NUMBER(6) | empId | Integer |
| NAME | VARCHAR2(20 CHAR) | name | String | |
| GENDER | NUMBER(1) | gender | Integer | |
| AGE | NUMBER(3) | age | Integer | |
| BIRTH_DATE | DATE | birthDate | Date | |
| DEPT_ID | NUMBER(4) | department | Department | |
| ADDRESS | VARCHAR2(10 CHAR) | address | String |
【知識メモ】名前の付け方のルール
データベースの列名は大文字とアンダースコア(DEPT_ID)、Java のフィールド名は先頭が小文字で単語の区切りを大文字にする書き方(deptId)にするのが一般的です。後者をキャメルケースといいます。
entity パッケージを作る
-
パッケージ・エクスプローラーで
employee-appのsrc/main/javaの中にある、起点となるcom.example.employeeappパッケージを右クリックします。 -
新規 → パッケージ を選びます。
図6-2 起点のパッケージを右クリック → 新規 → パッケージ
-
「名前」には、右クリックした
com.example.employeeappが最初から入っています。末尾に.entityを付け足してcom.example.employeeapp.entityにし、完了 をクリックします。図6-3 名前を com.example.employeeapp.entity にして「完了」
【ご注意】パッケージの場所に注意
第7章で作る Mapper(SQL を呼び出すためのインターフェース)は、必ず com.example.employeeapp パッケージの 中(com.example.employeeapp.○○)に作ってください。Spring Boot は、メインクラス EmployeeAppApplication があるパッケージとその下のパッケージだけを探します(第5章の「コンポーネントスキャン」で学んだ範囲です)。外に作ると、Spring に見つけてもらえずエラーになります。この章で作るデータのクラスは探す対象ではありませんが、この教材では JPA コースと同じ entity パッケージに置きます。
Department クラスを作る
-
作成した
com.example.employeeapp.entityパッケージを右クリックし、新規 → クラス を選びます。図6-4 entity パッケージを右クリック → 新規 → クラス
-
「パッケージ」に
com.example.employeeapp.entityが自動で入っていることを確認し、「名前」にDepartmentと入力して 完了 をクリックします。図6-5 パッケージを確認し、名前に Department と入力
-
エディターが開くので、次のようにフィールドを入力します(getter と setter はまだ書きません)。クラス名は、第4章で説明した 補完(Ctrl + Space)を使って入力します。
package com.example.employeeapp.entity;
public class Department {
/** 部署ID */
private Integer deptId;
/** 部署名 */
private String deptName;
}
図6-6 フィールドを入力したところ(赤枠の行にカーソルを置いて、次の getter と setter の生成に進む)
フィールド名に黄色の波線が表示されますが、これは「このフィールドはまだ使われていない」という警告で、この後 getter と setter を作ると消えます。
【再確認】クラス名は、必ず補完(Ctrl + Space)で入力する
第4章の「入力は補完で行う」と同じです。この章では、次の2点に気を付けてください。
- import 文が必要なクラス名(
Dateなど)は、Dateのように途中まで打って Ctrl + Space で補完します。補完で選ぶと、必要な import 文がファイルの先頭に自動で追加されます。 - 手で全部打ち込むと import 文が追加されず、赤い波線(エラー)になります。そのときは、打った語の末尾にカーソルを置いて Ctrl + Space を押し、候補から選び直してください。
getter と setter を自動生成する
フィールドは private なので、Controller や画面から値を読み書きするための getter と setter が必要です。Eclipse の機能で自動生成しましょう。
-
エディターの中で、最後の
}の直前の空行(図6-6 の赤枠の行)にカーソルを置きます。 -
メニューの ソース → getter および setter の生成 を選びます(エディター内で右クリックしても同じメニューがあります)。
図6-7 ソース → getter および setter の生成
-
表示された画面で すべて選択 をクリックし、
deptIdとdeptNameにチェックが入ったことを確認します。 -
生成 をクリックします。
図6-8 すべて選択 → チェックを確認 → 生成
-
Ctrl + S で保存します。エディターのタブの「*」(未保存のしるし)が消えたことを確認しましょう。
図6-9 getter と setter を生成した直後(上)と、保存したあと(下)
Eclipse でよく使う操作は、付録C「プロジェクトとファイルの操作」 にまとめています。
完成した Department クラスは次のとおりです。
package com.example.employeeapp.entity;
public class Department {
/** 部署ID */
private Integer deptId;
/** 部署名 */
private String deptName;
public Integer getDeptId() {
return deptId;
}
public void setDeptId(Integer deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
}
Employee クラスを作る
同じ手順で、com.example.employeeapp.entity パッケージを右クリックして Employee クラスを作り、次のように入力します。
package com.example.employeeapp.entity;
import java.util.Date;
public class Employee {
/** 社員ID */
private Integer empId;
/** 氏名 */
private String name;
/** 性別(1:男、2:女) */
private Integer gender;
/** 年齢 */
private Integer age;
/** 生年月日 */
private Date birthDate;
/** 部署 */
private Department department;
/** 住所(都道府県) */
private String address;
}
図6-10 フィールドを入力したところ(赤枠の行にカーソルを置いて、getter と setter を生成する)
【ご注意】Date は java.util.Date を選ぶ
Date を補完すると、候補に Date - java.sql など同じ名前のクラスも並びます。必ず Date - java.util を選んでください。
続けて、Department と同じ手順で getter と setter を自動生成します。
-
エディターの中で、最後の
}の直前の空行(図6-10 の赤枠の行)にカーソルを置きます。 -
メニューの ソース → getter および setter の生成 を選びます。
-
表示された画面で すべて選択 をクリックし、7つのフィールドすべてにチェックが入ったことを確認します。
-
生成 をクリックします。
-
Ctrl + S で保存し、エディターのタブの「*」が消えたことを確認します。
図6-11 Employee クラス(自動生成した getter と setter の部分)
完成した Employee クラスは次のとおりです。
package com.example.employeeapp.entity;
import java.util.Date;
public class Employee {
/** 社員ID */
private Integer empId;
/** 氏名 */
private String name;
/** 性別(1:男、2:女) */
private Integer gender;
/** 年齢 */
private Integer age;
/** 生年月日 */
private Date birthDate;
/** 部署 */
private Department department;
/** 住所(都道府県) */
private String address;
public Integer getEmpId() {
return empId;
}
public void setEmpId(Integer empId) {
this.empId = empId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthDate() {
return birthDate;
}
public void setBirthDate(Date birthDate) {
this.birthDate = birthDate;
}
public Department getDepartment() {
return department;
}
public void setDepartment(Department department) {
this.department = department;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
6.3 データのクラスの解説
アノテーションを付けない
MyBatis のデータのクラスには、アノテーションを付けません。フィールドと getter・setter だけの、普通のクラスです。
「どのテーブルから、どの列を取り出すか」は、第7章で XML に書く SQL で決めます。MyBatis は、その SQL の結果の列を、名前が対応するフィールドに入れます。そのため、データのクラスの側には、テーブルの情報を書く必要がありません。
列とフィールドの対応
SQL の結果の列名は BIRTH_DATE、フィールド名は birthDate のように、書き方が違います。MyBatis は、第4章で application.properties に書いた次の設定によって、アンダースコアの区切りを大文字の区切りに読み替えて対応させます。
mybatis.configuration.map-underscore-to-camel-case=true
| SQL の結果の列名 | 入るフィールド |
|---|---|
DEPT_ID |
deptId |
DEPT_NAME |
deptName |
BIRTH_DATE |
birthDate |
NAME |
name |
そのため、フィールド名は、列名と対応するように付けます。対応する名前のフィールドが無い列は、どのフィールドにも入りません(エラーにはならず、フィールドは null のままになります)。
部署の department フィールドだけは、名前で対応させることができません。後の「社員と部署の関係」で説明します。
主キーの番号(シーケンス)
第3章では、主キーの番号を払い出すシーケンスを作りました。
CREATE SEQUENCE SEQ_EMP NOCACHE;
MyBatis では、このシーケンスの使い方も SQL に書きます。社員を登録する SQL で SEQ_EMP.NEXTVAL から次の番号を取り出し、その番号を社員IDにして INSERT します(第7章で書きます)。データのクラスの側に書くことはありません。
なぜ int ではなく Integer を使うのか
ID や年齢のフィールドには、int ではなく Integer を使っています。int は必ず何かの数値(初期値は 0)を持ちますが、Integer は 「値がない」ことを null で表せる からです。
たとえば新規登録する前の社員には、まだ社員IDがありません。このとき int だと 0 になってしまい、「社員IDが 0 の社員」なのか「まだ ID がない社員」なのか区別できません。データのクラスでは、数値のフィールドに Integer などのラッパークラスを使うのが基本です。
日付は Date
Oracle の DATE 型の列(生年月日)は、Java の java.util.Date 型のフィールドに対応させます。java.util.Date は日付と時刻を表すクラスで、現場のシステムで広く使われています。生年月日のように日付だけを扱う場合、時刻の部分は 0時0分0秒 になります。変換は MyBatis が自動で行います。なお、新しく作るシステムでは、日付だけを表す java.time.LocalDate を使うことも増えています。
社員と部署の関係
社員は必ずどこか1つの部署に所属します。一方、1つの部署には複数の社員が所属できます。このように「社員(多)が部署(一)に属する」関係を 多対一 といいます。
図6-12 社員と部署は「多対一」の関係
テーブルでは、社員テーブルの DEPT_ID 列に部署IDの数値が入っているだけでした。部署名を知るには、SQL で部署テーブルと 結合(JOIN) する必要があります。
SELECT e.EMP_ID, e.NAME, d.DEPT_NAME
FROM EMPLOYEE e
JOIN DEPARTMENT d ON e.DEPT_ID = d.DEPT_ID;
Employee クラスでは、部署IDの数値(Integer deptId)ではなく、Department オブジェクトそのもの をフィールドに持たせました。
/** 部署 */
private Department department;
第7章では、社員テーブルと部署テーブルを結合する SQL を書き、その結果のうち部署の列(DEPT_ID・DEPT_NAME)を Department オブジェクトに入れて、department フィールドにセットするよう MyBatis に指定します。この指定は名前の対応だけではできないので、XML に書きます。
こうしておくと、Java のプログラムでは次のように書くだけで部署名が取れます。
String deptName = employee.getDepartment().getDeptName(); // "営業部"
図6-13 Employee オブジェクトが Department オブジェクトを持っている
【知識メモ】部署IDはどうやって取り出す?
社員の部署IDが必要なときは、employee.getDepartment().getDeptId() と書きます。第7章の登録・変更の SQL でも、この形で部署IDを取り出して DEPT_ID 列に入れます。
6.4 よくあるエラー
データのクラスの書き間違いは、この章の時点ではエラーになりません。第7章のテストや、第8章以降でアプリを起動したときに、次のような形で現れます。そのときは、次の表を参考に直してください。
| 起きること | 主な原因 | 対処 |
|---|---|---|
| 画面の一部の項目(生年月日など)が空になる | 列の値がフィールドに入っていない | フィールド名が列名と対応しているか(BIRTH_DATE → birthDate)、application.properties に mybatis.configuration.map-underscore-to-camel-case=true があるかを確認する |
| 生年月日を使うところでエラーになる |
Date の import を間違えた |
import 文が java.util.Date になっているかを確認する(java.sql.Date ではない) |
| 赤い波線(コンパイルエラー) | import 文が無い、つづりの間違い | 赤い波線の語の末尾にカーソルを置いて Ctrl + Space を押し、候補から選び直す |
その他のエラーは、付録B「部品(Bean)やクラスが見つからない」 も参考にしてください。
6.5 まとめと確認問題
まとめ
- MyBatis では、SQL は自分で書き、SQL の実行と、結果を Java のオブジェクトに詰め替える処理を MyBatis が行う
- テーブルの1行分のデータを入れるクラスは、フィールドと getter・setter だけの普通のクラスにする。アノテーションは付けない
- SQL の結果の列は、名前が対応するフィールドに入る(
BIRTH_DATE→birthDate)。読み替えはapplication.propertiesのmap-underscore-to-camel-caseの設定で行う - 主キーの番号は、登録の SQL の中で第3章のシーケンスから取り出す(第7章)
- 社員と部署のような多対一の関係は、
Department型のフィールドで表す。結合した結果の入れ方は第7章で XML に書き、employee.getDepartment().getDeptName()で部署名が取れるようにする
確認問題
問1 MyBatis と JPA の、SQL の扱い方の違いを説明してください。
解答を見る
MyBatis では SQL を自分で書き(XML ファイル)、SQL の実行と結果の詰め替えを MyBatis が行います。JPA では、SQL はメソッド名などから自動で作られ、自分で書く必要がありません。
問2 データのクラスの ID のフィールドに、int ではなく Integer を使うのはなぜですか。
解答を見る
Integer は「値がない」ことを null で表せるからです。新規登録前の社員のように、まだ ID が決まっていない状態を表すことができます。int では 0 になってしまい、区別がつきません。
問3 SQL の結果の BIRTH_DATE 列の値が、birthDate フィールドに入るのはなぜですか。
解答を見る
application.properties に mybatis.configuration.map-underscore-to-camel-case=true を書いているので、MyBatis がアンダースコアの区切り(BIRTH_DATE)を大文字の区切り(birthDate)に読み替えて、名前が対応するフィールドに値を入れるからです。












