目的
Frywayを利用したDBの利用法の最小構成を整理する。
環境
Java8
IDE STS
マイグレーションツール Flyway
O/Rマッパー Spring Data JPA
DB H2Database
手順
プロジェクト
pom
DB周りに関わる依存性として、以下の内容を追記する。
<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を利用
<!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データベースの作成位置を指定する。
spring:
datasource:
driverClassName: org.h2.Driver
url: jdbc:h2:file:/tmp/test_tbl #
username: sa
password:
jpa:
hibernate.ddl-auto: validate #
thymeleaf.cache: false
コントローラ
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";
}
}
フォームクラス
画面からデータ登録時に受け渡しに利用するフォームクラス
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;
}
サービス
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操作が使えるようになる
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によるもので、アクセッサを自動生成している。
その下でコンストラクタも自動生成されている
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;
}
マイグレーション
以下に
db/migration
ディレクトリを用意し、そこにマイグレーションファイルを配置する
V1(連番)__(アンダーバー二つ)任意のファイル名.sql
のルールでファイルを用意する
スクリプトを追加する場合は、V以降の番号を繰り上げたファイルを同じディレクトリに配置する
ここでは、テーブル定義を行う最初のDDLを実行している
CREATE TABLE test_tbl (id INT PRIMARY KEY AUTO_INCREMENT, col01 VARCHAR(30), col02 VARCHAR(30));
・バージョン番号後のアンダーバーは二つ必要
・一度流したスクリプトは変更しない、変えたければALTERTABLEとかを流す
・追加のスクリプトは、バージョンを繰り上げないと流れない、エラーとなる
ブラウザより
localhost:8080/testdb/
へリクエスト
以下のようにルートの画面へ遷移が行われる