0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[DDD ORM] Spring Data JDCB Repository操作

Last updated at Posted at 2023-05-06

はじめに

本記事は長文記事を避けるために連載方式を採用している。
以下が筆者が書いたSpring Data JDBC関連の記事である。

  1. [DDD ORM] Spring Data JDBC Entityの定義
  2. (本記事)[DDD ORM] Spring Data JDCB Repository操作

対象読者

  • Spring Data JDBCに入門したい
  • Spring Data JPAとの違いを知りたい
  • DDDに則ったORMを使用したい

復習

Spring Data JDBCとは

Spring Data JDBCはSpring(Spring Boot)が提供する
数あるデータアクセスフレームワークの一つ。

データアクセスオブジェクトをinterfaceとして定義しておくことで、
アプリケーションを起動する際にSpringがclassを自動生成してアプリケーションで使用してくれる。よってエンジニアはコネクションの管理などインフラ層の煩雑な処理を書かなくて済むといったようなSpring Dataの基本的な思想に基づいている。

またDDDの思想に強く影響を受けて開発されていることから、
集約やドメインイベントなどDDDのデータアクセスに則った実装を求められる。

Repositoryとは

DDDにおけるRepositoryとは

Spring Data JDBCにおいてRepositoryとはDDDのRepositoyパターンを再現する手法の一つであると考えて良い。データソースにアクセスし、Entityの永続化操作を担うのオブジェクトの総称である。

旧来のDaoパターンとの違いは、テーブル構造ではなくドメインモデルを主体とした永続化操作を行うところだ。Daoパターンではテーブルのデータ構造をほぼそのままJavanなどのプログラミング言語で反映させたようなオブジェクトにデータを格納しビジネスロジックで処理を行っていたが、Repositoryパターンではドメインモデル(Entity)に変換する。Repositoryがテーブルに格納されているデータをドメインモデルにマッピングしてビジネスロジックに渡すことで、ビジネスロジックにデータベースの都合を持ち込まれることがなくなる。

またDDDでは通常Repositoryはドメイン層の実装としてInterfaceが定義され、その具象クラスがインフラ層の実装として扱われる。これにより、DBをMySQLからMongoDBに移行するような場合でも、データベースの都合はすべてインフラ層で処理されるため、インフラ層に依存しないドメインモデルに変更を加える必要がない。これによりDBをシステムのコアとなる中核パーツではなく、ただの変更可能なパーツの一つとして扱うことができる。

Spring Data JDBCにおけるRepositoryとは

Spring JDBCの説明でも述べたように、このORMはDDDの思想に則って開発されているため、JPAの場合とは違い、Sprng Data JDBCのRepositoryはDDDのそれと同義である。そのためReposityはDDDのアンチパターンに気をつけながら実装しなければならない。

Repositoryの基本実装

Repsotiryの基本的な実装方法

Spring Data系のORMでは基本的にSpringによってインフラ層の具象クラスが実行時に自動的に作成される。よってよほどの場合を除いて、開発者はドメイン層の複雑で煩雑な実装を記述する必要はない。

Spring Data JDBCでRepositoryを実装する場合は、集約ルートとなるEntityとそのIDの型をジェネリクスで定義されている引数部分に下記のように指定してあげれば良い。

public interface BookRepository extends CrudRepository<Book, Integer> {
}

このように実装するだけでBook Entityの基本的なDBアクセス処理であるメソッド群がCrudRepositoryから提供される。実際Spring Data JDBCから提供される基底RepositoryはCrudRepositoy以外にもいくつかあるので必要に応じて使い分けることができる。

基底Repositoryの種類

Spring Data JDBCから提供される基底リポジトリはSpring Data Commonsに定義されている。その中から基本的なRepositoryを紹介する。

  1. CrudRepository
    1. 一番基本的なやつ
    2. 最低限のCRUD機能を提供する。
    3. Select結果はOptionalもしくはIterableで返却される。
  2. ListCrudRepository
    1. 基本的にはこれを選択すれば十分
    2. 最低限のCRUD機能を提供する。
    3. Select結果はOptionalもしくはListで返却される。
  3. PagingAndSortingRepository
    1. 正直一番使い道が微妙なやつ
    2. 最低限のCRUD機能に加え、ページング機能とソート機能を提供する
    3. Select結果はOptionalもしくはIterableで返却される。
  4. ListPagingAndSortingRepository
    1. CQRSパターンを採用しないのならこちらを選択すれば良い
    2. 最低限のCRUD機能に加え、ページング機能とソート機能を提供する
    3. Select結果はOptionalもしくはListで返却される。

カスタム基底Repositoryの追加

RepositoryとNoRepositoryBeanアノテーションを駆使することでカスタム基底リポジトリを作成することもできる。以下に読み取り専用のカスタム基底Repositoryを追加する場合の例を記述する。

@NoRepositoryBean
public interface ReadOnlyRepository<T, ID> extends Repository<T, ID> {
    Optional<T> findById(ID id);
    List<T> findAll();
}

public interface ConfRepository<Conf, String> extends ReadOnlyRepository {
}

Repositoryは、前章で取り扱った基底Repositoryの元となるインターフェースであり、これを継承することでSpringにDI対象のBeanであると認識させることができる。
また@NoRepositoryBeansを付与することで、SpringによってReadOnlyRepository自体の実装クラスとそのインスタンスが作成されるの防ぐことができ、ConfRepositoryのような派生Repositoryのみをインスタンス化対象と認識させることができる。

加えてSpring Data インターフェースを継承したくない場合は、リポジトリインターフェースに @RepositoryDefinition でアノテーションを付けることもできる。

Repositotyのカスタムクエリの追加

Spring Data JDBCのRepositoryに基底Repositoryから提供される以外のメソッドを追加する方法を以下の通りである。

  1. メソッド名によるカスタムクエリの自動追加
  2. Queryアノテーションによる手動追加
  3. Repositryの実装クラスの追加によるクエリ追加
  4. QueryDSL拡張による動的クエリ

上記を各章で詳しく解説していく。

メソッド名によるカスタムクエリの自動追加

Spring Data JDBCでは、他ののSpring Data系ORMと同様に、サポートされているキーワードと検索対象フィールド名を用いてメソッド名を定義するだけで、クエリを自動的に作成し、追加してくれる。
例えば以下のような例で、Pepleエンティティはid、firstName, lastNameフィールドを保持しているとする。

Person.java
@Data
public class Persion {
    private Integer id;
    private String firstName;
    private String lastName;
}
PersionRepository.java
public interface PersonRepository extends PagingAndSortingRepository<Person, Integer> {
    // 名前が一致するレコードを検索
    Optinal<Person> findByFirstName(String firstName); // ①
    // 名前が部分一致するレコードを取得
    List<Person> findByFirstnameLike(String firstName); // ②

    // 名前が部分一致するレコードを取得ページングとソートを付与して検索
    Page<Person> findByFirstnameLike(String firstName, Pageable pageable); // ③
}

①では名前検索で完全一致したフィールドを取得している。
②では名前検索で部分一致したEntityを返却させるために、戻り値をList型にしている。
③では②のメソッドにページング機能とソート機能を利用して検索することができるようにPagebleを引数に追加した。

上記のようにキーワドと対象フィールド名をメソッド名に明示し、引数の型を定義する。
このようにするだけでSpring Data JDBCが名前解決を行いクエリを自動作成してくれる。
サポートされているキーワドをは公式ドキュメントのクエリメソッドの章に記載されている。

また戻り値については、個別の結果を期待する場合はOptionalでのラップが推奨されており、複数の結果を期待する場合はコレクション型もしくはイテラブル型を用いる必要がある。使用可能なコレクションについては公式ドキュメントのこちらの項目をチェックすれば良い.

Queryアノテーションによる手動追加

複雑なクエリを作成したい場合、名前解決では複雑になってしまい、自前でSQLを記述しクエリを作成したいと思っているエンジニアは少なくないだろう。そうした場合にも対応できるようにQueryアノテーション + SQLによるクエリ追加もサポートされている。
以下は前章のRepositoryをQueryアノテーションで記述する場合のサンプルである。

PersionRepository.java
public interface PersonRepository extends PagingAndSortingRepository<Person, Integer> {
    // 名前が一致するレコードを検索
    @Query("SELECT * FROM Person WHERE firstName = :firstName")
    Optinal<Person> findByFirstName(String firstName); // ①

    // 名前が部分一致するレコードを取得
    @Query("SELECT * FROM Person WHERE firstName LIKE CONCAT('%',:firstName,'%')")
    List<Person> findByFirstnameLike(String firstName); // ②

    // 名前が部分一致するレコードを取得ページングとソートを付与して検索
    @Query("SELECT * FROM Person WHERE firstName LIKE CONCAT('%',:firstName,'%')")
    Page<Person> findByFirstnameLike(String firstName, Pageable pageable); // ③
}

Repositryの実装クラスの追加によるクエリ追加

Queryアノテーションでも実現できないような複雑な処理を行いたい場合はRepositoryImplというRepositoryの具象クラスを作成し、そちらに具体的な処理を委譲することもできる。こうすることでSpring Dataに依存しない処理を記述することや、DIを利用した処理を記述することもできる。
RepositoryインターフェースではSpringDataのRepositoryを継承していないことと、PostfixがImplである必要があることには注意したい。

interface CustomizedUserRepository {
  void someCustomMethod(User user);
}

class CustomizedUserRepositoryImpl implements CustomizedUserRepository {

  public void someCustomMethod(User user) {
    // Your custom implementation
  }
}

QueryDSL拡張による動的クエリ

Spring Data JDBCではQueryDSLによる拡張をサポートしている。
これによりカスタムクエリメソッドを定義することなく、FindAllなどのデフォルトのメソッドで検索条件にマッチしたEntityを取得することができる。検索条件などを含めたPredicateオブジェクトをQueryDSLで作成し、 QuerydslPredicateExecutorを継承したRepositoryに引数として渡してあげることで実現できる。Spring Data JPAのSpecification機能と同じようなことをSpring Data JDBCでも可能になるといった理解で問題ないだろう。

PersonRepository.java
public interface PersonRepository extends PagingAndSortingRepository<Person, Integer>, QuerydslPredicateExecutor<Person> {
}
Repository利用側のコード
Predicate predicate = person.firstname.equalsIgnoreCase("dave")
	.and(user.lastname.startsWithIgnoreCase("mathews"));

Iterable<Person> results = userRepository.findAll(predicate);

注意点

更新について

Springを利用する際のORMのデファクトスタンダードとなっているSpring Data JPAでは更新時にsaveメソッドを実行しなくても、Entityの状態変更を察知し、フラッシュ実行時にDBに自動的に変更を反映してくれた。しかしながらSpring Data JDBCには上記のような機能がないため。更新時には必ずsaveメソッドを呼び出し、明示的に変更をDBに反映させる必要がある

リポジトリのアンチパターン

何度も述べているようにSpring Data JDBCはDDDの思想に則って開発されているため、
利用側もDDDのデザインパターンを理解して使用する必要がある。
DDDのデザインパターンを採用しないのであれば、わざわざSpring Data JDBCを使用する必要はなく情報の多いSpring Data JPAや、もっとシンプルなMyBaitsやJDBC Templateを利用する方が分かりやすい実装になるというのが筆者の意見である。
具体的なアンチパターについては、以下の記事が丁寧にまとめられているので、そちらを一読してもらいたい。

まとめ

Spring Data JDBCのリポジトリ操作においては、他のSpring Data系のフレームワークとほとんど大差はないだろう。ただしこちらはDDDの思想に則っているため、Repositoryパターンを理解した上で実装を進める必要がある点に留意してほしい。

参考

Githubリポジトリ

文献

動画

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?