Edited at

flywayのサンプル

More than 3 years have passed since last update.


目的

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