2017-06-05 113 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