Spring BootからMyBatisを使ったDB接続(1回目 簡単なGET API)

2022/10/30に公開

使用するフレームワークと開発する機能について

使用するORMapper

DBへアクセスする処理を実装していきます。
開発効率、品質を考慮してO/Rマッパーを使用します。
選択肢はいくつかありますが、大きくは以下に分かれます。

  • SQLは一切記述せずにJavaだけで処理を記述
  • SQLで処理を記述

私は前者の経験がないので、あえてJava標準のJPAを使用することも考えましたが、以下の記事(特にp.53あたり)を読んで思いとどまりました。。。
https://www.slideshare.net/masatoshitada7/java-or-jsug

情報が多く、学習コストもそれほど高くないということで、MyBatisを使うことにします。

MyBatisの使用方法(XML or Annotation)

MyBatisを使用する際、2通りの使い方ができます。

  • XMLにSQLを記述
  • XMLファイルは作成せずアノテーションを使用してJavaのみDBアクセスを記述

メリット・デメリットがありますが、今回はシンプルにSQLを記載したいので、前者のやり方で実装します。
アノテーションを使った使い方については日本語マニュアルにサンプルコードがありますので、興味があれば、参照してみてください。
https://mybatis.org/mybatis-3/ja/java-api.html

開発するAPIの仕様

DBからPKを指定してレコードを取得し、結果をレスポンスとしてJSON形式で返却する簡単なAPIを開発します。

アクセスするテーブルは以下とします。

テーブル名: item(商品)

カラム名
id int(PK)
item_name varchar(20)

APIは以下のパスを指定して、Getメソッドでアクセスするようにします。

URLパス
/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 Bootを使ったWebAPIの作成

ただし、ここでspring initializerで新規作成しても問題ないです。
下記の内容で生成すればOKです。
ただし、mysql-connector-javaについてはサーバー側のバージョンを考慮して、バージョンを固定しています。

DB環境については、以下の記事でMySQL環境を構築済みであることを前提としています。

Dockerで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接続情報の記載
  • DBにアクセスする処理の作成
    • アプリケーション起動時にテーブルを作成し、データを登録するためにschema.sqlの作成
    • DBから取得したデータを格納するためのクラスItementityの作成
    • DBにアクセスするためのMapperインタフェースItemMapperの作成
    • 発行するSQLや結果を受け取るクラスを定義するitemMapper.xmlの作成
  • APIの新規追加とテスト
    • APIで返却するためのレスポンスクラスItemResponseクラスの作成
    • APIを追加するためにItemControllerクラスの作成
    • APIをテストするために既存テストクラスへのテストメソッドの追加

ソースコード

今回作成するソースコードの全量は以下を参照ください。

https://github.com/ryotsuka7/spring-boot-demo/tree/v.1.0.3

開発手順

アプリケーション接続ユーザーの追加

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は最新版を指定します。
https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test/2.7.5

mysql-connector-javaはサーバーにあわせて8.0.23を採用しようと思ったのですが、脆弱性があるようですので、8.0の中の最新バージョンを選択しました。
https://mvnrepository.com/artifact/mysql/mysql-connector-java/8.0.31

追加した内容は以下です。

pom.xml
		<!-- 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接続情報等を記述します。

application.properties
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を記述します。

schema.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アノテーションを使用して定義しています。

entity.Item.java
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クラスと同じディレクトリ(パッケージ)構成で作成します。

itemMapper.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.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を使用します。

dto.ItemResponse.java
package com.example.springbootdemo.dto;

import lombok.Data;

@Data
public class ItemResponse {
    private int id;
    private String itemName;
}

Controllerの作成

APIを新規に追加するためにControllerクラスを作成します。

controller.ItemController.java
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