1. 概要

この短いチュートリアルでは、読み取り専用のSpringデータリポジトリを作成する方法について説明します。

データベースを変更せずにデータベースからデータを読み取る必要がある場合があります。 この場合、読み取り専用のRepositoryインターフェースがあれば完璧です。

誰かがデータを変更するリスクなしにデータを読み取る機能を提供します。

2. リポジトリの拡張

spring-boot-starter-data-jpa依存関係を含むSpring Bootプロジェクトから始めましょう。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.4.3</version>
</dependency>

この依存関係には、SpringDataの人気のあるCrudRepository インターフェイスが含まれています。このインターフェイスには、ほとんどのアプリケーションが必要とするすべての基本的なCRUD操作(作成、読み取り、更新、削除)のメソッドが付属しています。 ただし、データを変更するいくつかのメソッドが含まれているため、データを読み取る機能しかないリポジトリが必要です。

CrudRepository は、実際にはRepositoryと呼ばれる別のインターフェイスを拡張します。 このインターフェースをニーズに合わせて拡張することもできます。

リポジトリを拡張する新しいインターフェースを作成しましょう。

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

ここでは、2つの読み取り専用メソッドのみを定義しました。 このリポジトリによってアクセスされるエンティティは、変更から安全になります。

また、 @NoRepositoryBean アノテーションを使用して、このリポジトリを汎用のままにすることをSpringに通知する必要があることに注意することも重要です。 これにより、読み取り専用リポジトリを必要な数の異なるエンティティに再利用できます。

次に、エンティティを新しいReadOnlyRepositoryに関連付ける方法を説明します。

3. ReadOnlyRepositoryを拡張しています

アクセスしたい単純なBookエンティティがあると仮定します。

@Entity
public class Book {
    @Id
    @GeneratedValue
    private Long id;
    private String author;
    private String title;

    //getters and setters
}

永続化可能なエンティティができたので、ReadOnlyRepositoryから継承するリポジトリインターフェイスを作成できます。

public interface BookReadOnlyRepository extends ReadOnlyRepository<Book, Long> {
    List<Book> findByAuthor(String author);
    List<Book> findByTitle(String title);
}

継承する2つのメソッドに加えて、ブック固有の読み取り専用メソッド findByAuthor() findByTitle()がさらに2つ追加されました。 合計で、このリポジトリは4つの読み取り専用メソッドにアクセスできます。

最後に、BookReadOnlyRepositoryの機能を確認するテストを作成しましょう。

@Test
public void givenBooks_whenUsingReadOnlyRepository_thenGetThem() {
    Book aChristmasCarolCharlesDickens = new Book();
    aChristmasCarolCharlesDickens.setTitle("A Christmas Carol");
    aChristmasCarolCharlesDickens.setAuthor("Charles Dickens");
    bookRepository.save(aChristmasCarolCharlesDickens);

    Book greatExpectationsCharlesDickens = new Book();
    greatExpectationsCharlesDickens.setTitle("Great Expectations");
    greatExpectationsCharlesDickens.setAuthor("Charles Dickens");
    bookRepository.save(greatExpectationsCharlesDickens);

    Book greatExpectationsKathyAcker = new Book();
    greatExpectationsKathyAcker.setTitle("Great Expectations");
    greatExpectationsKathyAcker.setAuthor("Kathy Acker");
    bookRepository.save(greatExpectationsKathyAcker);

    List<Book> charlesDickensBooks = bookReadOnlyRepository.findByAuthor("Charles Dickens");
    Assertions.assertEquals(2, charlesDickensBooks.size());

    List<Book> greatExpectationsBooks = bookReadOnlyRepository.findByTitle("Great Expectations");
    Assertions.assertEquals(2, greatExpectationsBooks.size());

    List<Book> allBooks = bookReadOnlyRepository.findAll();
    Assertions.assertEquals(3, allBooks.size());
    
    Long bookId = allBooks.get(0).getId();
    Book book = bookReadOnlyRepository.findById(bookId).orElseThrow(NoSuchElementException::new);
    Assertions.assertNotNull(book);
}

書籍を読み戻す前にデータベースに保存するために、テストスコープでCrudRepositoryを拡張するBookRepositoryを作成しました。 このリポジトリはメインプロジェクトスコープでは必要ありませんが、このテストでは必要でした。

public interface BookRepository
  extends BookReadOnlyRepository, CrudRepository<Book, Long> {}

4つの読み取り専用メソッドすべてをテストすることができ、ReadOnlyRepositoryインターフェイスを他のエンティティに再利用できるようになりました。

4. 結論

再利用可能な読み取り専用リポジトリを作成するために、SpringDataのRepositoryインターフェースを拡張する方法を学びました。 その後、それを単純な Book エンティティに結び付け、その機能が期待どおりに機能することを証明するテストを作成しました。

いつものように、このコードの実用的な例は、GitHubにあります。