Spring BootからMyBatisを使ったDB接続(1回目 簡単なGET API)
使用するフレームワークと開発する機能について
使用するORMapper
DBへアクセスする処理を実装していきます。
開発効率、品質を考慮してO/Rマッパーを使用します。
選択肢はいくつかありますが、大きくは以下に分かれます。
- SQLは一切記述せずにJavaだけで処理を記述
- SQLで処理を記述
私は前者の経験がないので、あえてJava標準のJPAを使用することも考えましたが、以下の記事(特にp.53あたり)を読んで思いとどまりました。。。
情報が多く、学習コストもそれほど高くないということで、MyBatisを使うことにします。
MyBatisの使用方法(XML or Annotation)
MyBatisを使用する際、2通りの使い方ができます。
- XMLにSQLを記述
- XMLファイルは作成せずアノテーションを使用してJavaのみDBアクセスを記述
メリット・デメリットがありますが、今回はシンプルにSQLを記載したいので、前者のやり方で実装します。
アノテーションを使った使い方については日本語マニュアルにサンプルコードがありますので、興味があれば、参照してみてください。
開発するAPIの仕様
DBからPKを指定してレコードを取得し、結果をレスポンスとしてJSON形式で返却する簡単なAPIを開発します。
アクセスするテーブルは以下とします。
テーブル名: item
(商品)
カラム名 | 型 |
---|---|
id | int(PK) |
item_name | varchar(20) |
APIは以下のパスを指定して、Getメソッドでアクセスするようにします。
/item/{id}
{id}
でPKを指定するようにします。
処理の流れ
今回開発する機能の全体像は以下の通りです。
点線のボックスはやりとりされるデータとなります。
コンポーネント | 説明 |
---|---|
DipatcherServlet | クライアントからのリクエストを受け付けて、パスに応じたControllerのメソッドを呼び出します |
ItemController | リクエストを処理するfindById() メソッドを実装します。この中でDBにアクセスするためのMapperのメソッドを呼び出します。 |
ItemMapper (Interface) |
Interfaceのため処理内容は定義しません。処理内容は(SQL)は対応するXMLファイルで定義します。 ここがMyBatisを使って実装する処理となります。 |
データを受け渡すクラス
APIクライアントとアプリケーション間、アプリケーションとDB間でデータをやりとりする際に入れ物となるクラスを定義します。
パッケージ名 | クラス名 | 説明 |
---|---|---|
entity | Item | DBから取得したデータを格納するクラス。次回以降、登録、更新するデータを格納するためにも使用します。一般的にEntityと呼ばれるクラスです。 |
dto | ItemResponse | APIクライアントに返却するレスポンスデータを格納するクラス。次回以降、登録、更新するデータをJSONとして送信するので、ItemRequestクラスを実装します。一般的にDTO(Data Transfer Object)と呼ばれるクラス。 |
前提条件
以下の記事で作成したSpring Bootアプリケーションに機能を追加していきます。
ただし、ここでspring initializerで新規作成しても問題ないです。
下記の内容で生成すればOKです。
ただし、mysql-connector-java
についてはサーバー側のバージョンを考慮して、バージョンを固定しています。
DB環境については、以下の記事でMySQL環境を構築済みであることを前提としています。
環境情報
筆者の環境は以下の通りです。
分類 | 環境 |
---|---|
PC | MacBook Air(M1, 2020) |
OS | macOS Monterey(バージョン12.3.1) |
IDE | IntelliJ IDEA 2022.2.2 (Community Edition) |
JDK | Amazon Corretto 17.0.4.1 |
Framework | String 2.7.3 |
上記で記載した通り、DB環境は構築済みの前提で、接続情報は以下となります。
設定項目 | 設定値 |
---|---|
コンテナ名 | mysql-container |
データベース名 | demo |
rootのパスワード | root |
TimeZone | Asia/Tokyo |
今回の作成物
長文なので、今回作成するものについてここで示しておきます。
-
Spring BootからMyBatisを使ってのDB接続のための最低限の設定
- MySQLのユーザー作成(ユーザー名
app
) -
pom.xml
へMyBatisライブラリの依存関係の追加 -
application.properties
へDB接続情報の記載
- MySQLのユーザー作成(ユーザー名
-
DBにアクセスする処理の作成
- アプリケーション起動時にテーブルを作成し、データを登録するために
schema.sql
の作成 - DBから取得したデータを格納するためのクラス
Item
entityの作成 - DBにアクセスするためのMapperインタフェース
ItemMapper
の作成 - 発行するSQLや結果を受け取るクラスを定義する
itemMapper.xml
の作成
- アプリケーション起動時にテーブルを作成し、データを登録するために
-
APIの新規追加とテスト
- APIで返却するためのレスポンスクラス
ItemResponse
クラスの作成 - APIを追加するために
ItemController
クラスの作成 - APIをテストするために既存テストクラスへのテストメソッドの追加
- APIで返却するためのレスポンスクラス
ソースコード
今回作成するソースコードの全量は以下を参照ください。
開発手順
アプリケーション接続ユーザーの追加
rootで接続するのもよろしくないので、アプリケーションから接続するためのapp
ユーザをMySQLに追加します。
以下のSQLを発行して、ユーザーを追加し、権限を付与します。
ここでは一旦demoデータベースに対して全権限を付与します。(業務システムを開発する際はきちんと権限を整理する必要があると思いますが)
-- ユーザー一覧
SELECT user, host FROM mysql.user;
-- ユーザーの新規作成(任意のホストから接続可能)
CREATE USER 'app'@'%' IDENTIFIED BY 'app';
-- 権限の確認
SHOW GRANTS FOR 'app'@'%';
Grants for app@localhost |
---------------------------------------+
GRANT USAGE ON *.* TO `app`@`%`|
-- 権限付与
GRANT ALL ON demo.* TO `app`@`%`;
FLUSH PRIVILEGES;
-- 権限の確認
SHOW GRANTS FOR 'app'@'%';
Grants for app@localhost |
-----------------------------------------------------+
GRANT USAGE ON *.* TO `app`@`%` |
GRANT ALL PRIVILEGES ON `demo`.* TO `app`@`%`|
Spring Bootアプリケーションへの組み込み
pom.xmlへの依存関係の追加
必要なライブラリをpom.xmlに追記します。
Spring BootでMyBatisを使ってDBにアクセスするためにはmybatis-spring-boot-starter
を追加すれば必要なライブラリが取得されます。
あとはMySQLのJDBCドライバmysql-connector-java
を追加します。
mybatis-spring-boot-starter
は最新版を指定します。
mysql-connector-java
はサーバーにあわせて8.0.23を採用しようと思ったのですが、脆弱性があるようですので、8.0の中の最新バージョンを選択しました。
追加した内容は以下です。
<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.31</version>
</dependency>
プロパティファイルの修正
resources
直下にapplication.properties
を新規に作成して、DB接続情報等を記述します。
spring.datasource.url=jdbc:mysql://localhost:3306/demo
spring.datasource.username=app
spring.datasource.password=app
# setting for executing 'schema.sql' at application starting.
spring.sql.init.mode=always
# setting for mapping DB columns by snake-style and Java properties by camel-case-style
mybatis.configuration.map-underscore-to-camel-case=true
spring.datasource
は、MySQLへの接続情報を指定します。
spring.sql.init.mode=always
を指定するとアプリケーションが起動するたびに同じフォルダに配置しているschema.sql
を実行してくれるようになります。
これは本番環境、ステージング環境等ではデータが削除されたりすると困るので、運用に注意してください。
mybatis.configuration.map-underscore-to-camel-case=true
を指定するとスネーク形式で定義されたDBのカラムとCamel形式で定義されたJavaのプロパティを自動でマッピングしてくれ、詰め替え処理を記載する必要がありません。
schema.sqlでテーブル作成とデータ登録
アプリケーションを実行する際に毎回schema.sql
が実行されるように設定しました。
ここではテーブルを作成して、テストデータを登録するSQLを記述します。
drop table if exists item;
create table item (
id integer,
item_name varchar(20),
primary key(id)
);
insert into item (id, item_name) values (1, '大豆');
insert into item (id, item_name) values (2, '小豆');
アプリケーション起動確認
上記まで設定するとSpring Bootアプリケーションは正常に起動するはずですので、試しに起動してみましょう。
また、DBに接続して、itemテーブルが作成され、レコードが登録されていることも確認しておきましょう。
ここでエラーが出るようであれば、まずは調査して先に進むことをお勧めします。
DBへのアクセス処理の実装
MyBatisを使ったDBアクセス処理を実装します。
Entityクラスの作成
DBから取得したデータを格納するEntityクラスを作成します。
シンプルなJava Beanクラスで、Getter/SetterはLombokの@Data
アノテーションを使用して定義しています。
package com.example.springbootdemo.entity;
import lombok.Data;
@Data
public class Item {
Integer id;
String itemName;
}
Mapperインタフェースの作成
DBにアクセスする際に呼び出すメソッドをMapperインタフェースに宣言します。
バインド変数として渡すパラメーター、結果を受け取るデータの型のみを定義して、ロジック自体はこのあと作成するXMLファイルで定義します。
package com.example.springbootdemo.mapper;
import com.example.springbootdemo.entity.Item;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface ItemMapper {
Item findById(int id);
}
Mapper XMLファイルの作成
XMLファイルitemMapper.xml
をresources配下にMapperクラスと同じディレクトリ(パッケージ)構成で作成します。
<?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.springbootdemo.mapper.ItemMapper">
<select id="findById" resultType="com.example.springbootdemo.entity.Item">
select i.id , i.item_name from item i where i.id = #{id}
</select>
</mapper>
このXMLファイルでは今回は以下を定義しています。
- 対応するMapperインタフェース
- メソッド名
- 結果を格納する型
- SQL文
SQL文でバインド変数を使用する場合は#{メソッドの引名}
とMapperインタフェースのメソッドの引数名を指定します。
自分で作成したクラスを使用することもできますが、次回、使います。
API機能の実装
前章で実装した機能を呼び出してDBにアクセスし、結果をJSONとして返却するAPIを実装します。
レスポンスクラスの作成
APIの結果としてJSONを返却しますが、対応するレスポンスクラスを作成します。
Entity同様にシンプルなJava Beanです。
ここでもSetter/GetterはLombokを使用します。
package com.example.springbootdemo.dto;
import lombok.Data;
@Data
public class ItemResponse {
private int id;
private String itemName;
}
Controllerの作成
APIを新規に追加するためにControllerクラスを作成します。
package com.example.springbootdemo.controller;
import com.example.springbootdemo.dto.ItemResponse;
import com.example.springbootdemo.dto.Sample;
import com.example.springbootdemo.entity.Item;
import com.example.springbootdemo.mapper.ItemMapper;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/item")
public class ItemController {
@Autowired
ItemMapper itemMapper;
@GetMapping("/{id}")
public ItemResponse findById(@PathVariable int id){
// DBからidをキーにデータを取得
Item item = itemMapper.findById(id);
// Responseにデータをコピーしてreturn
ItemResponse itemResponse = new ItemResponse();
BeanUtils.copyProperties(item, itemResponse);
return itemResponse;
}
}
APIを呼び出す際にパスでIDを指定するため、@PathVariable
アノテーションを使って値を取得しています。
DBをアクセスするためにItemMapperを@Autowired
でオブジェクトをDIしています。
Spring DIがオブジェクトを生成してくれるので、それを使ってDBにアクセスするメソッドを呼び出します。
DBから取得したEntityをレスポンスに詰める必要がありますが、EntityクラスとResponseクラスのプロパティ(変数)名を同じにして、BeanUtils
を使ってメソッド呼び出しのみでコピーしています。
以上でAPIの作成は完了となります。
動作確認
動作確認をします。
ブラウザを開いて、以下のURLを入力してidが1のデータを表示してみます。
http://localhost:8080/item/1
以下の通り、JSONデータが表示されればOKです。
{"id":1,"itemName":"大豆"}
idに2を指定してAPIを呼び出してみます。
http://localhost:8080/item/2
以下の通り表示されればOKです。
{"id":2,"itemName":"小豆"}
簡易的なテストコードの作成
簡易的にテストコードを作成してみます。
既存のテストクラスに以下のメソッドを追加します。
@Test
void testGetItemById() throws Exception {
// 検証するAPIパス
final String API_PATH = "/item/1";
// JavaのObjectをJSONに変換するためのクラスを生成
ObjectMapper objectMapper = new ObjectMapper();
// 結果を検証するためのクラスを生成して、期待値をセット
ItemResponse itemResponse = new ItemResponse();
itemResponse.setId(1);
itemResponse.setItemName("大豆");
// APIを実行してレスポンスを検証
this.mockMvc.perform(MockMvcRequestBuilders.get(API_PATH))
.andDo(MockMvcResultHandlers.print())
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(itemResponse)));
}
APIを呼び出して、schema.sqlで登録したデータが返却されることを検証しています。
きちんと単体テストを実装しようとするとテスト実行後のDBの状態を検証すべきですが、ここではとりあえずということで、上記のテストにとどめておきます。
今回の記事は以上となります。
次回は一覧検索/insert/update/deleteを実装してみたいと思います。
Discussion