テーブルの作成・データの追加
src/main/resources
直下に2つのSQL文を作成。ファイルが2つに分かれている理由の1つが、ファイルを分けることで、実行順序を明示し、テーブルが適切に作成された後にレコードが追加されるようにするため。
この2つのSQL文はアプリが立ち上がるたびに自動で処理されるため、各SQL文でエラー回避の工夫が必要。
役割は以下の通り。
1.schema.sql
テーブルを作成する。
CREATE TABLE IF NOT EXISTS houses (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
image_name VARCHAR(255),
description VARCHAR(255) NOT NULL,
price INT NOT NULL,
capacity INT NOT NULL,
postal_code VARCHAR(50) NOT NULL,
address VARCHAR(255) NOT NULL,
phone_number VARCHAR(50) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
ポイント:CREATE TABLE IF NOT EXISTS テーブル名
で「もしその名前のテーブルが存在しなければ」という条件をつけている。これは作成済みのテーブルを再度作成しようとしたときに発生するエラーを防ぐ。
テーブルを作成した後に必須の作業=モデルの作成
モデルはデータベースの中に作成したテーブルごとに作成しなければいけないので、モデルをこのSQLのテーブルと対応させる作業がいる!
モデルの設定方法
2.data.sql
レコードの追加などに使う。
-- housesテーブル
INSERT IGNORE INTO houses (id, name, image_name, description, price, capacity, postal_code, address, phone_number) VALUES (1, 'SAMURAIの宿', 'house01.jpg', '最寄り駅から徒歩10分。自然豊かで閑静な場所にあります。長期滞在も可能です。', 6000, 2, '073-0145', '北海道砂川市西五条南X-XX-XX', '012-345-678');
INSERT IGNORE INTO houses (id, name, image_name, description, price, capacity, postal_code, address, phone_number) VALUES (2, 'ペンション SAMURAI', 'house02.jpg', '最寄り駅から徒歩10分。自然豊かで閑静な場所にあります。長期滞在も可能です。', 7000, 3, '030-0945', '青森県青森市桜川X-XX-XX', '012-345-678');
INSERT IGNORE INTO houses (id, name, image_name, description, price, capacity, postal_code, address, phone_number) VALUES (3, 'SAMURAI荘', 'house03.jpg', '最寄り駅から徒歩10分。自然豊かで閑静な場所にあります。長期滞在も可能です。', 8000, 4, '029-5618', '岩手県和賀郡西和賀町沢内両沢X-XX-XX', '012-345-678');
ポイント:INSERT IGNORE INTO テーブル名
で重複や制約違反が発生した場合でもエラーが発生せず、レコードの追加をスキップする。もし、重複や制約違反が発生しない場合は通常どおりレコードを挿入。
モデルの作成
別記事に記載
モデルの設定方法
ここまでで、データベースに"house"という名前の1つのテーブルが完成しています。
コントローラの作成
下記の場所へ、AdminHouseController.java
という名前でコントローラ設定ファイルを作成。
ファイルの内容↓
package com.example.samuraitravel.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.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.samuraitravel.entity.House;
import com.example.samuraitravel.repository.HouseRepository;
@Controller // コントローラの宣言
@RequestMapping("/admin/houses") //このコントローラが"/admin/houses" パス以下のリクエストを処理する
public class AdminHouseController { //クラス定義
private final HouseRepository houseRepository; //依存先のオブジェクトをfinalで宣言
@Autowired //コンストラクタインジェクションで依存性注入
public AdminHouseController(HouseRepository houseRepository) {
this.houseRepository = houseRepository;
}
@GetMapping //index()でhouseRepositoryのデータ取得
public String index(Model model) {
List<House> houses = houseRepository.findAll();
//ビューに"house"という名前でhouseデータを渡す
model.addAttribute("houses", houses);
return "admin/houses/index";
}
}
ポイント:
- クラスに
@RequestMapping
アノテーションをつけ、ルートパスの基準値を設定する - コンストラクタで依存性の注入(DI)を行う(コンストラクタインジェクション)
-
HouseRepository
インターフェースのfindAll()
メソッドですべての民宿データを取得する -
Model
クラスを使ってビューにデータを渡す。
コントローラからビューにデータを渡す場合、Model
クラスを使う。
@GetMapping
public String index(Model model) {
List<House> houses = houseRepository.findAll();
model.addAttribute("var", houses);
return "admin/houses/index";
}
この例では、
- index()メソッド内ではHouseRepositoryインターフェースのfindAll()メソッドを使い、すべての民宿データを取得
- メソッドにModel型の引数を指定する
- メソッド内でaddAttribute()メソッドを使い、以下の引数を渡す
第1引数:ビュー側から参照する変数名
第2引数:ビューに渡すデータ
admin/houses/index.htmlファイル内でvarという変数を使うことで、コントローラから渡されたhousesというデータの中身を参照できる。
HouseRepository
インターフェースとは→リポジトリの設定
ビューの作成
- ビューファイルはsrc/main/resources/templatesフォルダ以下に配置する。理由はデフォルトでThymeleafのビューリゾルバーによって認識されるから。
indexファイルの作成・編集
以下のようにadmin
フォルダ新規作成、houses
フォルダ新規作成、そしてindex.html
ファイルを新規作成する。
ファイルの作成は以下の通り。
+<!DOCTYPE html>
+<html xmlns:th="https://www.thymeleaf.org">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <!-- Bootstrap -->
+ <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous">
+
+ <!-- Google Fonts -->
+ <link rel="preconnect" href="https://fonts.googleapis.com">
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+ <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;500&display=swap" rel="stylesheet">
+
+ <link th:href="@{/css/style.css}" rel="stylesheet" >
+
+ <title>民宿一覧</title>
+ </head>
+ <body>
+ <div class="samuraitravel-wrapper">
+ <!-- ヘッダー -->
+ <nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm samuraitravel-navbar">
+ <div class="container samuraitravel-container">
+ <a class="navbar-brand" th:href="@{/}">
+ <img class="samuraitravel-logo me-1" th:src="@{/images/logo.png}" alt="SAMURAI Travel">
+ </a>
+ </div>
+ </nav>
+
+ <main>
+ <div class="container pt-4 pb-5 samuraitravel-container">
+ <div class="row justify-content-center">
+ <div class="col-xxl-9 col-xl-10 col-lg-11">
+
+ <h1 class="mb-4 text-center">民宿一覧</h1>
+
+ <div class="d-flex justify-content-end">
+ <a href="#" class="btn text-white shadow-sm mb-3 samuraitravel-btn">登録</a>
+ </div>
+
+ <table class="table">
+ <thead>
+ <tr>
+ <th scope="col">ID</th>
+ <th scope="col">民宿名</th>
+ <th scope="col">郵便番号</th>
+ <th scope="col">住所</th>
+ <th scope="col">電話番号</th>
+ <th scope="col"></th>
+ </tr>
+ </thead>
+ <tbody>
+ <tr th:each="house : ${houses}">
+ <td th:text="${house.getId()}"></td>
+ <td th:text="${house.getName()}"></td>
+ <td th:text="${house.getPostalCode()}"></td>
+ <td th:text="${house.getAddress()}"></td>
+ <td th:text="${house.getPhoneNumber()}"></td>
+ <td><a href="#">詳細</a></td>
+ </tr>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+ </main>
+
+ <!-- フッター -->
+ <footer>
+ <div class="d-flex justify-content-center align-items-center h-100">
+ <p class="text-center text-muted small mb-0">© SAMURAI Travel All rights reserved.</p>
+ </div>
+ </footer>
+ </div>
+
+ <!-- Bootstrap -->
+ <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4" crossorigin="anonymous"></script>
+ </body>
+</html>
ポイント:
- CDNでBootstrapを導入する
- CDNでGoogle Fontsを導入する
- aタグのth:href属性でリンク先のルートパスを指定する
- imgタグのth:src属性で表示するロゴ画像のルートパスを指定する
- th:each属性で繰り返し処理を行う
CSSファイルの作成・編集
Spring Bootでは、静的なファイル(CSS、JavaScript、画像など)は通常、src/main/resources/static フォルダ内に配置する。
+html {
+ font-size: 14px;
+}
+
+body {
+ font-family: 'Noto Sans JP', sans-serif;
+}
+
+h1 {
+ font-size: 1.5rem;
+}
+
+a {
+ color: #5196a6;
+ text-decoration: none !important;
+}
+
+a:hover {
+ color: #407784;
+}
+
+footer {
+ width: 100%;
+ height: 60px;
+ position: absolute;
+ bottom: 0;
+ background-color: #f2f0eb;
+}
+
+.samuraitravel-wrapper {
+ min-height: 100vh;
+ position: relative;
+ padding-bottom: 60px;
+ box-sizing: border-box;
+}
+
+.samuraitravel-navbar {
+ min-height: 60px;
+}
+
+.samuraitravel-logo {
+ height: 30px;
+}
+
+.samuraitravel-btn {
+ background-color: #5196a6 !important;
+ transition: 0.1s;
+}
+
+.samuraitravel-btn:hover {
+ opacity: 0.8;
+}
+
+@media screen and (min-width: 1400px) {
+ .samuraitravel-container {
+ max-width: 1200px;
+ }
+}
ここまでの作業まとめ
この工程を経て、アプリを起動後、http://localhost:8080/admin/houses
にアクセスすると下記のようなページが表示されるはず。