为了解决1+N问题,我使用以下两种方法:
@EntityGraph
在Repository的findAll方法中,我使用@EntityGraph注释。只需要覆盖它:
@Override
@EntityGraph(attributePaths = {"author", "publisher"})
Page<Book> findAll(Pageable pageable);
这种方法适用于 Repository 的所有“读取”方法。
缓存
我使用缓存来减少复杂投影中1+N 问题的影响。
假设我们有一个Book实体来存储图书数据,以及一个Reading实体来存储特定 Book 的阅读量和读者评级的信息。要获取这些数据,我们可以创建如下的投影:
@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
的请求都将从我们的缓存中获取评分数据,并且不会导致冗余的数据库请求。
更多信息和工作示例请参见
此处。