💻

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

に公開

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

springDataJPA依存関係

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

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

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

https://github.com/tkmttkm/SQL.git

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

https://zenn.dev/tkmttkm/articles/bed90950f9e422

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

環境・定義

build.gradle

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

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の設定値については、以下に示します。

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

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

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

schema.sql

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

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

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

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

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のデータが存在しなくなったことを確認しています。

参考サイト

https://qiita.com/TaikiTkwkbysh/items/ba790d67e48e87799b70

https://blog.enjoyxstudy.com/entry/2016/12/26/000000

脚注
  1. 単語間の区切り文字を大文字にしているもの(例) testTable ↩︎

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

Discussion