LoginSignup
14
21

More than 3 years have passed since last update.

SpringBoot+MyBatis+MySQLの初歩

Last updated at Posted at 2020-10-18

概要

これまでJdbcTemplateの使用経験しかなくMyBatisを使ったことがなかったので、SpringBoot + MyBatis + MySQLを使用してみた。

環境

  • macOS Catalina 10.15.7
  • Java11
  • SpringBoot 2.3.4
  • MySQL 8.0
  • Gradle

やってみたこと

今回は本の情報がDBに登録されており、それを1件取得、全件表示することにします。

  • データベースからID検索し表示
  • 全件取得して一覧表示

■初回アクセス時の画面
初期画面.png

■1件検索(idをフォームに入力して検索を押下)
検索結果.png

■全件表示(一覧表示を押下)
一覧表示.png

準備する各種クラス・設定ファイルたち

  • build.gradle(こちらの依存関係にMyBatisを追加)
  • DB関連:data.sql、schema.sql
  • BookFormクラス(入力フォームからの値を受け取る)
  • ビュー画面(Thymeleaf使用)
  • Bookクラス(DBからの値を受け取るEntityクラス)
  • BookDaoインタフェース(DBへ問合せを行うためのインタフェース)
    • BookDao.xml(ここにSQLを記載。)
  • BookServiceクラス(Daoクラスを呼び出す。)
  • BookControllerクラス(ブラウザからのリクエストに応じたビューを返す。状況に応じてserviceクラスを呼び出す。)

なぜか、BookDaoはインタフェースを実装せずともBookServiceクラスから利用できる。裏側でごにょごにょ実装されてServiceクラスから利用できるようになっているらしい。

ディレクトリ構成

  • MyBatisはマッピングファイル(XMLファイル)にSQLを書くやり方を採用(アノテーション内にSQLを書くやり方もあり)。マッピングファイルはresorces配下のDaoと同じ階層に配置することで、Dao利用時に自動で読み込んでくれる。
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── demo
    │   │               ├── ServletInitializer.java
    │   │               ├── SpringTestApplication.java
    │   │               ├── controller
    │   │               │   └── BookController.java
    │   │               ├── dao
    │   │               │   └── BookDao.java
    │   │               ├── entity
    │   │               │   └── Book.java
    │   │               ├── form
    │   │               │   └── BookForm.java
    │   │               └── service
    │   │                   └── BookService.java
    │   └── resources
    │       ├── application.properties
    │       ├── com
    │       │   └── example
    │       │       └── demo
    │       │           └── dao
    │       │               └── BookDao.xml
    │       ├── data.sql
    │       ├── schema.sql
    │       ├── static
    │       │   └── css
    │       │       └── style.css
    │       └── templates
    │           └── index.html
    └── test

依存関係


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'mysql:mysql-connector-java'
    annotationProcessor 'org.projectlombok:lombok'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }

DBの定義

まずはMySQLでデータベースを作っておく。今回の例でいうとlibrary。


CREATE DATABASE library;

下記ファイルを用意すると、SpringBoot起動の度にテストデータを用意してくれる。

schema.sql
--booktableがあれば削除
DROP TABLE IF EXISTS booktable;

--booktableがなければ新しく作成
CREATE TABLE IF NOT EXISTS booktable(
id INT AUTO_INCREMENT,
book_name VARCHAR(50) NOT NULL,
volume_num INT NOT NULL,
author_name VARCHAR(50) NOT NULL,
published_date DATE NOT NULL,
PRIMARY KEY(id)
);
data.sql
--本のリスト初期データ
--idカラムはオートインクリメントなので不要
INSERT INTO booktable
(book_name, volume_num,author_name,published_date)
VALUES
( 'HUNTER X HUNTER',36,'冨樫義博','2018-10-04'),
( 'ベルセルク',40,'三浦健太郎','2018-09-28'),
( 'ドリフターズ',6,'平野耕太','2018-11-30'),
( '羅生門',1,'芥川龍之介','1915-11-01')
;

設定ファイル(application.properties)

接続先に?serverTimezone=JSTをつけないと上手くいかない。また、mybatis.configuration.map-underscore-to-camel-case=trueにより、DBのカラム名がスネークケースであってもJava側ではキャメルケースとして認識してくれる。

### データベース接続設定
spring.datasource.url=jdbc:mysql://localhost:3306/library?serverTimezone=JST
spring.datasource.username=root
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
### スネークケースのDBカラム名をSpringのEntity側ではキャメルケースとして対応付けてくれる。
mybatis.configuration.map-underscore-to-camel-case=true
### 初期化を行うかの指定。
spring.datasource.initialization-mode=always

各種クラス

Formクラス

今回は画面からはidのみ受け取るのでフィールドは1個。HTML側のinputタグ内name属性とフィールド名は一致させておく。lombokの機能により、@Dataでセッターゲッターは追記不要。フィールドの型はプリミティブ型のintではなく、参照型(ラッパークラス)のIntegerにするのが良いらしい。ゼロとnullで区別がつくため。

BookForm.java
package com.example.demo.form;
import lombok.Data;

@Data
public class BookForm {
    private Integer id;
}

Entityクラス

データベースから取得したデータをいったん格納するオブジェクト。Formクラスにも書いたが、型は参照型が良い。

Book.java
package com.example.demo.entity;
import java.time.LocalDate;
import lombok.Data;

@Data
public class Book {
    private Integer id;
    private String bookName;
    private Integer volumeNum;
    private String authorName;
    private LocalDate publishedDate;
}

Daoインタフェースとマッピングファイル

MyBatisの場合は、インタフェースをつくり@MapperアノテーションをつけることでRepositoryクラスになる。1件検索のメソッドでは、単純に数値だけ渡すよりも、Entityクラス(Bookクラス)を介した方が、マッピングファイルの中で、Entityクラスの各フィールドを柔軟に参照できる。

BookDao
package com.example.demo.dao;
import java.util.List;
import org.apache.ibatis.annotations.Mapper;
import com.example.demo.entity.Book;

@Mapper
public interface BookDao {
    //1件検索
    Book findById(Book book);

    //全件取得
    List<Book> findAll();
}

【XMLファイルについて】

  • ファイル名は対応するDaoと同じにする。
  • 配置場所はresorces配下のDaoインタフェースと同じ階層にする。
  • namespace属性にはDaoインタフェースの完全修飾クラス名を書く。
  • select要素にSELECT文を書く。
    • id属性にはDaoインタフェースの対応するメソッド名を書く。
    • resultType属性には検索結果をマッピングするクラス名を書く。今回はBookクラス。
    • parameterType属性は今回省略。メソッドの引数の型を書くらしい。省略すると、自動で実際の引数の型が判定される。
  • 下記findByIdでは、WHERE句でEntityクラス(Bookクラス)のidフィールドを参照している。
BookDao.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.demo.dao.BookDao">
    <select id="findById" resultType="com.example.demo.entity.Book">
        SELECT
            id,
            book_name,
            volume_num,
            author_name,
            published_date
         FROM
            booktable
         WHERE
            id = #{id}
    </select>
    <select id="findAll" resultType="com.example.demo.entity.Book">
        SELECT
            id,
            book_name,
            volume_num,
            author_name,
            published_date
         FROM
            booktable
    </select>
</mapper>

Serviceクラス

Daoクラスでも書いたが、1件検索メソッドではEntityクラスのidフィールドに値をセットしている。全件取得はEntityクラスを要素にもつリストを返す。

package com.example.demo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.demo.dao.BookDao;
import com.example.demo.entity.Book;

@Service
public class BookService {

    @Autowired
    BookDao bookDao;

    //1件検索
    public Book findById(Integer id) {
        Book book = new Book();
        book.setId(id);
        return this.bookDao.findById(book);
    }

    //全件取得
    public List<Book> getBookList(){
        return this.bookDao.findAll();
    }
}

Controllerクラス

下記では@GetMapping@PostMappingを使いわけてません。

BookController.java
package com.example.demo.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demo.entity.Book;
import com.example.demo.form.BookForm;
import com.example.demo.service.BookService;

@Controller
@RequestMapping("/book")
public class BookController {

    @Autowired
    BookService bookService;

    @RequestMapping("/search")
    public String index(BookForm bookForm, String showList, Model model) {

        //タイトル
        model.addAttribute("title", "本屋さん");

        //bookform(formクラス)がnullじゃなかったら1件検索
        if(bookForm.getId() != null) {
            Book book = bookService.findById(bookForm.getId());
            model.addAttribute("book", book);
        }

        //一覧表示ボタンが押されると本一覧をmodelに登録。
        if(showList != null) {
            List<Book> bookList = bookService.getBookList();
            model.addAttribute("bookList", bookList);
        }

        return "index";

    }

}

画面

th:ifでControllerのModelに登録したオブジェクトがnullかどうかで表示する内容が変わります。

index.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
  <head>
    <title th:text="${title}">title</title>
    <link href="/css/style.css" rel="stylesheet">
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  </head>
  <body>
    <p>本屋さん</p>
    <form action="/book/search" method="post">
        <label>ID:<input class="input" type="text" name="id"></label><br>
        <div><input class="search" type="submit" value="検索"/></div>
    </form>
    <form action="/book/search" method="post">
        <div><input class="list" type="submit" name="showList" value="一覧表示"/></div>
    </form>

    <div th:if="${book} !=null" th:object="${book}">
        <table>
            <tr>
                <th>ID</th>
                <th>書籍名</th>
                <th></th>
                <th>著者名</th>
                <th>刊行日</th>
            </tr>
            <tr>
                <td th:text="*{id}">id</td>
                <td th:text="*{bookName}">書籍名</td>
                <td th:text="*{volumeNum}"></td>
                <td th:text="*{authorName}">著者名</td>
                <td th:text="*{publishedDate}">刊行日</td>
            </tr>
        </table>
    </div>

        <div th:if="${bookList} !=null">
        <table>
            <tr>
                <th>ID</th>
                <th>書籍名</th>
                <th></th>
                <th>著者名</th>
                <th>刊行日</th>
            </tr>
            <tr th:each="book:${bookList}" th:object="${book}">
                <td th:text="*{id}">id</td>
                <td th:text="*{bookName}">書籍名</td>
                <td th:text="*{volumeNum}"></td>
                <td th:text="*{authorName}">著者名</td>
                <td th:text="*{publishedDate}">刊行日</td>
            </tr>
        </table>
    </div>
  </body>
</html>

CSSよく分かりません。タグを[type=" "]で区別できたり、table thとスペース区切りで子要素を指定できたりする、ということはわかりました。。。

style.css
@charset "UTF-8";

input[type="text"]{
    width:70%;
    border-radius: 5px;
    padding: 10px;
}
input[type="submit"].search{
    border-radius: 5px;
    padding: 5px;
    margin-top: 10px;
    background-color:#99CCCC;
}

input[type="submit"].list{
    border-radius: 5px;
    padding: 5px;
    margin-top: 10px;
    background-color: #008BBB;
    color:#FFFFFF
}

table{
    width: 100%;
    margin-top: 10px;
    border-collapse: collapse;
}
table th, table td {
    border: 1px solid #ddd;
    padding: 6px;
}
table th {
  background-color: #6699FF;
}
14
21
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
14
21