SpringBootでMyBatis使ってみた

Java

前回、JPARepositoryについて簡単な記事を書いたのですが

使ってみてやっぱり個人的にはMyBatisの方が好きなんでその紹介を兼ねて使ってみたいと思います。

JPARepositoryはORMでオブジェクトとRDBのテーブルを紐付けるフレームワークです。これも便利なんですが、既にテーブル設計ができていたりしてそのテーブルがオブジェクト指向で考えられてなかった場合は、アプリ側でそこを吸収してやらないといけなかったり

SQLを自動生成して実行するため、処理速度が遅かったりテーブル間結合がややこしく、SQL文を少しはやってきた私としてはJPAは使いにくかったです。

その点、

MyBatisはSQL Mapperと言われることもあって、SQL文とオブジェクトを紐付けてデータアクセスを行うフレームワークになります。

どういうことかというと、例えば、SELECT文で複数のテーブルを結合していくつかのカラムを取得するとします。その取得したカラムをフィールド変数にもつJavaクラスを作成し、紐づけることで、フィールド変数に取得した値をもつインスタンスが自動生成されます。これ、めちゃ画期的です!

いちいち、カラム1個ずつ値を取得してDataObjectみたいなオブジェクトに格納して、レスポンス返すときに取り出してってことをやらなくていいわけですから!

しかも!テーブル間結合はSQL文で書けるため、SQL文書いてきた人からすると使いやすいったらありゃしない

前置きは、このぐらいにしてMyBatisの使い方をみていきましょう!

前述した通り、MyBatisはSQL文とJavaクラスを紐づけて使用するんですが、その紐付け方が2通りあります。

・マッピングファイルによる紐付け

・アノテーションによる紐付け

今回は、マッピングファイルによる紐付けを紹介するので、アノテーションによる紐付けを軽く説明すると、

@Select("SELECT id, name, age FROM USER_TABLE")
List<User> getAllUsers()

こんな感じで、アノテーションにSQL文を書き込んでしまうやり方です。

簡単なSQL文だったらこれで事足りるんですが、複雑なSQL文になってくるとStringを+で繋いでくことになるため、可読性が低くなってしまいます。個人的にあまり好きじゃない。

一方、マッピングファイルによる紐付けは、あとで詳しく説明しますが、xmlファイルにSQL文を外だしできます!

さらなる朗報として、SpringBootでMyBatisを使うと

SqlSessionTemplateのBean定義なんかもこっちがやる必要はなく全て自動でやってくれます!!最高すぎかよ!

ではでは、早速作っていきましょう!

手順は以下の通り。

1:dependencyの追加

2:DB関連の設定

3:Entityクラスの作成

4:Mapperインターフェースの定義とマッピングファイルの作成

5:Mapperインターフェースの利用

6:テンプレートの作成

1:dependencyの追加

pom.xmlにdependencyを追加していきます。

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.1.0</version>
		</dependency>

		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

特筆すべきは「mybatis-spring-boot-starter」です!

これのおかげで、SqlSession関連の定義を自動で行うことができます!

あとは、よく見かけるものばかりです。

あ、ちなみに今回もDBはH2を使います。

2:DB関連の設定

次にDB関連の設定をしていきます。

まず、application.propertiesです。

spring.datasource.url=jdbc:h2:./h2db/sampledb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=soloware
spring.datasource.password=soloware
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# Enabling H2 Console
spring.h2.console.enabled=true
 
# Custom H2 Console URL
spring.h2.console.path=/h2

H2の設定です。

DBのデータを保存するためのファイルを「main/resources/h2db」配下に「sampledb」として置いておきます。これ忘れるとアプリが起動しないので注意。

で、スキーマと初期データを登録するために、「main/resources」配下にそれぞれ、「schema.sql」と「data.sql」をおきます。

中身はこんな感じ。

-- schema.sql
DROP TABLE IF EXISTS USER_TABLE;
  
CREATE TABLE USER_TABLE (
  id INT AUTO_INCREMENT  PRIMARY KEY,
  user_name VARCHAR(250) NOT NULL,
  phone VARCHAR(14) DEFAULT NULL,
  age Number(200) DEFAULT NULL
);
-- data.sql
INSERT INTO USER_TABLE (user_name, phone, age) VALUES
  ('Soloware', '08000000000', 20),
  ('Tcc', '08011111111', 30),
  ('Test', '08022222222', 40);

3:Entityクラスの作成

次にEntityクラスを作ります

このクラスが、MyBatisで実行するSQL文と紐付くクラスとなります。

package com.soloware.mybatis.entity;

import javax.validation.constraints.NotNull;

import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;

public class User {

    @NotNull
    private int id;

    @NotEmpty
    private String user_name;

    @Length(min=11, max=14)
    private String phone;

    private int age;

    /**
     * @return the id
     */
    public int getId() {
        return id;
    }

    /**
     * @param id the id to set
     */
    public void setId(int id) {
        this.id = id;
    }


    /**
     * @return the phone
     */
    public String getPhone() {
        return phone;
    }

    /**
     * @param phone the phone to set
     */
    public void setPhone(String phone) {
        this.phone = phone;
    }

    /**
     * @return the age
     */
    public int getAge() {
        return age;
    }

    /**
     * @param age the age to set
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * @return the user_name
     */
    public String getUser_name() {
        return user_name;
    }

    /**
     * @param user_name the user_name to set
     */
    public void setUser_name(String user_name) {
        this.user_name = user_name;
    }
}

フィールド変数についているアノテーションはバリデーション用のものです。詳しくは、「SpringBoot JPARepositoryを使ってみた」をみて下さい。

4:Mapperインターフェースの定義とマッピングファイルの作成

ここからが本番です。

まず、Mapperインターフェースを作ります。

package com.soloware.mybatis.mapper;

import java.util.List;

import com.soloware.mybatis.entity.User;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper{
    List<User> getAll();  
    
    void register(User user);
}

これだけでーす笑

@Mapperアノテーションをつけることで、SpringBootはこのクラスがMapperインターフェースだな!と判断します。MapperScanとかもしなくていいんですねー!楽チンだ!

で、実処理も書かないし、マッピングファイルによる紐付けを行うため、SQL文もここには書きません!

この次が大事なステップになります!マッピングファイルの作成です

何が大事かというと、このMapperインターフェースが存在するディレクトリ構成と全く同じディレクトリ構成を「main/resouces」配下に作成し、そこに同じファイル名で拡張子をxmlのファイルを作成します!

今回だと、こうです。

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.soloware.mybatis.mapper.UserMapper">
    <select id="getAll" resultType="com.soloware.mybatis.entity.User">
        SELECT * FROM USER_TABLE
    </select>
    <insert id="register" useGeneratedKeys="true" keyProperty="id">
     INSERT INTO USER_TABLE (user_name, phone, age) VALUES (#{user_name}, #{phone}, #{age})
   </insert>
</mapper>

今回は、update,deleteは割愛します。

mapperタグのnamespace属性にマッピングするMapperインターフェースの名前空間を指定します。

その中にCRUDタグをつけていくって流れ。

このタグ内のid属性が、Mapperインターフェースのメソッド名になります。

続くresultTypeが紐づけるJavaクラス( Entity)の名前空間です。

で、タグ内にはSQL文をそのまま記述できます。

insertタグには、useGeneratedKeysとkeyPropertyって属性がありますね。

これは、主キーを自動生成しますということと

USRE_TABLEが持つidカラムが主キーですよっていう設定をしているんですね。

ちなみに、このINSERT分には、#{}で囲まれた値が3つありますよね。

これは何かというと、Mapperインターフェースを思い出して欲しいんですが

void register(User user);

これ!引数にUserクラスを渡していますよね。Userクラスはフィールド変数に「id」「user_name」「phone」「age」を持っています。

idは自動生成なので、パス。そのほかの変数の値を#{}で参照できるんです!

このようにMapperインターフェースのメソッドの引数に値を渡してSQL文で使うことも簡単です。

5:Mapperインターフェースの利用

作成したMapperインターフェースの利用はこんな感じ

package com.soloware.mybatis.repository;

import java.util.List;

import com.soloware.mybatis.entity.User;

public interface UserRepository {

    void register(User user);

    List<User> getAll();
}
package com.soloware.mybatis.repository;

import java.util.List;

import com.soloware.mybatis.entity.User;
import com.soloware.mybatis.mapper.UserMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class UserRepositoryDAO implements UserRepository {

    @Autowired
    UserMapper userMapper;

    @Override
    public List<User> getAll() {
        return userMapper.getAll();
    }

    @Override
    public void register(User user) {
        userMapper.register(user);
    }

}

フィールドにMapperインターフェースをDIして使うんですね!

簡単すぎワロタ

あとは、このDAOクラスをリクエストハンドラーの「MainController」から呼びます。

package com.soloware.mybatis.controller;

import java.util.List;

import javax.xml.ws.BindingType;

import com.soloware.mybatis.entity.User;
import com.soloware.mybatis.repository.UserRepositoryDAO;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class MainController {

    @Autowired
    UserRepositoryDAO repository;

    @RequestMapping("/")
    public String index(){
        return "index";
    }

    @RequestMapping(value="/form", method=RequestMethod.GET)
    public String form(@ModelAttribute("userEntity") User user){
        return "form";
    }

    @RequestMapping(value="/register", method=RequestMethod.POST)
    @Transactional(readOnly=false)
    public String register(@ModelAttribute("userEntity") @Validated  User user,
                            BindingResult result, Model model){
        if(result.hasErrors()){
            model.addAttribute("msg", "There gotta be something wrong");
            return "form";
        }else{
            repository.register(user);
            model.addAttribute("msg", "Successfully registered your data");
            return "result";
        }
    }

    @RequestMapping("/all")
    public String all(Model model){
        List<User> list = repository.getAll();
        model.addAttribute("datalist", list);
        return "all";
    }
}

このクラスで使用しているアノテーションに関しては、「SpringBoot JAPRepository 使ってみた」記事で説明しているので、今回は割愛!

DAOクラスをDIして、使用しているってとこがミソ。

6:テンプレートの作成

index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <a href="form">Register A New User</a>
    <br>
    <hr>
    <a href="all">Get All Users</a>
</body>
</html>

form.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Form</title>
    <style>
        .err{
            color : red;
        }
    </style>
</head>
<body>
    <h2>Form Page</h2>
    <span th:text="${msg}"></span>
    <table>
        <form action="register" method="POST" th:object="${userEntity}"> 
            <tr>
                <td><label for="user_name">UserName</label></td>
                <td>
                    <input type="text" name="user_name" th:value="*{user_name}" th:errorclass="err">
                    <div th:if="${#fields.hasErrors('user_name')}" th:errors="*{user_name}" th:errorclass="err"></div>
                </td>
            </tr>
            <tr>
                <td><label for="phone">Phone</label></td>
                <td>
                    <input type="text" name="phone" th:value="*{phone}" th:errorclass="err">
                    <div th:if="${#fields.hasErrors('phone')}" th:errors="*{phone}" th:errorclass="err"></div>
                </td>
            </tr>
            <tr>
                <td><label for="age">Age</label></td>
                <td>
                    <input type="number" name="age" th:value="*{age}" th:errorclass="err">
                    <div th:if="${#fields.hasErrors('age')}" th:errors="*{age}" th:errorclass="err"></div>
                </td>
            </tr>
            <tr>
                <td><input type="submit" value="Register"/></td>
            </tr>
        </form>
    </table>
</body>
</html>

result.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h2>Result Page</h2>
    <span th:text="${msg}"></span>
</body>
</html>

all.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
    <h2>All Page</h2>
    <table>
        <tr><th>id</th><th>name</th><th>phone</th><th>age</th></tr>
        <tr th:each="user : ${datalist}">
            <td th:text="${user.id}"></td>
            <td th:text="${user.user_name}"></td>
            <td th:text="${user.phone}"></td>
            <td th:text="${user.age}"></td>
        </tr>
    </table>
</body>
</html>

特筆すべきことは、何もないです笑

では、動かしてみます!

index.htmlはこんな感じです。

Get All UsersをクリックするとMapperインターフェースのgetAllメソッドが呼ばれます。

data.sqlにて初期登録したデータですね。

UIはめんどかったんで、スタイル何もいじってなくてダサいですが、悪しからず笑

次は、データを登録してみましょう。

Register A New Userをクリックしてデータを入力しRegisterボタンを押します

Get All Usersで確かめます!

増えてますね!

次は、あえてバリデーションエラーにしてみます!

    @NotEmpty
    private String user_name;

    @Length(min=11, max=14)
    private String phone;

なので、名前を空白にし、phoneを2桁とかにしてみます

はい、ちゃんとチェックされましたね!

この文字列を日本語にする方法とかは、「SpringBoot JPARepository使ってみた」という記事をみてください。

以上、MyBatisを使ってのデータアクセスでした!

BYE

タイトルとURLをコピーしました