💻

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

に公開

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

Spring Boot依存関係

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

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

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

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

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

環境・定義

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-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

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

設定値 説明 備考
none DDLスクリプト[1]の生成をしない。スキーマの更新は手動。
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
(
  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(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

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

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

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 を使用するとマッピングできます。

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

また、フィールドの型とカラムの型は一致させておく必要があります。
これに関しては、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);  

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

参考サイト

https://www.confrage.jp/spring-data-jdbcの使い方-【java】/

https://qiita.com/kuchita_el/items/33e43b6cfc6f2f2bb393

https://qiita.com/KenjiOtsuka/items/8450c407ba121fea8151

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

Discussion