この記事では、Spring Bootを用いたブログアプリケーションの構造と設定について詳しく解説します。フォルダ階層の整理、実際のコード例、各コードの役割、posts.html
の内容、そしてapplication.properties
の設定方法を学ぶことができます。
はじめに
前のページではデータベース作成、実行方法などを紹介しています。またJavaファイルの解説もしています。今回は、「posts.html」「application.properties」の解説をします。
CRUD操作のブログ投稿アプリ
フォルダ階層
blog/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── blog/
│ │ │ ├── BlogApplication.java // メインアプリケーションクラス
│ │ │ ├── controller/
│ │ │ │ └── PostController.java // コントローラー
│ │ │ ├── model/
│ │ │ │ └── Post.java // エンティティ
│ │ │ ├── repository/
│ │ │ │ └── PostRepository.java // リポジトリインターフェース
│ │ │ └── service/
│ │ │ ├── PostService.java // サービスインターフェース
│ │ │ └── /impl
│ │ │ └── PostServiceImpl.java // サービスの実装クラス
│ │ │
│ │ └── resources/
│ │ ├── static/ // 静的リソース(CSS, JSなど)
│ │ │ └── css/
│ │ │ └── styles.css
│ │ ├── templates/ // Thymeleafテンプレート
│ │ │ └── posts.html // 投稿用のHTMLテンプレート
│ │ └── application.properties // アプリケーション設定
│ └── test/
│ └── java/
│ └── com/
│ └── example/
│ └── blog/
│ ├── PostControllerTests.java // コントローラーのテストクラス
│ └── PostServiceImplTests.java // サービスのテストクラス
└── pom.xml // Mavenの設定ファイル
コード例
BlogApplication.java:アプリケーションのエントリーポイント
package com.example.blog;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication // Spring Bootアプリケーションのエントリーポイントとして機能
public class BlogApplication {
// アプリケーションのメインメソッド
public static void main(String[] args) {
// Spring Bootアプリケーションの実行を開始
SpringApplication.run(BlogApplication.class, args);
}
}
PostController.java:投稿の一覧表示、新規投稿の保存、編集、削除の各機能を提供し、ビュー(Webページ)とデータを結びつけます。
package com.example.blog.controller;
import java.util.List;
import java.util.Optional;
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.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.blog.model.Post;
import com.example.blog.service.PostService;
@Controller
@RequestMapping("/posts")
public class PostController {
@Autowired
private PostService postService; // PostServiceのインスタンスを注入
// 投稿一覧を表示するメソッド
@GetMapping
public String list(Model model) {
List<Post> posts = postService.findAll(); // 全ての投稿を取得
model.addAttribute("posts", posts); // 投稿リストをモデルに追加
model.addAttribute("post", new Post()); // 新規投稿用の空のPostオブジェクトを追加
return "posts"; // posts.htmlを返す
}
// 新規投稿または編集済み投稿を保存するメソッド
@PostMapping
public String save(@ModelAttribute Post post) {
postService.save(post); // 投稿を保存
return "redirect:/posts"; // 投稿一覧ページにリダイレクト
}
// 特定のIDの投稿を編集するメソッド
@GetMapping("/{id}/edit")
public String edit(@PathVariable Long id, Model model) {
Optional<Post> post = postService.findById(id); // IDで投稿を検索
post.ifPresent(value -> model.addAttribute("post", value)); // 投稿が存在する場合、モデルに追加
return "posts"; // posts.htmlを返す(編集フォームも同じビューで表示)
}
// 特定のIDの投稿を削除するメソッド
@PostMapping("/{id}/delete")
public String delete(@PathVariable Long id) {
postService.deleteById(id); // IDで投稿を削除
return "redirect:/posts"; // 投稿一覧ページにリダイレクト
}
}
Post.java:Spring BootアプリケーションでJPA(Java Persistence API)を使ってデータベースに保存される「Post(投稿)」のエンティティ(モデル)クラスです。
package com.example.blog.model;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity // JPAのエンティティとして、このクラスがデータベーステーブルに対応することを示す
@Table(name = "posts") // このエンティティがマッピングされるデータベースのテーブル名を指定
public class Post {
@Id // プライマリキーとして使用するフィールドを指定
@GeneratedValue(strategy = GenerationType.IDENTITY) // プライマリキーの生成戦略を「IDENTITY」に設定
private Long id;
@Column(nullable = false) // タイトルは必須フィールドとして、NULLを許可しない設定
private String title;
@Column(nullable = false) // コンテンツも必須フィールドとして、NULLを許可しない設定
private String content;
// ゲッターとセッター
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
PostRepository.java:Spring Data JPAを使ってブログ投稿をデータベースから取得したり保存したりするためのリポジトリ(Repository)を定義しています。
package com.example.blog.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.blog.model.Post;
@Repository // このインターフェースがリポジトリであることを示す
public interface PostRepository extends JpaRepository<Post, Long> {
// JpaRepository<Post, Long>を継承することで、Postエンティティに対する標準的なCRUD操作を利用可能
}
PostService.java:このインターフェースでは、ブログ投稿の操作に必要なメソッドを定義しています。
package com.example.blog.service;
import java.util.List;
import java.util.Optional;
import com.example.blog.model.Post;
public interface PostService {
/**
* データベース内のすべての投稿を取得します。
* @return 投稿のリスト
*/
List<Post> findAll();
/**
* 指定されたIDに基づいて、投稿を取得します。
* @param id 投稿のID
* @return 該当する投稿のOptionalオブジェクト(存在しない場合は空)
*/
Optional<Post> findById(Long id);
/**
* 新しい投稿を保存するか、既存の投稿を更新します。
* @param post 保存する投稿オブジェクト
*/
void save(Post post);
/**
* 指定されたIDに基づいて投稿を削除します。
* @param id 削除する投稿のID
*/
void deleteById(Long id);
}
PostServiceImpl.java:このクラスはPostService
インターフェースを実装し、実際のデータ操作を行います。
package com.example.blog.service.impl;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.example.blog.model.Post;
import com.example.blog.repository.PostRepository;
import com.example.blog.service.PostService;
@Service // サービス層を示すアノテーション。ビジネスロジックを担う。
public class PostServiceImpl implements PostService {
@Autowired // PostRepositoryインスタンスの依存性注入
private PostRepository postRepository;
@Override
public List<Post> findAll() {
// すべての投稿を取得し、リストとして返す
return postRepository.findAll();
}
@Override
public Optional<Post> findById(Long id) {
// 指定されたIDの投稿を取得し、Optionalでラップして返す
return postRepository.findById(id);
}
@Override
public void save(Post post) {
// 新規投稿を保存または既存投稿を更新する
postRepository.save(post);
}
@Override
public void deleteById(Long id) {
// 指定されたIDの投稿をデータベースから削除する
postRepository.deleteById(id);
}
}
styles.css:HTMLのデザインを整えています。
/* src/main/resources/static/css/styles.css */
:root {
--primary-bg-color: #f9f9f9; /* 背景色 */
--form-bg-color: #ffffff; /* フォームの背景色 */
--button-bg-color: #28a745; /* 通常ボタンの背景色 */
--button-hover-bg-color: #218838; /* ホバー時のボタン色 */
--delete-button-bg-color: #dc3545; /* 削除ボタンの色 */
--button-text-color: #ffffff; /* ボタンのテキスト色 */
}
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: var(--primary-bg-color);
}
h1, h2 {
text-align: center;
}
section {
max-width: 600px; /* 最大幅を設定 */
margin: 0 auto; /* 中央揃え */
padding: 20px;
background: var(--form-bg-color); /* 背景色をフォームに設定 */
border-radius: 5px; /* 角を丸くする */
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 影をつける */
}
form {
padding: 20px;
}
label {
display: block; /* ラベルをブロック要素にする */
margin-bottom: 5px; /* 下のマージン */
}
input[type="text"],
textarea {
width: 100%; /* 幅を100%に */
padding: 10px; /* 内側の余白 */
margin-bottom: 20px; /* 下のマージン */
border: 1px solid #ccc; /* 枠線 */
border-radius: 4px; /* 角を丸くする */
}
button {
padding: 10px 15px; /* 内側の余白 */
background-color: var(--button-bg-color); /* ボタンの背景色 */
color: var(--button-text-color); /* テキストの色 */
border: none; /* 枠線を消す */
border-radius: 5px; /* 角を丸くする */
cursor: pointer; /* カーソルをポインターに */
}
button:hover {
background-color: var(--button-hover-bg-color); /* ホバー時の色 */
}
ul {
list-style-type: none; /* リストのスタイルを消す */
padding: 0; /* パディングを消す */
max-width: 600px; /* 最大幅を設定 */
margin: 0 auto; /* 中央揃え */
}
li {
margin-bottom: 20px; /* 下のマージン */
padding: 10px; /* 内側の余白 */
background: var(--form-bg-color); /* 背景色を白に */
border-radius: 5px; /* 角を丸くする */
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.1); /* 影をつける */
}
h3 {
text-align: center; /* タイトルを中央揃え */
}
.delete-button {
background-color: var(--delete-button-bg-color); /* 削除ボタンの色 */
}
.delete-button:hover {
background-color: #c82333; /* ホバー時の色 */
}
.edit-link {
display: inline-block; /* インラインブロックにしてボタン風に */
padding: 10px 15px; /* 内側の余白 */
background-color: var(--button-bg-color); /* 通常ボタンの色 */
color: var(--button-text-color); /* テキストの色 */
border: none; /* 枠線を消す */
border-radius: 5px; /* 角を丸くする */
cursor: pointer; /* カーソルをポインターに */
text-decoration: none; /* 下線を消す */
}
.edit-link:hover {
background-color: var(--button-hover-bg-color); /* ホバー時の色 */
}
posts.html:HTML内にth:
で始まる特殊なタグを使って、サーバーサイドのデータを埋め込み、ページを動的に生成します。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>ブログ投稿</title>
<link rel="stylesheet" type="text/css" href="/css/styles.css">
<script>
// 投稿削除確認のポップアップ表示
function confirmDelete() {
return confirm("本当にこの投稿を削除しますか?");
}
</script>
</head>
<body>
<h1>ブログ投稿</h1>
<div class="container">
<!-- 投稿の新規作成または編集用セクション -->
<section>
<form th:action="@{/posts}" th:object="${post}" method="post">
<!-- 投稿IDを隠しフィールドとして保持(新規作成時はnull) -->
<input type="hidden" th:field="*{id}" />
<!-- 新規投稿時と編集時で見出しを切り替え -->
<h2 th:if="${post.id == null}">新規投稿</h2>
<h2 th:if="${post.id != null}">投稿の編集</h2>
<!-- タイトル入力欄 -->
<label>タイトル:</label>
<input type="text" th:field="*{title}" required /> <!-- 必須項目 -->
<!-- 内容入力欄 -->
<label>内容:</label>
<textarea th:field="*{content}" required></textarea> <!-- 必須項目 -->
<!-- 新規保存ボタンまたは更新ボタンを表示 -->
<button type="submit" th:text="${post.id == null} ? '投稿を保存' : '投稿を更新'"></button>
<!-- 編集時のみ表示される投稿一覧に戻るリンク -->
<a th:if="${post.id != null}" th:href="@{/posts}" style="margin-left: 10px;">投稿一覧に戻る</a>
</form>
</section>
<!-- 投稿一覧セクション(新規投稿時のみ表示) -->
<h2 th:if="${post.id == null}">投稿一覧</h2>
<ul>
<!-- 投稿リストの表示。投稿ごとにタイトル、内容、編集・削除リンクを含む -->
<li th:each="post : ${posts}">
<h3 th:text="${post.title}">投稿タイトル</h3>
<p th:text="${post.content}">投稿内容</p>
<!-- 編集リンク。選択した投稿の編集画面に遷移 -->
<a th:href="@{/posts/{id}/edit(id=${post.id})}" class="edit-link">編集</a>
<!-- 削除フォーム。確認ポップアップを表示してから削除 -->
<form th:action="@{/posts/{id}/delete(id=${post.id})}" method="post" style="display:inline;" onsubmit="return confirmDelete();">
<button type="submit" class="delete-button">削除</button>
</form>
</li>
</ul>
</div>
</body>
</html>
application.properties:データベース接続情報やサーバーポート、ログ設定など、アプリケーションの構成情報を管理します
spring.application.name=blog
spring.datasource.url=jdbc:postgresql://localhost:5432/blogdb
spring.datasource.username=${POSTGRESQL_DB_USERNAME}
spring.datasource.password=${POSTGRESQL_DB_PASSWORD}
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.thymeleaf.cache=false
pom.xml:
pom.xml
はMavenによるプロジェクト管理において不可欠なファイルであり、依存関係、ビルド設定、プラグインなどを一元管理することで、開発者が効率的にプロジェクトをビルド、テスト、デプロイできるようにします。
コードの解説
posts.htmlの解説
このHTMLコードは、ブログ投稿の一覧表示や新規作成・編集・削除ができるページを、Thymeleafテンプレートエンジンを使用して作成しています。Thymeleaf
は、HTML内にth:
で始まる特殊なタグを使って、サーバーサイドのデータを埋め込み、ページを動的に生成します。
HTMLの基本構造とCSS・JavaScriptの設定
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>ブログ投稿</title>
<link rel="stylesheet" type="text/css" href="/css/styles.css">
<script>
// 投稿削除確認のポップアップ表示
function confirmDelete() {
return confirm("本当にこの投稿を削除しますか?");
}
</script>
</head>
<body>
<!DOCTYPE html>
:HTMLのバージョンを指定する宣言です。<html xmlns:th="http://www.thymeleaf.org">
:Thymeleafを使用するための宣言です。これにより、th:
で始まる属性を使えるようになります。<link rel="stylesheet" type="text/css" href="/css/styles.css">
:スタイルシートstyles.css
を読み込んで、ページの見た目を整えます。<script> function confirmDelete() ... </script>
:削除ボタンを押したときに確認のポップアップを表示するJavaScript関数です。
新規投稿または編集用のフォーム
<div class="container">
<section>
<form th:action="@{/posts}" th:object="${post}" method="post">
<input type="hidden" th:field="*{id}" />
<h2 th:if="${post.id == null}">新規投稿</h2>
<h2 th:if="${post.id != null}">投稿の編集</h2>
<label>タイトル:</label>
<input type="text" th:field="*{title}" required />
<label>内容:</label>
<textarea th:field="*{content}" required></textarea>
<button type="submit" th:text="${post.id == null} ? '投稿を保存' : '投稿を更新'"></button>
<a th:if="${post.id != null}" th:href="@{/posts}" style="margin-left: 10px;">投稿一覧に戻る</a>
</form>
</section>
<form th:action="@{/posts}" th:object="${post}" method="post">
:フォームの送信先を/posts
に設定します。th:object="${post}"
で、post
オブジェクトを使ってフォーム内のデータをバインドします。<input type="hidden" th:field="*{id}" />
:投稿IDを保持する隠しフィールド。新規投稿時には空のまま、編集時には投稿のIDが入ります。<h2 th:if="${post.id == null}">新規投稿</h2>
と<h2 th:if="${post.id != null}">投稿の編集</h2>
:post.id
が空かどうかで新規投稿か編集かを切り替えて表示します。<input type="text" th:field="*{title}" required />
:タイトルの入力欄。required
で必須項目としています。<textarea th:field="*{content}" required></textarea>
:内容の入力欄で、必須項目です。<button type="submit" th:text="${post.id == null} ? '投稿を保存' : '投稿を更新'"></button>
:新規投稿と更新でボタンの表示を切り替えます。<a th:if="${post.id != null}" th:href="@{/posts}" style="margin-left: 10px;">投稿一覧に戻る</a>
:編集時のみ表示される「投稿一覧に戻る」リンクです。
投稿一覧の表示
<h2 th:if="${post.id == null}">投稿一覧</h2>
<ul>
<li th:each="post : ${posts}">
<h3 th:text="${post.title}">投稿タイトル</h3>
<p th:text="${post.content}">投稿内容</p>
<a th:href="@{/posts/{id}/edit(id=${post.id})}" class="edit-link">編集</a>
<form th:action="@{/posts/{id}/delete(id=${post.id})}" method="post" style="display:inline;" onsubmit="return confirmDelete();">
<button type="submit" class="delete-button">削除</button>
</form>
</li>
</ul>
<h2 th:if="${post.id == null}">投稿一覧</h2>
:新規投稿のフォームが表示されているときのみ、投稿一覧の見出しを表示します。<ul> <li th:each="post : ${posts}"> ... </li> </ul>
:posts
リストをループし、各投稿を<li>
要素内に表示します。<h3 th:text="${post.title}">投稿タイトル</h3>
:各投稿のタイトルを表示します。<p th:text="${post.content}">投稿内容</p>
:各投稿の内容を表示します。<a th:href="@{/posts/{id}/edit(id=${post.id})}" class="edit-link">編集</a>
:投稿の編集リンクです。投稿のIDに基づいて編集ページに遷移します。<form th:action="@{/posts/{id}/delete(id=${post.id})}" method="post" style="display:inline;" onsubmit="return confirmDelete();">
:削除ボタンのフォームで、削除確認ポップアップを表示します。
このHTMLコードは、Thymeleafを用いて、ブログ投稿の作成・編集・削除を管理するページを実現しています。posts
リストを使って投稿の一覧を表示し、フォームを使って新規投稿や既存の投稿の編集ができるようになっています。Thymeleafのth:action
やth:text
といった機能でサーバーサイドデータを動的に埋め込むことができ、効率的なページ構築が可能です。
application.propertiesの解説
このコードは、Spring Bootアプリケーションの設定ファイル(通常application.properties
)で、データベースの接続やJPA(Java Persistence API)とThymeleafの設定をしています。これらの設定により、Springアプリケーションが正しくデータベースに接続し、必要な機能を使えるようになります。
アプリケーション名の設定
spring.application.name=blog
spring.application.name=blog
:アプリケーションの名前をblog
に設定しています。spring.application.name
は、アプリケーションの識別に使われるプロパティで、ログなどに表示されることがあります。
データベース接続情報の設定
spring.datasource.url=jdbc:postgresql://localhost:5432/blogdb
spring.datasource.username=${POSTGRESQL_DB_USERNAME}
spring.datasource.password=${POSTGRESQL_DB_PASSWORD}
spring.datasource.url=jdbc:postgresql://localhost:5432/blogdb
:データベース接続URLを設定しています。jdbc:postgresql://localhost:5432/blogdb
は、PostgreSQLデータベースblogdb
にローカル接続するためのURLです。spring.datasource.username=${POSTGRESQL_DB_USERNAME}
:データベース接続に使用するユーザー名を設定しています。ここでは、${POSTGRESQL_DB_USERNAME}
のように、外部環境変数から値を取得しています。こうすることで、セキュリティを強化できます。spring.datasource.password=${POSTGRESQL_DB_PASSWORD}
:データベース接続に使用するパスワードを設定します。ここでも外部環境変数POSTGRESQL_DB_PASSWORD
から値を取得し、パスワードが直接コードに含まれないようにしています。
JPAとHibernateの設定
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=update
:Hibernateがアプリケーション起動時にデータベースをどのように扱うかを指定します。update
は、既存のテーブル構造を変更せず、新しいフィールドなどがあれば追加する設定です。開発中にデータベースの構造が変更された際、自動的にテーブルが更新され便利です。spring.jpa.show-sql=true
:SQLクエリをコンソールに表示する設定です。true
に設定すると、データベースに対して実行されるSQLクエリがログに出力され、デバッグが容易になります。
Thymeleafのキャッシュ設定
spring.thymeleaf.cache=false
spring.thymeleaf.cache=false
:Thymeleafテンプレートのキャッシュを無効にします。false
に設定すると、テンプレートファイルが変更されるたびに最新の状態で表示されるため、開発中の変更がすぐに反映されて便利です。本番環境ではtrue
に設定し、パフォーマンスを向上させるのが一般的です。
この設定ファイルでは、アプリケーション名、データベース接続、JPAの動作、Thymeleafのキャッシュを設定して、開発に適した環境を整えています。特にデータベース接続情報やDDLの自動更新設定、SQLの表示設定は、Spring Bootでのデータベース管理を簡単にしてくれる重要な設定です。
まとめ
- コード解説: 各クラスやメソッドがどのように機能するかの詳細
- HTMLテンプレートの解説:
posts.html
がどのように表示されるか、またその内容を把握 - 設定ファイルの理解:
application.properties
でアプリケーションの設定を行う方法
この知識を活用することで、Spring Bootを使ったWebアプリケーション開発における基礎力が向上し、より高度な機能追加やカスタマイズが可能になります。
コメント