1. 概述
在本篇教程中,我们将探讨如何在 Spring Data 中创建一个只读 Repository。
在某些场景下,我们只需要从数据库中读取数据,而不需要对其进行任何修改。此时,使用一个只读的 Repository 接口会非常合适。
它能提供完整的数据读取能力,同时避免数据被意外修改的风险 ✅。
2. 继承 Repository 接口
我们从一个包含 spring-boot-starter-data-jpa
依赖的 Spring Boot 项目开始:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<version>3.1.5</version>
</dependency>
这个依赖中包含了 Spring Data 提供的 CrudRepository
接口,它封装了常用的 CRUD 操作。但问题在于,它也包含了修改数据的方法(如 save、delete 等),而这正是我们要避免的。
CrudRepository
实际上继承自一个更基础的接口:Repository
。我们可以直接继承 Repository
,来自定义只读行为。
下面是我们自定义的只读接口:
@NoRepositoryBean
public interface ReadOnlyRepository<T, ID> extends Repository<T, ID> {
Optional<T> findById(ID id);
List<T> findAll();
}
这里我们只定义了两个只读方法。✅ 通过这种方式,任何使用该接口的实体都将无法被修改。
另外,使用 @NoRepositoryBean
注解非常重要,它告诉 Spring 不要为这个接口创建具体的实现 Bean。这样我们就可以将这个只读接口复用到多个实体上。
接下来,我们看看如何将这个接口与具体实体绑定。
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);
}
除了继承的 findById
和 findAll
,我们又增加了两个只读的查询方法:findByAuthor()
和 findByTitle()
。✅ 这样,该 Repository 总共拥有了四个只读方法。
我们写一个测试用例来验证其功能:
@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);
}
⚠️ 注意:为了在测试中插入数据,我们额外创建了一个普通的 BookRepository
(继承自 CrudRepository
):
public interface BookRepository extends CrudRepository<Book, Long> {}
这个 Repository 只用于测试,项目主代码中并不需要它。
通过这个测试,我们验证了所有四个只读方法都能正常工作,并且可以将 ReadOnlyRepository
复用到其他实体上。
4. 小结
本教程中我们学习了如何继承 Spring Data 的 Repository
接口来创建一个通用的只读 Repository,并通过 Book
实体进行了演示和测试。
这种做法简单粗暴,非常适合在需要严格控制写操作的场景下使用 ✅。
完整代码示例可以在 GitHub 上找到:https://github.com/eugenp/tutorials/tree/master/spring-boot-modules/spring-boot-data-2