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

この記事は約19分で読めます。
Spring Data JDBCアイキャッチ

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

このような画面で出てきます。



今回は、これがどのように使えるのか、
サンプルコードを交えてご紹介します。


なお、使用例については
JUnitを用いてご説明します!



また、コードについては以下のGitHubへ載せています。

環境・定義

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-jdbc'
	implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
	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()
}

Javaのバージョンや依存関係はこんな感じです。


GetterとSetterをいちいち生成するのは面倒なので、
lombokを用います。


また、データベースはh2を用います。
いちいち作るのが面倒だったので。

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=validateの設定値は以下です。

設定値説明備考
noneDDLスクリプト1の生成をしない。スキーマの更新は手動。
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
(
  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(ID,FIRST_NAME, LAST_NAME, BIRTH_DAY)
VALUES(1,'テスト', '太郎', 20240101);

INSERT INTO TEST(ID,FIRST_NAME, LAST_NAME, BIRTH_DAY)
VALUES(2,'テスト', '二郎', 20240101);

INSERT INTO TEST(ID,FIRST_NAME, LAST_NAME, BIRTH_DAY)
VALUES(3,'テスト', '三郎', 20240101);

INSERT INTO TEST(ID,FIRST_NAME, LAST_NAME, BIRTH_DAY)
VALUES(4,'テスト', '花子', 20250101);

ファイル階層

  • /
    • src/
      • main/
        • java/
          • com/example/demo/
            • Entity/
              • DataJDBCEntity.java
            • Repository/
              • DataJDBCRepository.java
        • resources/
          • application.properties
          • shema.sql
          • data.sql
          • ……
      • test/
        • java/com/example/demo/Repository/
          • DataJDBCRepositoryTest.java
    • build.gradle
    • ……

サンプルコード

DataJDBCEntity.java

package com.example.demo.Entity;

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

/**
 * DataJDBCテスト用
 * @author Takumi
 *
 */
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Table("TEST")
public class DataJDBCEntity {
	@Id
	private Integer id;
	private String firstName;
	private String lastName;
	private Integer birthDay;
}

DataJDBCRepository.java

package com.example.demo.Repository;

import org.springframework.data.repository.CrudRepository;

import com.example.demo.Entity.DataJDBCEntity;

public interface DataJDBCRepository extends CrudRepository<DataJDBCEntity, Integer> {
}

DataJDBCRepositoryTest.java

package com.example.demo.Repository;

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

import java.util.ArrayList;
import java.util.Iterator;
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.DataJDBCEntity;

@SpringBootTest
@Transactional
class DataJDBCRepositoryTest {

	@Autowired
	private DataJDBCRepository repository;

	@Test
	void testFindAll() {
		Iterable<DataJDBCEntity> allData = repository.findAll();
		List<DataJDBCEntity> alldataList = new ArrayList<DataJDBCEntity>();
		
		Iterator<DataJDBCEntity> iterator = allData.iterator();
		while(iterator.hasNext()) {
			alldataList.add(iterator.next());
		}
		
		assertEquals(alldataList.size(), 4);
	}
	
	@Test
	void testFindById() {
		Optional<DataJDBCEntity> dataOpt = repository.findById(1);
		DataJDBCEntity data = dataOpt.isPresent() ? dataOpt.get() : null;
		
		assertEquals(data.getFirstName().strip(), "テスト");
		assertEquals(data.getLastName().strip(), "太郎");
		assertEquals(data.getBirthDay(), 20240101);
	}
	
	@Test
	void testDeleteById() {
		assertTrue(repository.existsById(1));
		
		repository.deleteById(1);
		
		assertFalse(repository.existsById(1));
	}
	
	@Test
	void testSave() {
		Optional<DataJDBCEntity> beforeDataOpt = repository.findById(1);
		DataJDBCEntity beforeData = beforeDataOpt.isPresent() ? beforeDataOpt.get() : null;
		
		assertEquals(beforeData.getFirstName().strip(), "テスト");
		assertEquals(beforeData.getLastName().strip(), "太郎");
		assertEquals(beforeData.getBirthDay(), 20240101);
		
		DataJDBCEntity data = new DataJDBCEntity(1, "テストテスト", "save太郎くん", 20240214);
		repository.save(data);
		
		Optional<DataJDBCEntity> updatedOpt = repository.findById(1);
		DataJDBCEntity updatedData = updatedOpt.isPresent() ? updatedOpt.get() : null;
		
		assertEquals(updatedData.getFirstName().strip(), "テストテスト");
		assertEquals(updatedData.getLastName().strip(), "save太郎くん");
		assertEquals(updatedData.getBirthDay(), 20240214);
	}

}

サンプルコード詳細説明

ここでは、Spring Data JDBCに関連するところのみご説明します。

DataJDBCEntity.java

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

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

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

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

ただ、注意が必要なのですが、
Spring Data JDBC でマッピングできるのは
テーブルのスキーマが全て大文字の英語の時のみのようです。
(どれだけ小文字で試しても私はマッピングできませんでした。
もしできた方は教えて欲しいです。。。)

小文字のテーブルのスキーマの場合は、
Spring Data JPA を使用するとマッピングできます。

また、フィールドの型とカラムの型は一致させておく必要があります。
これに関しては、application.properties
spring.jpa.hibernate.ddl-autoをどの設定にしているかにもよりますが、
今回は、validateに設定しているため、
フィールドの型とカラムの型が一致していないと、
コンパイルエラーを起こします。

余談ですが、プライマリキーが複数ある場合は、
@Idをつけるフィールドをクラスで持たせるなど、
工夫が必要となります。

DataJDBCRepository.java

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

ここがSpring Data JDBCの肝です。

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

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

DataJDBCRepositoryTest.java

使用例を確認していきます。
Junitの詳細な説明は省き、テストケースごとに説明します。

ちなみにこちらのJUnitは全てきちんと通っていますので
ご安心ください。

testFindAll

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

	@Test
	void testFindAll() {
		Iterable<DataJDBCEntity> allData = repository.findAll();
		List<DataJDBCEntity> alldataList = new ArrayList<DataJDBCEntity>();
		
		Iterator<DataJDBCEntity> iterator = allData.iterator();
		while(iterator.hasNext()) {
			alldataList.add(iterator.next());
		}
		
		assertEquals(alldataList.size(), 4);
	}

まず、
3行目
Iterable allData = repository.findAll();

でテーブルのすべてのデータを取得しています。

その後、
4〜9行目
List<DataJDBCEntity> alldataList = new ArrayList<DataJDBCEntity>();

Iterator<DataJDBCEntity> iterator = allData.iterator();
while(iterator.hasNext()) {
 alldataList.add(iterator.next());
}

ここで、取得したデータをListに詰め直しています。

最後に、
11行目
assertEquals(alldataList.size(), 4);

で取れたデータの数を検証しています。


data.sqlでは4つのデータを挿入していたので、
findAllですべてのデータが取れていることがわかります。

testFindById

findByIdは設定したプラマリキーに対応するデータを取得するメソッドです。

	@Test
	void testFindById() {
		Optional<DataJDBCEntity> dataOpt = repository.findById(1);
		DataJDBCEntity data = dataOpt.isPresent() ? dataOpt.get() : null;
		
		assertEquals(data.getFirstName().strip(), "テスト");
		assertEquals(data.getLastName().strip(), "太郎");
		assertEquals(data.getBirthDay(), 20240101);
	}

3行目
Optional<DataJDBCEntity> dataOpt = repository.findById(1);

でIdが1のデータを取得して、
4行目
DataJDBCEntity data = dataOpt.isPresent() ? dataOpt.get() : null;

でDataJDBCEntityクラスに型を変換しています。

6〜8行目
assertEquals(data.getFirstName().strip(), “テスト”);
assertEquals(data.getLastName().strip(), “太郎”);
assertEquals(data.getBirthDay(), 20240101);

で取得できたことを確認しています。

testDeleteById

deleteByIdは、設定したプライマリキーに対応するデータを削除するメソッドです。

	@Test
	void testDeleteById() {
		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));

でしています。

testSave

saveは設定したデータに既存データをUPDATEする、
もしくはセッテーしたデータをINSERTするメソッドです。
今回は、UPDATEの例です。

	@Test
	void testSave() {
		Optional<DataJDBCEntity> beforeDataOpt = repository.findById(1);
		DataJDBCEntity beforeData = beforeDataOpt.isPresent() ? beforeDataOpt.get() : null;
		
		assertEquals(beforeData.getFirstName().strip(), "テスト");
		assertEquals(beforeData.getLastName().strip(), "太郎");
		assertEquals(beforeData.getBirthDay(), 20240101);
		
		DataJDBCEntity data = new DataJDBCEntity(1, "テストテスト", "save太郎くん", 20240214);
		repository.save(data);
		
		Optional<DataJDBCEntity> updatedOpt = repository.findById(1);
		DataJDBCEntity updatedData = updatedOpt.isPresent() ? updatedOpt.get() : null;
		
		assertEquals(updatedData.getFirstName().strip(), "テストテスト");
		assertEquals(updatedData.getLastName().strip(), "save太郎くん");
		assertEquals(updatedData.getBirthDay(), 20240214);
	}

3行目〜8行目
Optional<DataJDBCEntity> beforeDataOpt = repository.findById(1);
DataJDBCEntity beforeData = beforeDataOpt.isPresent() ? beforeDataOpt.get() : null;

assertEquals(beforeData.getFirstName().strip(), “テスト”);
assertEquals(beforeData.getLastName().strip(), “太郎”);
assertEquals(beforeData.getBirthDay(), 20240101);

で既存データを確認しています。

その後、
10〜11行目
DataJDBCEntity data = new DataJDBCEntity(1, “テストテスト”, “save太郎くん”, 20240214);
repository.save(data);

にてデータを更新。

13〜18行目
Optional<DataJDBCEntity> updatedOpt = repository.findById(1);
DataJDBCEntity updatedData = updatedOpt.isPresent() ? updatedOpt.get() : null;

assertEquals(updatedData.getFirstName().strip(), “テストテスト”);
assertEquals(updatedData.getLastName().strip(), “save太郎くん”);
assertEquals(updatedData.getBirthDay(), 20240214);

でデータが更新されたことを確認しています。


  1. CREATE, ALTER, DROP, TRUNCATE, COMMIT ↩︎