Spring Boot入門:Spring Data JPAでデータベース操作

Spring Boot入門Spring Data JPAでデータベース操作アイキャッチプログラム
スポンサーリンク

本記事ではSpring Data JPAというORM(Object-Relational Mapping:JavaのクラスとDBを橋渡しする)ツールを使って、データベースを操作する方法を説明します。

ORMツールはSpring Data JPA以外にもたくさんあって(MyBatis, DOMA, Spring JDBC, HIBERNATEなど)、それぞれ特徴が異なります。

今回Spring Data JPAを選んだのは、私がメインで使っているORMツールだからです。それぞれにメリット、デメリットがあるので、万が一プロジェクトで使うORMツールを選択することになった場合は、慎重に選んでください。

この記事はSpring Boot入門:ServiceとDI(依存性の注入)の続きとなります。

この記事を読むとわかること
  • H2 Databaseの設定方法
  • Spring Data JPAの簡単な使い方
前提条件
  • Spring Bootの開発環境が整っている
  • 解説ではPleiades All in One Eclipseを使う
  • Javaのバージョンは11
  • Spring Bootのバージョンは2.5.6
関連記事

この記事はSpring Boot入門の一部です。環境構築の方法から初めて基本的なWebアプリケーションの開発に必要なことを説明しています。

この記事のソースコード

この記事のソースコードはGithubに公開しています。

GitHub - gsg0222/spring-boot-tutorial-step6
Contribute to gsg0222/spring-boot-tutorial-step6 development by creating an account on GitHub.

GithubからSpring BootプロジェクトをEclipseにインポートする方法は次の記事を参考にしてください。

Spring Bootプロジェクトを作成

例によってプロジェクト名やパッケージは適当で大丈夫ですが、Spring Data JPAとテスト用データベースを構築するために、色々依存関係を追加する必要があります。

今回必要になる依存関係は以下のとおりです。

  • Spring Boot DevTools
  • lombok
  • Validation
  • Spring Data JPA
  • H2 Database
  • Thymeleaf
  • Spring Web

次の画像のようになります。

依存関係、H2 Database, Spring Data JPAが目新しい

だんだん依存関係が増えてきました。

H2 Databaseの設定

H2 Databaseの設定から説明していきます。

そもそもH2 Databaseってなに?

H2 DatabaseはRDBMS(Relational DataBase Management System)の一つです。MYSQL、PostgreSQL、Oracle、DB2などの仲間になります。

特徴としてはJavaで書かれていること、軽量なこと。今回のようにテストプログラムを組む用途でよく使われます。(要件によっては本番で使うこともあるかも)

Spring BootでH2 Databaseを使えるように設定する

依存関係位はH2 Databaseを追加しましたが、それだけでは使えません。Spring Bootの設定ファイルに必要な記述をする必要があります。

Spring Bootの設定ファイルはsrc/main/resourcesにあるapplication.propertiesです。(ymlという形式で書くこともできますが、本サイトではpropertiesを使います)

このファイルに、以下のような設定を記述します。

# H2の設定
# 利用するドライバ、H2を使う場合はこの値で固定
spring.datasource.driver-class-name=org.h2.Driver
# インメモリで使い、データベース名はtestdbとする
spring.datasource.url=jdbc:h2:mem:testdb
# ここで指定したユーザが作成される
spring.datasource.username=sa
# 上で指定したユーザのパスワードを指定
spring.datasource.password=

# data.sqlをschema.sqlの後に読み込むように設定
spring.jpa.defer-datasource-initialization=true

H2 Databaseを使ったテストコードを作る場合には、この設定をそのまま使い回せば大丈夫です。設定の意味はコメントを見てもらえば大体わかると思います。

spring.jpa.defer-datasource-initializationだけはややこしいですが、要するにテーブルを作るSQLを実行した後にデータを登録するSQLを実行するよという設定です。

データベースにテーブルを構築する

Spring Bootではsrc/main/resourcesにschema.sqlやdata.sqlというファイルが存在すると、起動時に実行してくれます。

Spring Boot 2.5より前はデフォルトでschema.sqlを実行してからdata.sqlを実行していたと思うのですが、2.5以降は仕様が変わったみたいで「spring.jpa.defer-datasource-initialization=true」という設定をapplication.propertiesに行う必要がありました。

この2つのsqlファイルで、Spring Data JPAから利用するテーブルを初期化します。

schema.sqlはBOOKテーブルを定義。

DROP TABLE IF EXISTS BOOK;

CREATE TABLE BOOK (
    ID IDENTITY NOT NULL PRIMARY KEY,
    TITLE VARCHAR(255) NOT NULL
);

data.sqlではBOOKテーブルに初期値を設定しています。

INSERT INTO BOOK(TITLE) VALUES ('Don Quijote de la Mancha');
INSERT INTO BOOK(TITLE) VALUES ('A Tale of Two Cities');
INSERT INTO BOOK(TITLE) VALUES ('The Fellowship of the Ring');

Spring Data JPAの使い方

Spring Data JPAを使うためには、データベースの内容をマッピングするEntityクラスと、その Entity クラスをどのように操作するのかを定義するRepositoryインタフェースの2つを作成します。

Entityクラス

Entityクラスはあるテーブル上の1カラムを意味するクラスです。基本的にカラムとフィールが1対1になるように設定します。(応用編としてはフィールドに外部キーを設定した他のクラスを設定することもできますが、ややこしいので今回は説明しません)

先程schema.sqlで定義したBOOKテーブルに対応するEntityクラスは以下のとおりです。

package blog.tsuchiya.tutorial.step6.model;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.validation.constraints.Size;

import com.sun.istack.NotNull;

import lombok.Data;

/**
 * 書籍を意味するエンティティクラス。
 * Entityアノテーションを付けるとデータベースのカラムを意味する
 * クラスとして扱える。
 */
@Entity
@Data
public class Book {
	
	/**
	 * フィールにアノテーションを付けることでどういう制約がある
	 * 絡むなのか定義できる。
	 * Idアノテーションで主キーであること、
	 * GeneratedValueアノテーションで自動採番することを示している。
	 */
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;
	
	/**
	 * こちらはNotNullでnullではないこと、Sizeで
	 * 最大文字が255文字であることを示している。
	 */
	@NotNull
	@Size(max=255)
	private String title;
}

Entityクラスには@Entityアノテーションを付けます。このアノテーションを付けると、Spring Data JPAがこのクラスをEntityクラスだと判断して、必要なコードを自動的に追加してくれるわけです。

クラス名はテーブルの名前をパスカルケースにしたもの、フィールド名はカラム名をキャメルケースにしたものにします。テーブル名やカラム名にアンダースコアがある場合はそこを区切りにして名前をつければ良いです。(例えばAUTHOR_NAMEというカラムがあった場合、対応するフィールドはauthorNameになります)

各フィールドにはアノテーションを使って制約をつけることができます。テーブルを定義したときにカラムにつけた制約はEntityクラスでも同じように定義するべきです。

Bookクラスではidフィールドが主キーであり自動採番されること、titleが非nullで長さ255文字までであることをアノテーションを使って宣言しています。

@Id、@NotNull、@Size、@GeneratedValue以外はあまり使わないと思うのでとりあえずこの4つだけ覚えてください。

Repositoryインタフェース

RepositoryインタフェースではEntityクラスに対する操作を定義します。インタフェースに必要なインタフェースを継承させると、実装はすべてSpring Data JPAが行ってくれます。SQLやJavaのコードを自分で書かなくても良いので非常に楽です。

package blog.tsuchiya.tutorial.step6.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import blog.tsuchiya.tutorial.step6.model.Book;

/**
 * データの永続化を行う。
 * 今回はJPAの仕組みを使うのでコンクリートクラスではなく
 * インタフェースを使っている。
 * JpaRepositoryを継承した@Repositoryのインタフェースを作成すると、
 * JPAが自動でコンクリートクラスを作成してくれる。
 */
@Repository
public interface MainRepository extends JpaRepository<Book, Long>{

}

必要なのは@Repositoryアノテーションをインタフェースに付けることと、JpaRepositoryインタフェースを継承することです。JpaRepositoryには操作対象のクラスとその主キーを指定します。今回の場合操作対象はBookクラス、主キーはLongです。

これだけで、以下のテーブルのような操作が実装されます。(よく使うものだけ、実際にはもっとたくさんある)

メソッド説明
save引数のインスタンスをデータベースに保存する
findById指定したIDでデータベースからインスタンスを取得する
findAllデータベースからすべての行のインスタンスを取得する
countテーブルにある行数を取得する
delete引数のインスタンスをデータベースから削除する
existsById指定したIDがデータベースに存在するかどうかを返す
主なRepositoryの操作

また、JpaRepositoryを継承したインタフェースにルールに沿ったメソッドを作成すると、やはり実装せずにいろいろな操作ができます。

例えば、

// タイトルに完全一致したインスタンスを1つだけ取得
Optional<Book> findByTitle(String title);
// タイトルを前方一致で検索した結果をリストで取得
List<Book> findByTitleStartingWith(String prefix);

MainRepositoryにこの様なメソッドを定義すると、自分でSQLを書かなくても自動でメソッド名に沿った実装がされます。

詳細はQiitaのこの記事によくまとまっているので、複雑なクエリを実装したい場合は参考にすると良いでしょう。

その他の部分のコード

残りはこれまでに学んだところなのでおまけですが、動かすために必要なのでソースを張っておきます。

サンプルコードを動かすにはSpring Bootを起動してhttp://localhost:8080/にアクセスしてください。

なお、ControllerからRepositoryを直接使わずにServiceを経由しているのは、各階層での責任を明確にするためです。ビジネスロジックはServiceに書き、データベースの操作はRepositoryが行うという責任分担になっています。

今回のサンプルのような規模では全く意味がありませんが、今後大きなプロジェクトを作成する際の練習だと思ってください。

package blog.tsuchiya.tutorial.step6.controller;

import java.util.Objects;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import blog.tsuchiya.tutorial.step6.service.MainService;
import lombok.RequiredArgsConstructor;

@Controller
@RequiredArgsConstructor
public class MainController {

	/**
	 * ControllerではServiceを呼ぶ。今回の機能を実装するだけなら
	 * このようにする必要はないが、今後規模が大きくなった際の
	 * 役割分担を意識している。
	 */
	private final MainService service;
	@RequestMapping
	public String index(@RequestParam(required = false) String title, Model model) {
		// タイトルが入力されたらデータベースに保存する
		if(!Objects.isNull(title) && !title.isBlank()) {
			this.service.save(title);
		}
		model.addAttribute("books", this.service.findAll());
		return "index";
	}
}

package blog.tsuchiya.tutorial.step6.service;

import java.util.List;

import org.springframework.stereotype.Service;

import blog.tsuchiya.tutorial.step6.model.Book;
import blog.tsuchiya.tutorial.step6.repository.MainRepository;
import lombok.RequiredArgsConstructor;

/**
 * ビジネスロジックを記述するクラス。
 * ServiceからRepositoryを使うようにする。
 */
@Service
@RequiredArgsConstructor
public class MainService {

	private final MainRepository repository;
	
	/**
	 * 入力したタイトルを書籍データベースに登録する。
	 * @param title
	 */
	public void save(String title) {
		Book book = new Book();
		book.setTitle(title);
		this.repository.save(book);
	}
	
	/**
	 * すべての書籍データを返す
	 * @return 書籍データ
	 */
	public List<Book> findAll(){
		this.repository.
		return this.repository.findAll();
	}
}

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>書籍一覧</title>
<link
	href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css"
	rel="stylesheet">
</head>
<body>
	<section class="border  p-1 m-3">
		<form method="post" th:action="@{/}">
			<div class="mb-3">
				<label for="title" class="form-label">タイトル</label> <input
					type="text" class="form-control" name="title" id="title">
			</div>
			<div>
				<button type="submit" class="btn btn-primary">送信</button>
			</div>
		</form>
	</section>
	<main class="border  p-1 m-3">
		<h1>書籍一覧</h1>
		<ul class="list-group">
			<li class="list-group-item" th:each="book : ${books}"
				th:text="${book.title}">An item</li>
		</ul>
	</main>
</body>
</html>

まとめ:ここまでできたら自分でアプリケーションを作れる!

Spring Data JPAでデータベースを操作する方法を説明しました。SQLを自分で書かなくても良いので、敷居は低いと思います。

Controllerの作成から始まり、Thymeleafファイル分割)、入力チェックService、そして今回でデータベース操作と説明してきました。これまでの記事で説明してきた内容が理解できていれば、簡単なWebアプリケーションは作成できます。

実践に勝る勉強はありません。簡単でいいので、何か動くアプリケーションを自分で作ってみましょう。色々エラーが出たりして大変だと思いますが、大きく成長できるはずです。

とりあえずの方針として、今回の記事で作ったプログラムを拡張するでも良いでしょう。サンプルプログラムは本の追加しかできないので、以下の機能を追加してみるとか。

  • 本の削除と更新をできるようにする
  • 本のタイトルだけでなく著者やISBNも管理できるようにする
  • 複雑な検索を実装する

エラーが出たり思ったように動かなければ、Twitterお問い合わせフォームから連絡をください。できる限り対応します。

Spring Boot入門:Spring Securityで認証と認可に続きます。

おすすめ

せっかくJavaを勉強しているのなら、資格を取得してみませんか?OCJP(Oracle Certified Java Programmer) SilverはJavaの基本を学ぶのに適した資格です。初心者はもとより、中堅の人でも普段使わない機能を勉強するきっかけになると思います。

残念なことに試験は有料で、しかも結構高い(税抜26,600円)です。

しかし、お勤めの会社によっては費用を負担してくれるところもあるでしょうし、場合によっては報奨金まで出してくれるはずです。(私の会社は初回の試験料負担+報奨金を出してくれました)

私のおすすめな勉強方法は徹底攻略Java SE 11 Silver問題集を読むことです。問題集ですが、解説が充実しています。

徹底攻略Java SE 11 Silver問題集[1Z0-815]対応 徹底攻略シリーズ | 志賀 澄人 | 工学 | Kindleストア | Amazon
Amazonで志賀 澄人の徹底攻略Java SE 11 Silver問題集[1Z0-815]対応 徹底攻略シリーズ。アマゾンならポイント還元本が多数。一度購入いただいた電子書籍は、KindleおよびFire端末、スマートフォンやタブレットなど、様々な端末でもお楽しみいただけます。

大体30時間くらいの勉強で合格できました。

関連記事

この記事はSpring Boot入門の一部です。環境構築の方法から初めて基本的なWebアプリケーションの開発に必要なことを説明しています。

コメント

タイトルとURLをコピーしました