@EntityGraph
我使用存储库 '@EntityGraph' 注释为findAll
方法:要使用1个+ N问题我用下面两种方法战斗。只需覆盖它:
@Override
@EntityGraph(attributePaths = {"author", "publisher"})
Page<Book> findAll(Pageable pageable);
此方法适用于Repository的所有“读取”方法。
缓存
我用缓存减少1个+ N问题的复杂预测的影响。
假设我们有书实体存储图书数据和阅读实体存储有关特定图书和读者评价的读数数量的信息。为了得到这个数据,我们可以做出这样的预测:
@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。