LoginSignup
2

More than 5 years have passed since last update.

flywayのサンプル

Last updated at Posted at 2016-01-02

目的

Frywayを利用したDBの利用法の最小構成を整理する。

環境

Java8
IDE STS
マイグレーションツール Flyway
O/Rマッパー Spring Data JPA
DB H2Database

手順

プロジェクト

スクリーンショット 2016-01-02 16.11.17.png

pom

DB周りに関わる依存性として、以下の内容を追記する。

pom.xml
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
        </dependency>

画面

テンプレートエンジン
Thymeleafを利用

list.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>top page</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="stylesheet" type="text/css" href="../../static/css/style.css" th:href="@{/css/style.css}" />
<link rel="stylesheet" type="text/css" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css" th:href="@{/webjars/bootstrap/3.2.0/css/bootstrap-theme.min.css}" />
</head>
<body>
<div class="container">
    <h1>テスト</h1>
    <!-- 登録を行う  -->
    <div class="col-sm-12">
    <!-- th:objectでは、フォームクラス名の頭小文字を指定すると、フォームオブジェクトとHTMLフィールドをバインドでき、
    このクラスのフィールド名で *{フィールド名}で値を呼び出せる -->
        <form th:action="@{/testdb/create}" th:object="${testForm}"
            method="post">
            <fieldset>
            <legend>登録</legend>
            <div class="form-group" th:classappend="${#fields.hasErrors('col01')}? 'has-error has-feedback'">
                <label for="col01" class="col-sm-2 control-label">col01</label>
                <div class="col-sm-10">
                    <input type="text" id="col01" name="col01" th:field="*{col01}" class="form-control" value="col01" />
                    <span th:if="${#fields.hasErrors('col01')}" th:errors="*{col01}" class="help-block">error!</span>
                </div>
            </div>
            <div class="form-group" th:classappend="${#fields.hasErrors('col02')}? 'has-error has-feedback'">
                <label for="col02" class="col-sm-2 control-label">col02</label>
                <div class="col-sm-10">
                    <input type="text" id="col02" name="col02" th:field="*{col02}" class="form-control" value="col02" />
                    <span th:if="${#fields.hasErrors('col02')}" th:errors="*{col02}" class="help-block">error!</span>

                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-offset-2 col-sm-10">
                    <button type="submit" class="btn btn-primary">作成</button>
                </div>
            </div>
            </fieldset>
        </form>

        <ht/>

        <!-- DB登録済みの内容をリストに書き出す  -->

        <table class="table table-striped table-bordered table-condensed">
            <tr>
                <th>ID</th>
                <th></th>
                <th></th>
                <th colspan="2">編集</th>
            </tr>
            <!-- ${オブジェクト}は、コントローラ側で(model.addAttribute("testdata", view);)で指定した名前に紐付いたデータがとれる -->
            <tr th:each="test : ${testdata}">
            <!-- eachで、testに写し、一つずつ取り出せる -->
                <td th:text="${test.id}">100</td>
                <td th:text="${test.col01}">001</td>
                <td th:text="${test.col02}">002</td>
                <td>
                    <form th:action="@{/testdb/delete}" method="post">
                        <input class="btn btn-danger" type="submit" value="削除" />
                        <!-- 繰り返された一つのtestオブジェクトのidをvalueにわたし、コントローラに渡す -->
                        <input type="hidden" name="id" th:value="${test.id}" />
                    </form>
                </td>
            </tr>
        </table>
    </div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" th:src="@{/webjars/jquery/1.11.1/jquery.min.js}">
</script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js" th:src="@{/webjars/bootstrap/3.2.0/js/bootstrap.min.js}"></script>

</body>
</html>

データベース

データベースの設定
H2データベースの作成位置を指定する。

property.yml
spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:file:/tmp/test_tbl #
    username: sa
    password:
  jpa:
    hibernate.ddl-auto: validate #
  thymeleaf.cache: false

コントローラ

TestController
package com.example.web;

import java.util.List;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
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;
import org.springframework.web.bind.annotation.RequestParam;

import com.example.domain.Test;
import com.example.service.TestService;

@Controller
@RequestMapping("testdb") //ルートとなるパスを指定している
public class TestController {

    @Autowired
    TestService testservice;

    @ModelAttribute
    TestForm setUpForm() {
        //登録時データ受け渡し用のフォーム
        return new TestForm();
    }

    @RequestMapping(method = RequestMethod.GET)
    String list(Model model) {
        // サービスから取得した全件データを画面側へ送る
        List<Test> view = testservice.findAll();
        model.addAttribute("testdata", view);
        return "views/list";
    }

    // testdb/create へPOSTメソッドで呼び出した場合ここが呼び出される
    @RequestMapping(value = "create", method = RequestMethod.POST) 
    String create(@Validated TestForm form, BindingResult result, Model model) {
        // @Validatedアノテーションで、フォームに指定されていた制約をチェックできる。結果はresultに入る
        if (result.hasErrors()) {
            return list(model);
        }
        // 登録データの受け渡しフォームを作成し
        Test testdata = new Test();
        // サービスから登録する
        BeanUtils.copyProperties(form, testdata);//左から右に値をコピー
        testservice.create(testdata);
        //ルートへリダイレクトする
        return "redirect:/testdb";
    }   
    @RequestMapping(value = "delete", method = RequestMethod.POST)
    String delete(@RequestParam Integer id){
        testservice.delete(id);
        return "redirect:/testdb";
    }
}

フォームクラス

画面からデータ登録時に受け渡しに利用するフォームクラス

TestForm.java
package com.example.web;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import lombok.Data;

@Data
public class TestForm {
    @NotNull
    @Size(min=1, max=10)
    private String col01;
    @NotNull
    @Size(min=1, max=10)
    private String col02;
}

サービス

TestService.java
package com.example.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.domain.Test;
import com.example.repository.TestRepository;

@Service
@Transactional
public class TestService {
    @Autowired
    TestRepository TestRepository;

    public Test save(Test Test){
        return TestRepository.save(Test);
    }
    public List<Test> findAll(){
        return TestRepository.findAll();
    }
    public Test findOne(Integer id){
        return TestRepository.findOne(id);
    }
    public Test create(Test test){
        return TestRepository.save(test);
    }
    public Test update(Test test){
        return TestRepository.save(test);
    }
    public void delete(Integer id){
        TestRepository.delete(id);
    }
}

※ここでAutowierdによるインジェクションに失敗するエラーがでてうまくいかなかった。
 原因はこのサービスが利用しているrepositoryクラスでエラーが起きていた。
 エラーは、repositoryが利用していたTestクラス(ドメインのクラス)が問題があった
 ドメインのクラスが、@Entityになっていなかったため(javax.persistance.Entityじゃなかった)、
 DBとフィールドがバンドできていなかった。

DBの操作

リポジトリ

 SpringJPA の用意するインターフェースを継承するだけで、基本的なCLUD操作が使えるようになる

TestRepository
package com.example.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.example.domain.Test;

@Repository
@Transactional
public interface TestRepository extends JpaRepository<Test, Integer>{
}

ドメインクラス

@Entityにより、DBのテーブルとフィールドがバインドされている。
@DataアノテーションはLambokによるもので、アクセッサを自動生成している。
その下でコンストラクタも自動生成されている

Test
package com.example.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity // javax.persistance.Entityの@Entityのアノテーションを使う、hibernateの@Entityではない
@Table(name = "test_tbl")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Test {
    @Id
    @GeneratedValue
    private Integer id;
    @Column(nullable = false)
    private String col01;
    @Column(nullable = false)
    private String col02;

}

マイグレーション

スクリーンショット 2016-01-02 16.26.55.png

以下に
db/migration
ディレクトリを用意し、そこにマイグレーションファイルを配置する

V1(連番)__(アンダーバー二つ)任意のファイル名.sql
のルールでファイルを用意する

スクリプトを追加する場合は、V以降の番号を繰り上げたファイルを同じディレクトリに配置する

ここでは、テーブル定義を行う最初のDDLを実行している

V1__create-table.sql
CREATE TABLE test_tbl (id INT PRIMARY KEY AUTO_INCREMENT, col01 VARCHAR(30), col02 VARCHAR(30));

・バージョン番号後のアンダーバーは二つ必要
・一度流したスクリプトは変更しない、変えたければALTERTABLEとかを流す
・追加のスクリプトは、バージョンを繰り上げないと流れない、エラーとなる

ブラウザより
localhost:8080/testdb/
へリクエスト

以下のようにルートの画面へ遷移が行われる

スクリーンショット 2016-01-02 22.27.11.png

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
2