Spring Data JPAは、Spring Bootアプリケーションにおけるデータベースアクセスを劇的に簡素化するライブラリです。本記事では、JPA Entityの定義からJpaRepositoryを使ったCRUD操作、クエリメソッドの命名規則、@Queryアノテーションによるカスタムクエリまで、REST APIとデータベースを連携させるための実践的な手法を解説します。
実行環境と前提条件#
本記事の内容を実践するにあたり、以下の環境を前提としています。
| 項目 |
バージョン・要件 |
| Java |
17以上 |
| Spring Boot |
3.4.x |
| Spring Data JPA |
3.4.x(Spring Boot Starterに含まれる) |
| データベース |
H2 Database(開発・テスト用インメモリDB) |
| ビルドツール |
Maven または Gradle |
| IDE |
VS Code または IntelliJ IDEA |
事前に以下の準備を完了してください。
- JDK 17以上のインストール
- Spring Boot REST APIプロジェクトの基本構成(前回記事を参照)
- Maven または Gradle によるプロジェクトビルド環境
Spring Data JPAとH2 Databaseの依存関係を追加する#
Spring Data JPAを使用するには、spring-boot-starter-data-jpaと使用するデータベースドライバーを依存関係に追加する必要があります。開発時はH2 Databaseを使用することで、手軽にインメモリデータベースでテストできます。
Mavenの場合#
pom.xmlに以下を追加します。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<dependencies>
<!-- Spring Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database(開発・テスト用) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
|
Gradleの場合#
build.gradleに以下を追加します。
1
2
3
4
|
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'
}
|
application.propertiesでデータベースを設定する#
H2 Databaseの接続設定とJPAの動作設定をapplication.propertiesに記述します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
# H2 Database設定
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# JPA/Hibernate設定
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# H2 Console設定(開発時のみ有効化)
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
|
各設定項目の意味は以下のとおりです。
| 設定項目 |
説明 |
spring.jpa.hibernate.ddl-auto |
テーブルの自動生成モード。create-dropはアプリ起動時に作成、終了時に削除 |
spring.jpa.show-sql |
実行されるSQLをコンソールに出力 |
spring.h2.console.enabled |
H2 Consoleを有効化(ブラウザからDBを確認可能) |
Entityクラスを定義する#
JPA Entityは、データベースのテーブルに対応するJavaクラスです。@Entityアノテーションを付与することで、JPAがそのクラスをエンティティとして認識します。
基本的なEntityクラスの構成#
以下は、タスク管理アプリケーションを想定したTaskエンティティの例です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
package com.example.demoapi.entity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
@Entity
@Table(name = "tasks")
public class Task {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, length = 100)
private String title;
@Column(length = 500)
private String description;
@Column(nullable = false)
private boolean completed;
@Column(name = "created_at", nullable = false, updatable = false)
private LocalDateTime createdAt;
@Column(name = "updated_at")
private LocalDateTime updatedAt;
// JPAが要求する引数なしコンストラクタ
protected Task() {
}
public Task(String title, String description) {
this.title = title;
this.description = description;
this.completed = false;
this.createdAt = LocalDateTime.now();
}
// Getter/Setter
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
this.updatedAt = LocalDateTime.now();
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
this.updatedAt = LocalDateTime.now();
}
public boolean isCompleted() {
return completed;
}
public void setCompleted(boolean completed) {
this.completed = completed;
this.updatedAt = LocalDateTime.now();
}
public LocalDateTime getCreatedAt() {
return createdAt;
}
public LocalDateTime getUpdatedAt() {
return updatedAt;
}
}
|
主要なJPAアノテーションの解説#
Entityクラスで使用する主要なアノテーションを整理します。
| アノテーション |
説明 |
@Entity |
クラスがJPAエンティティであることを宣言 |
@Table |
マッピングするテーブル名を指定(省略時はクラス名がテーブル名) |
@Id |
主キーフィールドを指定 |
@GeneratedValue |
主キーの生成戦略を指定 |
@Column |
カラムの詳細設定(名前、制約、長さなど) |
@GeneratedValueの生成戦略#
@GeneratedValueのstrategy属性で主キーの生成方法を指定できます。
| 戦略 |
説明 |
GenerationType.IDENTITY |
データベースのAUTO_INCREMENT機能を使用(MySQL、PostgreSQL等) |
GenerationType.SEQUENCE |
データベースのシーケンスを使用(Oracle、PostgreSQL等) |
GenerationType.TABLE |
キー生成用のテーブルを使用 |
GenerationType.AUTO |
JPAプロバイダが適切な戦略を自動選択 |
Repositoryインターフェースを作成する#
Spring Data JPAでは、JpaRepositoryインターフェースを継承することで、基本的なCRUD操作を自動的に利用できます。
JpaRepositoryの定義#
以下のように、エンティティ型と主キーの型を指定してインターフェースを定義します。
1
2
3
4
5
6
7
8
9
10
|
package com.example.demoapi.repository;
import com.example.demoapi.entity.Task;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface TaskRepository extends JpaRepository<Task, Long> {
// 基本的なCRUD操作はJpaRepositoryが提供
}
|
JpaRepositoryが提供する主要メソッド#
JpaRepositoryを継承するだけで、以下のメソッドが利用可能になります。
| メソッド |
説明 |
save(S entity) |
エンティティを保存(新規作成または更新) |
findById(ID id) |
IDでエンティティを検索(Optional<T>を返却) |
findAll() |
全エンティティを取得 |
findAllById(Iterable<ID> ids) |
複数IDでエンティティを検索 |
deleteById(ID id) |
IDでエンティティを削除 |
delete(T entity) |
エンティティを削除 |
count() |
エンティティの総数を取得 |
existsById(ID id) |
指定IDのエンティティが存在するか確認 |
クエリメソッドの命名規則#
Spring Data JPAの強力な機能のひとつが「クエリメソッド」です。メソッド名の命名規則に従うことで、JPQLやSQLを書かずにクエリを自動生成できます。
基本的なクエリメソッドの例#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
package com.example.demoapi.repository;
import com.example.demoapi.entity.Task;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
@Repository
public interface TaskRepository extends JpaRepository<Task, Long> {
// タイトルで検索
List<Task> findByTitle(String title);
// 完了状態で検索
List<Task> findByCompleted(boolean completed);
// タイトルに特定文字列を含むタスクを検索
List<Task> findByTitleContaining(String keyword);
// 指定日時以降に作成されたタスクを検索
List<Task> findByCreatedAtAfter(LocalDateTime dateTime);
// 完了状態かつタイトルに特定文字列を含むタスクを検索
List<Task> findByCompletedAndTitleContaining(boolean completed, String keyword);
// タイトルで検索し、作成日時の降順でソート
List<Task> findByTitleContainingOrderByCreatedAtDesc(String keyword);
// 最初の5件を取得
List<Task> findTop5ByOrderByCreatedAtDesc();
}
|
クエリメソッドで使用できるキーワード一覧#
メソッド名に使用できる主要なキーワードを以下に示します。
| キーワード |
サンプルメソッド |
生成されるJPQL相当 |
And |
findByTitleAndCompleted |
WHERE title = ?1 AND completed = ?2 |
Or |
findByTitleOrDescription |
WHERE title = ?1 OR description = ?2 |
Between |
findByCreatedAtBetween |
WHERE createdAt BETWEEN ?1 AND ?2 |
LessThan |
findByIdLessThan |
WHERE id < ?1 |
GreaterThan |
findByIdGreaterThan |
WHERE id > ?1 |
IsNull |
findByDescriptionIsNull |
WHERE description IS NULL |
IsNotNull |
findByDescriptionIsNotNull |
WHERE description IS NOT NULL |
Like |
findByTitleLike |
WHERE title LIKE ?1 |
Containing |
findByTitleContaining |
WHERE title LIKE %?1% |
StartingWith |
findByTitleStartingWith |
WHERE title LIKE ?1% |
EndingWith |
findByTitleEndingWith |
WHERE title LIKE %?1 |
OrderBy |
findByCompletedOrderByCreatedAtDesc |
ORDER BY createdAt DESC |
Not |
findByCompletedNot |
WHERE completed <> ?1 |
In |
findByIdIn |
WHERE id IN (?1) |
True |
findByCompletedTrue |
WHERE completed = true |
False |
findByCompletedFalse |
WHERE completed = false |
IgnoreCase |
findByTitleIgnoreCase |
WHERE UPPER(title) = UPPER(?1) |
@Queryによるカスタムクエリを定義する#
命名規則だけでは表現が難しい複雑なクエリは、@Queryアノテーションを使用してJPQLまたはネイティブSQLで記述できます。
JPQLによるカスタムクエリ#
JPQL(Java Persistence Query Language)はエンティティを対象としたクエリ言語です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
package com.example.demoapi.repository;
import com.example.demoapi.entity.Task;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface TaskRepository extends JpaRepository<Task, Long> {
// JPQLによるカスタムクエリ(位置パラメータ)
@Query("SELECT t FROM Task t WHERE t.completed = false AND t.title LIKE %?1%")
List<Task> findIncompleteTasksByKeyword(String keyword);
// JPQLによるカスタムクエリ(名前付きパラメータ)
@Query("SELECT t FROM Task t WHERE t.completed = :completed AND t.title LIKE %:keyword%")
List<Task> findTasksByStatusAndKeyword(
@Param("completed") boolean completed,
@Param("keyword") String keyword
);
// 完了タスクの件数を取得
@Query("SELECT COUNT(t) FROM Task t WHERE t.completed = true")
long countCompletedTasks();
// タイトルと説明文の両方で検索(OR条件)
@Query("SELECT t FROM Task t WHERE t.title LIKE %:keyword% OR t.description LIKE %:keyword%")
List<Task> searchByKeyword(@Param("keyword") String keyword);
}
|
ネイティブSQLによるカスタムクエリ#
データベース固有の機能を使用したい場合は、nativeQuery = trueを指定してネイティブSQLを記述できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package com.example.demoapi.repository;
import com.example.demoapi.entity.Task;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.NativeQuery;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface TaskRepository extends JpaRepository<Task, Long> {
// ネイティブSQLによるカスタムクエリ
@NativeQuery(value = "SELECT * FROM tasks WHERE completed = false ORDER BY created_at DESC LIMIT :limit")
List<Task> findRecentIncompleteTasks(@Param("limit") int limit);
// 従来の書き方(nativeQuery属性を使用)
@Query(value = "SELECT * FROM tasks WHERE LOWER(title) LIKE LOWER(CONCAT('%', :keyword, '%'))",
nativeQuery = true)
List<Task> findByTitleCaseInsensitive(@Param("keyword") String keyword);
}
|
更新・削除クエリの定義#
データを更新または削除するクエリには、@Modifyingアノテーションを併用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
package com.example.demoapi.repository;
import com.example.demoapi.entity.Task;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
@Repository
public interface TaskRepository extends JpaRepository<Task, Long> {
// 一括更新クエリ
@Modifying
@Transactional
@Query("UPDATE Task t SET t.completed = true WHERE t.id IN :ids")
int markAsCompleted(@Param("ids") List<Long> ids);
// 一括削除クエリ
@Modifying
@Transactional
@Query("DELETE FROM Task t WHERE t.completed = true")
int deleteCompletedTasks();
}
|
@Modifyingを使用する際は以下の点に注意してください。
@Transactionalアノテーションが必要(ServiceクラスまたはRepositoryメソッドに付与)
- 戻り値は
int(影響を受けた行数)またはvoid
- 更新後、永続化コンテキストと実データの不整合に注意(必要に応じて
clearAutomatically = trueを指定)
ServiceクラスでRepositoryを使用する#
Repositoryで定義したメソッドは、Serviceクラスからインジェクションして使用します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
package com.example.demoapi.service;
import com.example.demoapi.entity.Task;
import com.example.demoapi.repository.TaskRepository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Service
@Transactional(readOnly = true)
public class TaskService {
private final TaskRepository taskRepository;
public TaskService(TaskRepository taskRepository) {
this.taskRepository = taskRepository;
}
public List<Task> getAllTasks() {
return taskRepository.findAll();
}
public Optional<Task> getTaskById(Long id) {
return taskRepository.findById(id);
}
public List<Task> getIncompleteTasks() {
return taskRepository.findByCompleted(false);
}
public List<Task> searchTasks(String keyword) {
return taskRepository.findByTitleContaining(keyword);
}
@Transactional
public Task createTask(String title, String description) {
Task task = new Task(title, description);
return taskRepository.save(task);
}
@Transactional
public Task updateTask(Long id, String title, String description, boolean completed) {
Task task = taskRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Task not found: " + id));
task.setTitle(title);
task.setDescription(description);
task.setCompleted(completed);
return taskRepository.save(task);
}
@Transactional
public void deleteTask(Long id) {
if (!taskRepository.existsById(id)) {
throw new RuntimeException("Task not found: " + id);
}
taskRepository.deleteById(id);
}
}
|
ControllerクラスからServiceを呼び出す#
REST APIのエンドポイントとしてControllerを実装し、Serviceクラスを経由してデータベース操作を行います。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
package com.example.demoapi.controller;
import com.example.demoapi.entity.Task;
import com.example.demoapi.service.TaskService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/tasks")
public class TaskController {
private final TaskService taskService;
public TaskController(TaskService taskService) {
this.taskService = taskService;
}
@GetMapping
public ResponseEntity<List<Task>> getAllTasks() {
return ResponseEntity.ok(taskService.getAllTasks());
}
@GetMapping("/{id}")
public ResponseEntity<Task> getTaskById(@PathVariable Long id) {
return taskService.getTaskById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@GetMapping("/search")
public ResponseEntity<List<Task>> searchTasks(@RequestParam String keyword) {
return ResponseEntity.ok(taskService.searchTasks(keyword));
}
@PostMapping
public ResponseEntity<Task> createTask(@RequestBody TaskRequest request) {
Task created = taskService.createTask(request.title(), request.description());
return ResponseEntity.status(HttpStatus.CREATED).body(created);
}
@PutMapping("/{id}")
public ResponseEntity<Task> updateTask(
@PathVariable Long id,
@RequestBody TaskRequest request
) {
Task updated = taskService.updateTask(
id,
request.title(),
request.description(),
request.completed()
);
return ResponseEntity.ok(updated);
}
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteTask(@PathVariable Long id) {
taskService.deleteTask(id);
return ResponseEntity.noContent().build();
}
// リクエストボディ用のRecord
public record TaskRequest(String title, String description, boolean completed) {}
}
|
動作確認の手順#
アプリケーションを起動し、REST APIの動作を確認します。
アプリケーションの起動#
curlコマンドによる動作確認#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
# タスクの作成
curl -X POST http://localhost:8080/api/tasks \
-H "Content-Type: application/json" \
-d '{"title":"Spring Data JPAを学習する","description":"EntityとRepositoryを理解する","completed":false}'
# 全タスクの取得
curl http://localhost:8080/api/tasks
# IDでタスクを取得
curl http://localhost:8080/api/tasks/1
# キーワードで検索
curl "http://localhost:8080/api/tasks/search?keyword=JPA"
# タスクの更新
curl -X PUT http://localhost:8080/api/tasks/1 \
-H "Content-Type: application/json" \
-d '{"title":"Spring Data JPAを学習する","description":"EntityとRepositoryを理解した","completed":true}'
# タスクの削除
curl -X DELETE http://localhost:8080/api/tasks/1
|
H2 Consoleでデータを確認する#
ブラウザでhttp://localhost:8080/h2-consoleにアクセスし、以下の設定で接続します。
| 項目 |
値 |
| JDBC URL |
jdbc:h2:mem:testdb |
| User Name |
sa |
| Password |
(空欄) |
接続後、SELECT * FROM TASKSなどのSQLでデータを確認できます。
期待される結果#
本記事の手順を完了すると、以下の成果物が得られます。
| 成果物 |
説明 |
| Entityクラス |
@Entityを付与したデータベーステーブル対応クラス |
| Repositoryインターフェース |
JpaRepositoryを継承したCRUD操作インターフェース |
| クエリメソッド |
命名規則に基づく自動生成クエリ |
| カスタムクエリ |
@Queryで定義したJPQL/ネイティブSQLクエリ |
| Service/Controllerクラス |
レイヤードアーキテクチャに基づくREST API実装 |
以下のアーキテクチャ図は、本記事で構築したアプリケーションの全体構成を示しています。
flowchart TB
subgraph Client["クライアント"]
HTTP["HTTP リクエスト"]
end
subgraph Application["Spring Boot Application"]
Controller["@RestController<br/>TaskController"]
Service["@Service<br/>TaskService"]
Repository["@Repository<br/>TaskRepository<br/>(JpaRepository)"]
Entity["@Entity<br/>Task"]
end
subgraph Database["Database"]
H2["H2 Database<br/>tasks テーブル"]
end
HTTP --> Controller
Controller --> Service
Service --> Repository
Repository --> Entity
Entity -.-> H2
Repository -.-> H2まとめ#
本記事では、Spring Boot REST APIにおけるSpring Data JPAの基本的な使い方を解説しました。
@Entity、@Id、@GeneratedValueを使ったEntityクラスの定義
JpaRepositoryを継承したRepositoryインターフェースの作成
- クエリメソッドの命名規則による自動クエリ生成
@Queryアノテーションを使ったカスタムJPQL/ネイティブSQLの記述
- Service層を経由したビジネスロジックの実装
Spring Data JPAを活用することで、ボイラープレートコードを大幅に削減し、データアクセス層の開発を効率化できます。次のステップとして、エンティティ間のリレーションシップ設計やN+1問題の対策に取り組むことをおすすめします。
参考リンク#