13
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SpringBoot+MyBatisでCRUDを書いてみた

Last updated at Posted at 2020-11-05

はじめに

SpringBootでDB操作を行うRepository層の実装をまとめていきたいと思います。
DBとのインタフェースに使うO/Rマッパーはいろいろありますが、この記事ではMyBatisを使います。

MyBatisはJavaとSQLのマッピングがシンプルでわかりやすく動的SQLの記述も可能です。
MyBatisGeneratorを使うとDBのテーブル情報から、JavaのModelクラスやCRUD用メソッドが実装されたMapperクラスをごそっと作れるのがとても便利です。(別の記事で紹介予定)

今回はModelとMapperの対応を理解するため手動で作成します。

開発環境

OS : macOS Catalina
IDE : IntelliJ Ultimate
Java : 11
Gradle : 6.6.1
SpringBoot : 2.3.4
MySQL : 8.0.17

1. データベースの準備

まずはアプリケーションで操作するDBの準備を行います。
ここではMySQLのローカルの環境を整えます。

1-1. MySQLの準備

DockerでMySQLを使うのがお手軽で簡単です。(丸投げ)

上記でMySQLにrootユーザで接続後、本アプリ用のdatabaseuserを作っておきます。

-- データベースdemo_dbの作成
CREATE DATABASE demo_db;
-- ユーザ名:demo、パスワード:demo、権限:demo_dbに対する全ての権限
CREATE USER 'demo'@'%' IDENTIFIED BY 'demo';
GRANT ALL PRIVILEGES ON demo_db.* TO 'demo'@'%';
FLUSH PRIVILEGES;

これでMySQLの準備は完了です。

1-2. SpringBootプロジェクトの作成

SpringBootプロジェクトは前回のをベースにMyBatis、JDBC(MySQL)のライブラリを追加します。

build.gradle
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3' // 追加
    compile group: 'mysql', name: 'mysql-connector-java', version: '8.0.22' // 追加
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testCompileOnly 'org.projectlombok:lombok'
    testAnnotationProcessor 'org.projectlombok:lombok'
}

1-3. アプリ用のデータソースの設定

アプリのローカル環境用にMySQLへの接続設定をmain/resources/application.ymlに記述します。
先ほど作った user:demoDatabase:demo_db の接続設定は以下の通りです。

src/main/resources/application.yml
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/demo_db
    username: demo
    password: demo
mybatis:
  configuration:
    map-underscore-to-camel-case: true

map-underscore-to-camel-case: trueはDBオブジェクトとJavaオブジェクトをマッピングする際にスネークケースの名前をキャメルケースにマッピングしてくれます。

以上でMySQLの接続設定は完了です。

1-4. テーブルの作成

簡単なサンプルのテーブルを作成します。

customerテーブル仕様
列名 制約など
id int 自動採番のプライマリキー
name varchar(100) not null制約
age int not null制約
address varchar(200)
DDL
schema.sql
create table customer
(
    id int auto_increment,
    name varchar(100) not null,
    age int not null,
    address varchar(200) null,
    constraint customer_pk primary key (id)
);
DML

ついでに初期投入データも作っておきます。

data.sql
insert into
    customer (name, age, address)
VALUES
    ('ルーク・スカイウォーカー', 19, 'タトゥイーン'),
    ('レイア・オーガナ', 19, 'オルデラン'),
    ('ハン・ソロ', 32, 'コレリア'),
    ('ダース・ベイダー', 41, 'タトゥイーン');

上記のDDL、DMLをMySQLで実行&commitしてテーブルと初期データをセットアップします。

また、作ったDDLはschema.sql、DMLはdata.sqlとしてtest/resourcesに保存しておきます。
これらのファイルは次回のDBUnitテストで使います。

├── build.gradle
└── src
    ├── main
    └── test
        ├── java
        └── resources
            ├── application.yml
            ├── schema.sql   // DDL
            └── data.sql     // DML

2. MyBatisを使ったRepositoryの作成

DB操作を行うためのRepositoryクラスを作成します。
今回、O/RマッパーにMyBatisを使うため、お作法に則り

  • Select句の結果にマッピングするModel(Entity)クラスの作成
  • ModelとCRUDのSQLをマッピングするMapperの作成
  • Mapperを利用するRepositoryクラスの作成

の順に作っていきます。

2-1. Model(Entity)クラスの作成

先ほど作ったcustomerテーブルの1行分のデータを保持するModelクラスを作成します。

Customer.java
package com.example.dbunitdemo.domain.model;

import lombok.Builder;
import lombok.Data;

@Builder
@Data
public class Customer {
    private Long id;
    private String name;
    private Integer age;
    private String address;
}

@lombok.DataのおかげでアクセサとtoString()が自動で作られるのでシンプルです。

2-2. Mapperの作成

先ほど作ったCustomerクラスをCRUDのSQLと結びつけるMapperを作成します。
MapperはJavaのインタフェースSQLを記述したXMLファイルの2つで1組みとなるよう作成します。

CustomerMapper.java
com.example.dbunitdemo.domain.mapper.CustomerMapper.java
package com.example.dbunitdemo.domain.mapper;

import com.example.dbunitdemo.domain.model.Customer;
import org.apache.ibatis.annotations.*;
import java.util.List;

@Mapper
public interface CustomerMapper {
    List<Customer> findAll();
    Customer get(@Param("id") Long id);
    int insert(@Param("customer") Customer customer);
    int update(@Param("customer") Customer customer);
    int delete(@Param("id") Long id);
}

@Paramは引数を後述のXMLのSQLステートメントで参照するための名前を指定します。

CustomerMappler.xml
main/resources/com/example/dbunitdemo/domain/mapper/CustomerMapper.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.dbunitdemo.domain.mapper.CustomerMapper">
    <select id="findAll" resultType="com.example.dbunitdemo.domain.model.Customer">
        SELECT id, name, age, address FROM customer
    </select>
    <select id="get" resultType="com.example.dbunitdemo.domain.model.Customer">
        SELECT id, name, age, address FROM customer WHERE id = #{id}
    </select>
    <insert id="insert" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO customer (name, age, address) VALUES (#{customer.name}, #{customer.age}, #{customer.address})
    </insert>
    <update id="update">
        UPDATE customer SET name = #{customer.name}, age = #{customer.age}, address = #{customer.address} WHERE id = #{customer.id}
    </update>
    <delete id="delete">
        DELETE FROM customer WHERE id = #{id}
    </delete>
</mapper>
補足:MapperのXML作成の注意点
  • main/resources配下にJavaのパッケージ階層と同じディレクトリ階層を作成して(Mapper名).xmlファイルを作成する。
  • <mapper namespace="...">にはMapperインタフェースのFQCNを指定する。
  • <insert><select><update><delete>id属性にMapperインタフェースのメソッド名を指定し、コンテンツ部分にSQLを記述してMapperのメソッドとSQLを紐付ける。
  • <select resultType="...">には検索結果をマッピングするModelクラスのFQCNを指定する。
  • <insert>で登録時に、自動採番された値を元のModelのプロパティにセットしたい場合useGeneratedKeys="true"keyProperty="id"を組み合わせて指定する。
AppConfig.java

最後にMapperインタフェースの属するパッケージをコンフィグクラスのアノテーションで指定します。

AppConfig.java
package com.example.dbunitdemo.config;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.example.dbunitdemo.domain.mapper") // Mapperの属するパッケージを指定
public class AppConfig {
   // ... 省略
}

以上でCustomerMapperの作成は完了です。

Mapperの作成後のディレクトリ構成は以下のようになっています。

├── main
│   ├── java
│   │   └── com
│   │       └── example
│   │           └── dbunitdemo
│   │               ├── DbunitDemoApplication.java
│   │               ├── config
│   │               │   └── AppConfig.java
│   │               └── domain
│   │                   ├── mapper
│   │                   │   └── CustomerMapper.java
│   │                   └── model
│   │                       └── Customer.java
│   └── resources
│       ├── application.yml
│       ├── com
│       │   └── example
│       │       └── dbunitdemo
│       │           └── domain
│       │               └── mapper
│       │                   └── CustomerMapper.xml

2-3. Mapperを利用するRepositoryクラスの作成

シンプルにDIしたMapperRepositoryから利用します。

CustomerRepository.java
package com.example.dbunitdemo.domain.repository;

import com.example.dbunitdemo.domain.mapper.CustomerMapper;
import com.example.dbunitdemo.domain.model.Customer;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
@RequiredArgsConstructor
public class CustomerRepository {

  private final CustomerMapper customerMapper;

  public List<Customer> findAll() {
    return customerMapper.findAll();
  }

  public Customer get(Long id) {
    return customerMapper.get(id);
  }

  public int create(Customer customer) {
    return customerMapper.insert(customer);
  }

  public int update(Customer customer) {
    return customerMapper.update(customer);
  }

  public int delete(Long id) {
    return customerMapper.delete(id);
  }
}

Mapperをラップしてるだけで意味のないクラスに思えるかもしれませんが、
Serviceインフラやミドルウェアに依存しないようにするために必要なことです。

例えば、Repositoryを介さずに直接ServiceからMapperを利用した場合、Serviceのロジック内にMyBatis特有の処理(例えばExampleを使ったクエリ構築)や、下手したらDBがMySQLであることを意識した処理なんかが実装されてしまう可能性があります。

ServiceRepositoryのトランザクション管理に注力すべきで、インフラやミドルウェアに依存した処理は実装すべきではありません。
そのような部分を隠蔽するためにRepositoryを作成します。

3. 動作確認する

ここまで作ったら動作確認したいので、サクッとRepository -> Service -> Controllerまで繋げてみます。
ひとまず、DBの初期データの表示を確認するために findAll メソッドにだけ実装してみます。

CustomerService.java
@Service
@RequiredArgsConstructor
public class CustomerService {
  private final CustomerRepository customerRepository;

  @Transactional(readOnly = true)
  public List<Customer> findAll() {
    return customerRepository.findAll();
  }
}
CustomerController.java

@RequiredArgsConstructor
@RestController
public class CustomerController {
    private final CustomerService customerService;

    @GetMapping("customers")
    public List<Customer> findAll() {
        return customerService.findAll();
    }
}

ここまで作成したらbootRunでアプリケーションを起動して、ブラウザから http://localhost:8080/customers にアクセスしてみます。
image.png

無事に投入していたデータが表示されました!

次回はDBUnitでテストしてみる

今回はMySQLの設定、MyBatisのMapperとそれを利用するRepositoryの実装をまとめました。
次回はこれらをDBUnitを使ったテストケースの実装を紹介していきます。

13
13
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
13
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?