2017-06-05 61 views
2

我一直在弹簧数据REST中支持Spring Data的新应用程序原型Spring Data JPA & Hibernate,这对我的团队来说是一个非常棒的生产力提升器,但是随着数据模型变得越来越复杂,性能越来越差。纵观执行的SQL,我看到了两个独立但相关的问题:如何用Spring Data REST预测避免N + 1查询?

  1. 当使用Projection只有少数性能,以减少我的有效载荷的大小,SDR仍然加载整个实体图,所有的发生的开销。 编辑:提起DATAREST-1089

  2. 似乎有没有办法来指定使用JPA预先加载,因为SDR自动生成库的方法,所以我不能添加@EntityGraph他们。 (并且按照下面的DATAREST-905,即使这也不起作用)编辑:在Cepr0的答案中提到,尽管这只能应用于每个查找方法一次。见DATAJPA-749

我有使用要看具体情况(列表页,查看页面,自动完成,相关的网页等)的几种不同的投影一款关键车型,所以实现一个自定义ResourceProcessor不看起来像一个解决方案。)

有没有人找到解决这些问题的方法?否则,任何具有不平凡对象图的人都会看到随着模型增长,性能会急剧恶化。

我的研究:

回答

2

@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