Spring Bootキャンプシリーズ、Spring Boot + Spring Data JDBC編です。
今回の目的
Spring BootアプリでSpring Data JDBCを利用してDBアクセスを行います。
今回使用するライブラリ
- spring-boot-starter:2.2.0.M4
- spring-boot-starter-data-jdbc:2.2.0.M4
- spring-boot-starter-test:2.2.0.M4
- h2:1.4.199
- lombok:1.18.8
以降の手順のいくつかはSpring Initializrでプロジェクトを作成することにより解決されます。
DBアクセス
Spring Data JDBCの実装を行う前に、まずはDBアクセスするための設定を行います。
DBアクセスの定義
Spring BootではDBにアクセスするDataSource
等のBean定義を自動的に行ってくれます。
デフォルトではインメモリのH2データベースにアクセスするため、依存関係にh2を追加する必要があります。
pom.xml
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
Note.
Spring Bootの設定ファイルでドライバと接続先を変更すれば、別DBにアクセスできます。(依存関係に対応するドライバを追加する必要があります)src/main/resources/application.yml
spring: datasource: driver-class-name: org.postgresql.Driver url: jdbc:postgresql://localhost:5432/testdb username: postgres password: postgres
src/main/resources直下に以下のSQLファイルを作成すると、アプリ起動時にデータベースの初期化も自動的に行うこともできます。
デフォルトで認識されるSQLファイルは以下の通りです。
- schema.sql
- schema-${platform}.sql
- data.sql
- data-${platform}.sql
見たままですが、schema.sqlにはDDLを定義し、data.sqlにはDMLを定義します。
Note.
${platform}
はspring.datasource.platform
プロパティで指定します。
単体テスト時はH2、結合テスト時はPostgresqlといった使い分けができそうですね。
今回はschema.sqlを作成してテーブルを作成します。
src/main/resources/schema.sql
create table if not exists todo (
todo_id identity,
todo_title varchar(30),
finished boolean,
created_at timestamp
);
spring-boot-starter-data-jdbc
Spring BootでSpring Data JDBCを利用するためのスターターです。
ぶっちゃけ詳細は開発者の@kazuki43zooさんの記事が詳しいので、そちらを見たほうが良いですw
Spring BootのAuto Configurationの仕組みを利用することで、Spring BootアプリでSpring Data JDBCを使用するためのBean定義を自動的に行ってくれます。開発者は依存関係にspring-boot-starter-data-jdbcを追加するだけでOKです。
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
</dependencies>
src/main/java/*/Application.java
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
メインクラスはSpring Bootアプリのデフォルトから何も変更していません。
そもそもSpring Data JDBCとは?
SpringのDBアクセスといえばSpring DataのJdbcTemplateかSpring Data JPAですが、Spring Data JDBCはその両方をハイブリッドしたようなものです。
Spring Data JPAのような文法でRepositoryを定義すると、JdbcTemplate(NamedParameterJdbcTemplate)を利用してSQLが実行されます。
Spring Data JDBCがサポートする主な機能は以下の通りです。
- CRUDを実現するシンプルなAPIを提供する
-
@Query
でカスタムクエリを定義できる - SQL実行前後などのイベントをインターセプトする
- Spring Data JDBCを経由してMyBatisのSQLを実行できる -> 別エントリで!
- これらをAuto-Configする
Spring Data JPAと比べると、ぱっと見以下のような違いがあります。
- カスタムクエリはJPQLではなくSQL(NamedParameterJdbcTemplateの文法)で定義する
- Lazy Fetch(遅延読み込み)のような機能がない
Domainクラス
DBのデータをマッピングするドメインクラスには、プライマリキーを識別する@ID
を付与します。
以下では、GetterやSetterの実装を省略するため、Lombokの@Data
を利用しています。
src/main/java/*/domain/Todo.java
@Data
public class Todo {
@Id
private String todoId;
private String todoTitle;
private boolean finished;
private LocalDateTime createdAt;
}
Note.
テーブル名とDomainクラス名が異なるときは、クラスに@Table("テーブル名")
を付与すればOKです。
Repositoryインターフェイス
SpringのRepositoryインターフェイスは、Spring DataのCrudRepositoryインターフェイスを継承します。
src/main/java/*/repository/TodoRepository.java
// (1)
public interface TodoRepository extends CrudRepository<Todo, String> {
// (2)
// (3)
@Query("SELECT COUNT(*) FROM todo WHERE finished = :finished")
long countByFinished(@Param("finished") boolean finished);
}
(1) RepositoryインターフェイスはCrudRepositoryを継承します。ジェネリクスは<Domainクラスの型, IDの型>
です。
(2) CrudRepositoryを継承すると、自動的にCRUDをサポートするデフォルトメソッド(クエリ)が利用可能になります。提供されるメソッドはCrudRepositoryのJavaDocを確認すると分かります。
(3) カスタムクエリを実装したければ、@Query
をメソッドに付与してSQLを記述します。@Param
を付与した引数が、SQL内の:パラメータ名
に埋め込まれます。分かりやすいですね。
Note.
更新系のカスタムクエリには、@Query
+@Modifying
を付与する必要があります。
複数行のSQL
上ではSQLを1行で記載していますが、可読性が悪いので複数行で記載することをお勧めします。
こんな感じで文字列結合する形になります。
@Query("SELECT"
+ " COUNT(*)"
+ " FROM todo"
+ " WHERE"
+ " finished = :finished")
long countByFinished(@Param("finished") boolean finished);
CRUDのデフォルトメソッドを使わないとき
テーブル構成が複雑でテーブル単位のCRUDを使えないときは、CrudRepositoryの代わりにRepositoryインターフェイスを継承すればOKです。
カスタムクエリを実装する要領で、@Query
メソッドを実装していきます。
public interface TodoRepository extends Repository<Todo, String> {
@Query("SELECT ...")
long findAll();
}
動的SQL
Spring Data JDBCでは、動的SQLの組み立てはサポートされていないと思われます。
Spring Data JPAのCrudRepositoryインターフェイスにはExample引数を受け取るメソッドが定義されていますが、Spring DataのCrudRepositoryインターフェイスには定義されておらず、非常に簡素です。
spring-boot-starter-test
Spring Bootをテストするためのスターターです。
Spring BootのAuto Configurationの仕組みを利用することで、Spring BootアプリをテストするためのBean定義を自動的に行ってくれます。開発者は依存関係にspring-boot-starter-testを追加して、少しの設定をするだけでOKです。
pom.xml
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
JUnitテストケース
JUnitテストケースでは、クラスに@DataJdbcTest
を付与してテスト対象のリポジトリを@Autowired
するだけでOKです。
src/test/java/*/repository/TodoRepositoryTest.java
@DataJdbcTest
@Transactional
class TodoRepositoryTest {
@Autowired
TodoRepository todoRepository;
@Test
@Sql(statements = "INSERT INTO todo (todo_title, finished, created_at) VALUES ('sample todo', false, '2019-01-01')")
void testFindAll() {
// execute
Iterable<Todo> todos = todoRepository.findAll();
// assert
assertThat(todos)
.hasSize(1)
.extracting(Todo::getTodoTitle, Todo::isFinished, Todo::getCreatedAt)
.containsExactly(tuple("sample todo", false, LocalDate.of(2019, 1, 1).atStartOfDay()));
}
@DataJdbcTest
はSpring Data JDBCを利用するためのBean定義、DBにアクセスするDataSource
等のBean定義を自動的に行ってくれます。
@DataJdbcTest
は@Transactional
を付与してくれないので、テスト終了後にロールバックするためには自分で@Transactional
を付与する必要があります。@DataJpaTest
は付与してくれるのに、なぜだ、、、
Note.
Spring Boot 2.2.2から@DataJdbcTest
で@Transactional
が付与されるようになりました。
まとめ
Spring Data JDBCのスターターを利用することで、設定周りを省略してRepositoryの実装に注力することができました。
Spring Data JDBCからMyBatisの連携については、別エントリで。