为了解决 1+N 问题,我使用以下两种方法:
@EntityGraph
我在存储库中为findAll 方法使用'@EntityGraph' 注释。只需覆盖它:
@Override
@EntityGraph(attributePaths = {"author", "publisher"})
Page<Book> findAll(Pageable pageable);
这种方法适用于 Repository 的所有“读取”方法。
缓存
我使用 缓存 来减少复杂投影的 1+N 问题的影响。
假设我们有 Book 实体来存储图书数据和 Reading 实体来存储有关特定图书的阅读次数及其读者评分的信息。为了得到这些数据,我们可以像这样进行投影:
@Projection(name = "bookRating", types = Book.class)
public interface WithRatings {
String getTitle();
String getIsbn();
@Value("#{@readingRepo.getBookRatings(target)}")
Ratings getRatings();
}
其中readingRepo.getBookRatings是ReadingRepository的方法:
@RestResource(exported = false)
@Query("select avg(r.rating) as rating, count(r) as readings from Reading r where r.book = ?1")
Ratings getBookRatings(Book book);
它还返回一个存储“评级”信息的投影:
@JsonSerialize(as = Ratings.class)
public interface Ratings {
@JsonProperty("rating")
Float getRating();
@JsonProperty("readings")
Integer getReadings();
}
/books?projection=bookRating 的请求将导致每本书都调用readingRepo.getBookRatings,这将导致冗余 N 个查询。
为了减少这种影响,我们可以使用缓存:
在SpringBootApplication类中准备缓存:
@SpringBootApplication
@EnableCaching
public class Application {
//...
@Bean
public CacheManager cacheManager() {
Cache bookRatings = new ConcurrentMapCache("bookRatings");
SimpleCacheManager manager = new SimpleCacheManager();
manager.setCaches(Collections.singletonList(bookRatings));
return manager;
}
}
然后给readingRepo.getBookRatings方法添加对应的注解:
@Cacheable(value = "bookRatings", key = "#a0.id")
@RestResource(exported = false)
@Query("select avg(r.rating) as rating, count(r) as readings from Reading r where r.book = ?1")
Ratings getBookRatings(Book book);
并在书本数据更新时实现缓存驱逐:
@RepositoryEventHandler(Reading.class)
public class ReadingEventHandler {
private final @NonNull CacheManager cacheManager;
@HandleAfterCreate
@HandleAfterSave
@HandleAfterDelete
public void evictCaches(Reading reading) {
Book book = reading.getBook();
cacheManager.getCache("bookRatings").evict(book.getId());
}
}
现在/books?projection=bookRating 的所有后续请求都将从我们的缓存中获取评分数据,不会导致对数据库的冗余请求。
更多信息和工作示例是here。