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の設定値は以下です。
設定値 | 説明 | 備考 |
---|---|---|
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
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
- Entity/
- com/example/demo/
- resources/
- application.properties
- shema.sql
- data.sql
- ……
- java/
- test/
- java/com/example/demo/Repository/
- DataJDBCRepositoryTest.java
- java/com/example/demo/Repository/
- main/
- build.gradle
- ……
- src/
サンプルコード
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);
でデータが更新されたことを確認しています。
参考サイト
- CREATE, ALTER, DROP, TRUNCATE, COMMIT ↩︎