我一直在Spring Data JPA支持的Spring Data REST中对我的新应用程序进行原型设计。 Hibernate,对我的团队来说是一个出色的生产力助推器,但随着数据模型变得越来越复杂,性能也越来越低。查看执行的SQL,我看到两个独立但相关的问题:
当使用只有少数属性的Projection
来减少有效负载的大小时,SDR仍在加载整个实体图,并产生所有开销。 编辑:提交DATAREST-1089
似乎没有办法使用JPA指定预先加载,因为SDR会自动生成存储库方法,因此我无法向其添加@EntityGraph
。 (并且根据下面的DATAREST-905,即使它不起作用)编辑:在下面的Cepr0的答案中解决,尽管这只能应用于每个finder方法一次。请参阅DATAJPA-749
我有一个关键模型,我根据上下文使用了几个不同的投影(列表页面,视图页面,自动完成,相关项目页面等),因此实现一个自定义ResourceProcessor
似乎不会像一个解决方案。)
有没有人找到解决这些问题的方法?否则,任何拥有非平凡对象图的人都会看到随着模型的增长,性能会急剧恶化。
我的研究:
答案 0 :(得分:2)
为了解决1 + N问题,我使用以下两种方法:
<强> @EntityGraph 强>
我在Repository中为findAll
方法使用'@EntityGraph'注释。只需覆盖它:
@Override
@EntityGraph(attributePaths = {"author", "publisher"})
Page<Book> findAll(Pageable pageable);
此方法适用于存储库的所有“读取”方法。
<强>缓存强>
我使用缓存来减少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);
在更新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。