Spring FrameworkでWebアプリを作る手順のメモです。業務で今後使う機会がありそうなので、手元の環境で軽くいじってみました。
【環境】
- Windows11
- OpenJDK11
- Eclipse Version 2022-06 M2 (4.24.0 M2)
- Spring Boot v2.7.2
- PostgreSQL 14.4
プロジェクトの作成
[ファイル]->[新規]->[Spring スターター・プロジェクト]を選択します。
プロジェクトの基本設定はデフォルトのままで問題ありません。執筆時点(2022年8月)では、SpringFrameworkはJavaバージョン11を推奨しています。
以下のように選択します。追加したのはThymeleaf(テンプレートエンジン)とPostgreSQL Driver(JDBC)です。
プロジェクトが作成されました。
とりあえず実行してみます。
実行できました。コンソールにはログが表示されるだけですね。
ブラウザからアクセスしてみます。デフォルトではポート8080になっているので、http://localhost:8080へアクセスします。
結果は…この時点では、URLにマッピングされているページが無いのでエラーになります。
ポート番号を変更したい場合は、application.propertiesに設定を追加します。
server.port=<任意のポート番号>
コンソール側でHello Worldしてみる
起動時に実行されるクラスは(プロジェクト名)Applicationです。コードを以下の通り修正して再度実行します。
package com.example.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringTestProjectApplication {
public static void main(String[] args) {
// SpringApplication.run(SpringTestProjectApplication.class, args);
// 起動時にexecute()を先にコールする
SpringApplication.run(SpringTestProjectApplication.class, args)
.getBean(SpringTestProjectApplication.class).execute();
}
private void execute() {
System.out.println("Hello World!!");
}
}
無事にHello Worldされました。デバッグなどの目的で処理を動かしたい場合はここにコードを書くとよさそうです。
Web側でHello Worldしてみる
HTMLファイルの作成
新規からHTMLファイルを選択します。
作成する場所はresource/templetesになるので注意してください。
ファイルの中身は適当に。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Hello World!!</h1>
</body>
</html>
コントローラクラスの作成
コントローラとはMVCモデルのCを担っており、リクエストに対して適切な処理を行ったのち、HTMLファイルの返却(表示)を行います。
新規から通常のJavaクラスを作成します。
普通のJavaクラスのことをPOJO(Plain Old Java Object)と呼ぶそうです。
com.example.test.controllerパッケージを指定(作成)します。
パッケージはSpringTestProjectApplicationと同じ、または、その配下の空間にする必要があります。
コードを以下の通り記述します。
@Controllerアノテーションをクラスに付与することで、SpringがこのクラスをControllerクラスと認識します。
package com.example.test.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller // Controllerクラスであることを宣言
@RequestMapping("/test") // マッピングするURLの基点
public class TestController {
@GetMapping // Getリクエストのマッピング
public String showHome() {
// 表示させたいhtmlファイルのファイル名を返却
return "home";
}
}
動作確認
では、再度実行してブラウザからアクセスしてみます。http://localhost:8080/testへアクセスします。
無事にHello Worldされました。
ちなみに、URLにhttp://localhost:8080/test/index.htmlとかを指定すると、マッピングエラーになります。test以下すべてのURLをマッピングしたい場合は、@GetMapping(“*”)にします。
DB(PostgreSQL)からデータを取得する
動的なWebサイトを作成していきます。
DBの用意
postgresデータベースに適当なテーブル・データを追加します。
-- Table: public.member
-- DROP TABLE IF EXISTS public.member;
CREATE TABLE IF NOT EXISTS public.member
(
id integer NOT NULL DEFAULT nextval('member_id_seq'::regclass),
name character varying(10) COLLATE pg_catalog."default" NOT NULL,
CONSTRAINT member_pkey PRIMARY KEY (id)
)
TABLESPACE pg_default;
ALTER TABLE IF EXISTS public.member
OWNER to postgres;
INSERT INTO public.member(name) VALUES('taro');
INSERT INTO public.member(name) VALUES('jiro');
INSERT INTO public.member(name) VALUES('hanako');
接続先設定
接続先DBの情報をapplication.propertiesへ設定します。
ファイルを開く際は、Limy プロパティー・エディターで開くように気を付けましょう
追加するコードです。ポート番号やユーザ名、パスワードは環境に合わせて変更してください。
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
ビルド依存関係の設定
エンティティクラスで利用するパッケージを解決するために、build.gradleに依存関係を追加します。
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
エンティティクラスの作成
エンティティとは、DBにおけるテーブル1行に対応するオブジェクトです。原則的に、クラス名=テーブル名、メンバ名=フィールド名というように、DBと対応付けて作成していきます。
新規から通常のJavaクラス(POJO)を選択します。
エンティティも専用のパッケージを作成するのが通例のようです。com.example.test.entityとしました。
package com.example.test.entity;
import org.springframework.data.annotation.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data // getter, setterを自動生成
@NoArgsConstructor // 引数なしコンストラクターを自動生成
@AllArgsConstructor // 全メンバの引数ありコンストラクターを自動生成
public class Member {
@Id
private Integer id;
private String name;
}
@Entityアノテーションも必要では?と思ったものの、私の環境では依存関係が解決できず、@Entityが無くても問題なく動きました。
リポジトリクラスの作成
リポジトリとは、DB操作を実装するクラスです。CrudRepositoryクラスを継承したインターフェースを作成します。
新規からインターフェースを作成します。
リポジトリもパッケージを分けます。また、拡張インターフェースにCrudRepositoryクラスを指定します。
基本的なCrud操作は予め定義されているため実装する必要はありません。それ以外に、独自のクエリを投げたい場合には、メソッドを追加します。
package com.example.test.repository;
import org.springframework.data.jdbc.repository.query.Query;
import org.springframework.data.repository.CrudRepository;
import com.example.test.entity.Member;
public interface MemberRepository extends CrudRepository<Member, Integer> {
// 基本的なCRUDはCrudRepositoryにてあらかじめ実装されている
// 独自メソッド
@Query("SELECT * FROM member ORDER BY ID DESC")
Iterable<Member> findAllOrderByIdDesc();
}
利用できる標準メソッドの一覧は公式のリファレンスで確認できます。
動作確認(コンソール)
メインクラスにデバッグ用のコードを書いて動作確認を行います。executeメソッドを以下のように修正します。
MemberRepositoryは@Autowiredアノテーションで注入するため、executeメソッド内でインタンスを生成する必要はありません。
/** 注入 */
@Autowired
MemberRepository repository;
private void execute() {
/** DB接続テスト */
// 標準メソッド
System.out.println("Call findAll");
Iterable<Member> members1 = repository.findAll();
for (Member member : members1) {
System.out.println(member);
}
// 独自メソッド
System.out.println("Call findAllOrderByIdDesc");
Iterable<Member> members2 = repository.findAllOrderByIdDesc();
for (Member member : members2) {
System.out.println(member);
}
}
実行してみます。データを取得できていることが分かります。
DBから取得したデータをWeb上に表示する
取得したデータをWebへ表示するために、サービスとHTMLを作成します。
サービスの作成
サービスはMVCモデルのM(業務処理)にあたる部分です。
新規からインターフェースを作成します。例によってパッケージは分離します。
業務処理を宣言します。
package com.example.test.service;
import com.example.test.entity.Member;
public interface TestService {
/** Member情報を全件取得する */
Iterable<Member> selectAll();
}
次に、インターフェースを実装したクラスを作成します。
業務処理を実装します。
package com.example.test.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.test.entity.Member;
import com.example.test.repository.MemberRepository;
@Service
@Transactional // 自動的にクエリ全般をトランザクションで動かす
public class TestServiceImpl implements TestService {
/** Repositoryを注入 */
@Autowired
MemberRepository repository;
@Override
public Iterable<Member> selectAll() {
return repository.findAll();
}
}
コントローラの修正
サービスを呼び出してデータを取得し、ビューへデータを渡す処理を追加します。
package com.example.test.controller;
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.test.service.TestService;
@Controller
@RequestMapping("/test") // マッピングするURLの基点
public class TestController {
/** サービスの注入 */
@Autowired
TestService service;
@GetMapping // Getリクエストのマッピング
public String showHome(Model model) {
// 業務処理を呼び出し、返り値をモデル(リスエストセッション)へ格納
model.addAttribute("members", service.selectAll());
// 表示させたいhtmlファイルのファイル名を返却
return "home";
}
}
Thymeleafでフロント側を実装
Thymeleafはテンプレートエンジンです。HTMLに処理を埋め込んでデザインの可読性が下がるのを抑えることができます。利用する場合、htmlタグにおまじないを追加します。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Member</h1>
<div th:each="obj : ${members}">
<p th:text="|id:${obj.id}, name:${obj.name}|"></p>
</div>
</body>
</html>
動作確認(ブラウザ)
ブラウザからアクセスします。無事に取得したデータが表示されました。
Webで入力したデータを受け取る
ウェブフォームを設置してPOSTしたデータを受け取ってみます。
Formクラスの作成
Formとは、ウェブフォームからデータを受け取るためのクラスです。例によってパッケージは分離します。
コードは以下の通りです。エンティティと同じように、基本的にはDBのテーブルに合わせてフィールドを定義していきます。バリデーションを定義することができます。
package com.example.test.form;
import javax.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MemberForm {
private Integer id;
@NotBlank // 入力必須
private String name;
}
サービス・コントローラの修正
フォームから受けたデータをDBへ登録したいので、サービスを修正します。
package com.example.test.service;
import com.example.test.entity.Member;
public interface TestService {
/** Member情報を全件取得する */
Iterable<Member> selectAll();
/** Member1件を登録する */
Member insertOne(Member insertOne(Member member);
}
package com.example.test.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.example.test.entity.Member;
import com.example.test.repository.MemberRepository;
@Service
@Transactional // 自動的にクエリ全般をトランザクションで動かす
public class TestServiceImpl implements TestService {
/** Repositoryを注入 */
@Autowired
MemberRepository repository;
@Override
public Iterable<Member> selectAll() {
return repository.findAll();
}
@Override
public Member insertOne(Member member) {
return repository.save(member);
}
}
コントローラに以下のコードを追加します。
@GetMapping("/form")
public String showForm(MemberForm memberForm) {
return "form";
}
@PostMapping("/post")
public String saveMember(@Validated MemberForm memberForm, BindingResult result, Model model) {
// Memberインスタンスを生成
Member member = new Member();
// MemberFormにバインドされたPOSTデータを取得してMemberインスタンスにセット
member.setName(memberForm.getName());
// MemberインスタンスをDBへ登録し、返り値をモデル(リスエストセッション)へ格納
if (!result.hasErrors()) { // 入力チェック
model.addAttribute("member", service.insertOne(member));
return "complete";
} else {
return "error";
}
}
HTMLの作成
ウェブフォームを作成します。th:objectでPOSTにmemberFormオブジェクトをバインドします。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>フォーム</h1>
<form method="POST" th:action="@{/test/post}" th:object="${memberForm}">
<input type="text" th:field="*{name}">
<input type="submit" value="送信">
</form>
</body>
</html>
登録完了画面を作成します。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Complete</h1>
<p th:text="|id:${member.id}, name:${member.name}|"></p>
</body>
</html>
動作確認
ブラウザでアクセスします。フォームに入力して送信すると…
完了画面が表示されました。
DBにもちゃんと登録されています。
MemberFormにてnameに@NotBlankアノテーションを付与しているため、入力無しで送信するとちゃんとエラーとして判定されました(/errorに対するマッピングを作成していないのでWhitelabel Errorになりましたが)。
デプロイ(.jarファイルの生成)
build.gradleに以下のコードを追加します。
bootJar {
launchScript()
}
Gradleタスクから追加したbootJarタスクを実行します。
build\libs配下に.jarファイルが生成されるので、お目当てのサーバへ配置すればOKです。コンパイルした環境と、JDKのバージョンがそろっていないと起動しないので注意してください。
コメント