Spring Data JPAの使い方(サンプルコード付き)

この記事は約17分で読めます。
springDataJPアイキャッチ

Spring Data JDBCは、Spring Bootの依存関係です。
プロジェクトを立ち上げる際、Eclipseも用いると、
設定を進めていくうちに、

この画面で、SQLのリストの中にあり、チェックを入れると
使用することができます。


今回は、このSpring Data JPAの使用方法を
サンプルコードを用いてご紹介します。


なお、使用例についてはJUnitを用い
使用したコードは、以下のGitHubへ載せています。

GitHub - tkmttkm/SQL
Contribute to tkmttkm/SQL development by creating an account on GitHub.

Spring Data JPAの使用方法は、
Spring Data JDBCの使い方と非常に似ています。

しかし、違いもあります。
個人的にはJPAの方が使いやすいと思ってます。
気になる方は、この記事をぜひ読み進めてみてください。

環境・定義

build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.2.0'
	id 'io.spring.dependency-management' version '1.1.4'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '17'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.h2database:h2'
	annotationProcessor 'org.projectlombok:lombok'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testImplementation 'org.junit.jupiter:junit-jupiter:5.5.2'
}

tasks.named('test') {
	useJUnitPlatform()
}

今回、DBはh2データベースを使用し、
GetterやSetterの生成には、lombokを用います。

application.properties

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:hogedb
spring.datasource.username=sa
spring.datasource.password=

spring.jpa.hibernate.ddl-auto=validate
spring.sql.init.mode=always
spring.sql.init.schema-locations=classpath:schema.sql
spring.sql.init.data-locations=classpath:data.sql

spring.jpa.hibernate.ddl-autoの設定値については、以下に示します。

設定値説明備考
noneDDLスクリプトを生成しない。スキーマの更新は手動。
validateデータベースの更新、作成はしないが、検証をする。テーブルの型とEntityの型が違うなどのことがあるとコンパイルエラーとなる。
updateEntityに対応するテーブルがなければ作成。
createEntityに対応するテーブルが無けれ作成。あればデータを削除する。
create-dropcreateの挙動にプラスし、アプリケーション終了時にテーブルを削除する。

spring.sql.init.modeの設定値については、以下に示します。

設定値説明
alwayssrc/main/resources配下のschema.sql, data.sqlを常に実行
embeddedH2、HSQLなどの組み込みデータベースの際に、src/main/resources配下のschema.sql, data.sqlを実行
neverSQLスクリプトを実行しない

schema.sql

CREATE TABLE test_table
(
  id INT NOT NULL AUTO_INCREMENT,
  first_name VARCHAR(10) NOT NULL,
  last_name VARCHAR(10) NOT NULL,
  birth_day INT NULL,
  PRIMARY KEY(id)
);

data.sql

INSERT INTO test_table(id,first_name, last_name, birth_day)
VALUES(1,'テスト', '太郎', 20240101);
INSERT INTO test_table(id,first_name, last_name, birth_day)
VALUES(2,'テスト', '二郎', 20240101);
INSERT INTO test_table(id,first_name, last_name, birth_day)
VALUES(3,'テスト', '三郎', 20240101);
INSERT INTO test_table(id,first_name, last_name, birth_day)
VALUES(4,'テスト', '花子', 20250101);

ファイル階層

  • /
    • src/
      • main/
        • java/
          • com/example/demo/
            • Entity/
              • JPAEntity.java
            • Repository/
              • JPARepository.java
        • resources/
          • application.properties
          • shema.sql
          • data.sql
          • ……
      • test/
        • java/com/example/demo/Repository/
          • JPARepositoryTest.java
    • build.gradle
    • ……

サンプルコード

JPAEntity.java

package com.example.demo.Entity;

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

/**
 * JPAのテストエンティティ
 * @author Takumi
 *
 */
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Table(name = "test_table")
public class JPAEntity {
	
	@Id
	private Integer id;
	private String first_name;
	private String last_name;
	private Integer birth_day;
}

JPARepository.java

package com.example.demo.Repository;

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

import com.example.demo.Entity.JPAEntity;

public interface JPARepository extends JpaRepository<JPAEntity, Integer>{
}

JPARepositoryTest.java

package com.example.demo.Repository;

import static org.junit.jupiter.api.Assertions.*;

import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

import com.example.demo.Entity.JPAEntity;

/**
 * {@link JPARepository}のテスト
 * @author Takumi
 *
 */
@SpringBootTest
@Transactional
class JPARepositoryTest {

	@Autowired
	private JPARepository repository;

	@Test
	void findAllTest() {
		List<JPAEntity> data = repository.findAll();
		
		assertEquals(data.size(), 4);
		assertEquals(data.get(0).getId(), 1);
		assertEquals(data.get(0).getFirst_name().strip(), "テスト");
		assertEquals(data.get(0).getLast_name().strip(), "太郎");
		assertEquals(data.get(0).getBirth_day(), 20240101);
	}

	@Test
	void findByIdTest() {
		Optional<JPAEntity> dataOpt = repository.findById(1);
		JPAEntity data = dataOpt.orElse(null);
		
		assertEquals(data.getId(), 1);
		assertEquals(data.getFirst_name().strip(), "テスト");
		assertEquals(data.getLast_name().strip(), "太郎");
		assertEquals(data.getBirth_day(), 20240101);
	}
	
	@Test
	void saveTest() {
		assertFalse(repository.existsById(9));
		
		JPAEntity insertEntity = new JPAEntity(9,"テストくん", "テストちゃん", 20200202);
		repository.save(insertEntity);
		
		assertTrue(repository.existsById(9));
		
		Optional<JPAEntity> insertDataOpt = repository.findById(9);
		JPAEntity insertData = insertDataOpt.orElse(null);
		
		assertEquals(insertData.getFirst_name().strip(), "テストくん");
		assertEquals(insertData.getLast_name().strip(), "テストちゃん");
		assertEquals(insertData.getBirth_day(), 20200202);
	}
	
	@Test
	void deleteTest() {
		assertTrue(repository.existsById(1));
		
		repository.deleteById(1);
		
		assertFalse(repository.existsById(1));
	}
}

サンプルコード詳細説明

Spring Data JPAに関する部分のみご説明します。

JPAEntity.java

このクラスは、テーブルとのマッピングをします。

schema,sqlで定義しているテーブルとマッピングしています。

マッピングするにはまず、
@Entity、@Table、そして@Idが必要になります。

もちろん@Tableはテーブル名であり、
@Idはプライマリキーを指定します。

今回は、テーブルのカラム名と
Entityのフィールド名を一致させているため必要ありませんが、
もしフィールド名をカラム名と一致させていない場合は、
@Columnを用いてマッピングさせる必要があります。

余談ですが、
Spring Data JDBCとの違いは、
テーブル名に日本語でも英語の小文字でも用いることができる
ということです。
ただ、テーブル名にキャメルケース1を用いた場合は、
スネークケース2として判断されるようですので、注意が必要そうです。

JPARepository.java

このinterfaceは、jpaRepositoryを継承するのが肝です。

ここがSpring Data JPAの肝です。

第一引数にマッピングしたテーブルのEntityクラスを、
第二引数にEntityクラスの@Idをつけているフィールドの型、
つまり、テーブルのプライマリキーの型を渡して継承
します。

このクラスを継承することで、
findAll、findById、delete、saveなどのメソッドを提供してくれ、
単純なSQL文であれば書く必要がなくなります。

JPARepositoryTest.java

テストケースごとに、Spring Data JPAに関連するところのみ
説明します。

findAllTest

findAllはテーブルにあるデータを全て取得するメソッドです。

	@Test
	void findAllTest() {
		List<JPAEntity> data = repository.findAll();
		
		assertEquals(data.size(), 4);
		assertEquals(data.get(0).getId(), 1);
		assertEquals(data.get(0).getFirst_name().strip(), "テスト");
		assertEquals(data.get(0).getLast_name().strip(), "太郎");
		assertEquals(data.get(0).getBirth_day(), 20240101);
	}

3行目

List<JPAEntity> data = repository.findAll();

で全てのデータを取得しています。

その後、
5〜9行目

assertEquals(data.size(), 4);
assertEquals(data.get(0).getId(), 1);
assertEquals(data.get(0).getFirst_name().strip(), “テスト”);
assertEquals(data.get(0).getLast_name().strip(), “太郎”);
assertEquals(data.get(0).getBirth_day(), 20240101);

でデータが正しく取得できていることを確認しています。
(本来ならば、テーブルにある4データすべての確認をするべきですが、
ここでは省略します。)

findByIdTest

findByIdは引数に取得したいデータのプライマリキーの値を渡すことで、
データを取得するメソッドです。

	@Test
	void findByIdTest() {
		Optional<JPAEntity> dataOpt = repository.findById(1);
		JPAEntity data = dataOpt.orElse(null);
		
		assertEquals(data.getId(), 1);
		assertEquals(data.getFirst_name().strip(), "テスト");
		assertEquals(data.getLast_name().strip(), "太郎");
		assertEquals(data.getBirth_day(), 20240101);
	}

3〜4行目

Optional<JPAEntity> dataOpt = repository.findById(1);
JPAEntity data = dataOpt.orElse(null);

で、データを取得しています。

その後、
6〜9行目

assertEquals(data.getId(), 1);
assertEquals(data.getFirst_name().strip(), “テスト”);
assertEquals(data.getLast_name().strip(), “太郎”);
assertEquals(data.getBirth_day(), 20240101);

でデータがきちんと取得できているかを
確認しています。

saveTest

saveメソッドは、引数に渡したマッピングしたEntityクラスのデータを
テーブルへインサート、もしくはテーブルをアップデートするメソッドです。

今回は、インサートの例です。

	@Test
	void saveTest() {
		assertFalse(repository.existsById(9));
		
		JPAEntity insertEntity = new JPAEntity(9,"テストくん", "テストちゃん", 20200202);
		repository.save(insertEntity);

		assertTrue(repository.existsById(9));
		
		Optional<JPAEntity> insertDataOpt = repository.findById(9);
		JPAEntity insertData = insertDataOpt.orElse(null);
		
		assertEquals(insertData.getFirst_name().strip(), "テストくん");
		assertEquals(insertData.getLast_name().strip(), "テストちゃん");
		assertEquals(insertData.getBirth_day(), 20200202);
	}

3行目

assertFalse(repository.existsById(9));

で、Idが9のデータが存在しないことを確認しています。
その後、
5〜6行目

JPAEntity insertEntity = new JPAEntity(9,”テストくん”, “テストちゃん”, 20200202);
repository.save(insertEntity);

で、Idが9のデータをインサートし、
8行目

assertTrue(repository.existsById(9));

で、インサートされたのかを確認しています。

最後に、
10〜15行目

Optional<JPAEntity> insertDataOpt = repository.findById(9);
JPAEntity insertData = insertDataOpt.orElse(null);

assertEquals(insertData.getFirst_name().strip(), “テストくん”);
assertEquals(insertData.getLast_name().strip(), “テストちゃん”);
assertEquals(insertData.getBirth_day(), 20200202);

で、データが正しくインサートされているのかを確認しています。

deleteTest

deleteByIdは、引数に消したいデータのプライマリキーの値を渡すことで、
そのデータを削除するメソッドです。

	@Test
	void deleteTest() {
		assertTrue(repository.existsById(1));
		
		repository.deleteById(1);
		
		assertFalse(repository.existsById(1));
	}

3行目

assertTrue(repository.existsById(1));

でIdが1のデータが損じすることを確認しています。
その後、
5行目

repository.deleteById(1);

で、Idが1のデータを削除し、
7行目

assertFalse(repository.existsById(1));

で、Idが1のデータが存在しなくなったことを確認しています。


  1. 単語間の区切り文字を大文字にしているもの(例) testTable ↩︎
  2. 単語間の区切り文字をアンダーバーにしているもの(例)test_table ↩︎