Spring Data Dijkstra 有哪些新特性?

工程 | Oliver Drotbohm | May 21, 2014 | ...

我們剛剛宣佈了名為 Dijkstra 的 Spring Data 發行火車 GA 版本的可用性。我想借此機會向您介紹在此版本中新增的一些功能。

5 個新模組加入發行火車

該版本包含的第一個重要特性是向發行火車添加了 5 個模組。其中大多數已經存在了一段時間,但從今往後,我們將與其它模組同步釋出它們。新新增的模組包括 Spring Data ElasticsearchCassandraCouchbaseGemfireRedis

Spring Data Commons

發行火車的許多改進通常會落入 Commons 模組,以便各個儲存模組都可以從新新增的功能中受益。以下是 Dijkstra 中最重要的幾項改進:

支援包裝型別作為返回值

對於宣告返回單個域型別例項的 Spring Data 儲存庫方法,如果無法獲得結果,儲存庫會返回 null。然而,如果我們從頭開始構建 API,而不是直接返回例項,我們可能更傾向於使用 Optional 來確保客戶端不會意外忘記進行 null 檢查。

Optional 是當今許多 Java 庫提供的一種型別。Google Guava 提供了它,更重要的是,JDK 8 也提供了一個。因此,透過 Dijkstra 發行火車,我們提供了使用這些型別作為返回值包裝器的可能性,並讓 Spring Data 儲存庫基礎設施自動為您包裝 null 值。

interface CustomerRepository extends Repository<Customer, Long> {

  Optional<Customer> findOne(Long id);

  Optional<Customer> findByEmailAddress(EmailAddress emailAddress);
}

您在此處看到的第一個方法是 CrudRepository.findOne(…) 的一個變體。請注意,儲存庫介面不繼承 CrudRepository,因為這會導致編譯錯誤,因為我們無法重新宣告 findOne(…) 方法並更改返回型別。因此,如果您想改變 findOne(…) 的行為,我們建議您簡單地建立自己的基礎儲存庫介面(如參考文件中所述)。

第二個方法是一個簡單的查詢方法,它使用查詢派生(query derivation)讓 Spring Data 基礎設施從方法名派生出一個查詢。我們在這裡也檢測到 Optional 作為包裝型別,執行查詢並自動將結果包裝成 Optional 例項。

非同步儲存庫方法呼叫

與此相關,我們開始向儲存庫方法新增非同步執行能力。查詢方法現在可以返回 Future<T>,如果它用 @Async 進行註解,這將導致其非同步執行。

interface CustomerRepository extends Repository<Customer, Long> {

  @Async
  @Query(" … long running query declaration … ")
  Future<List<Customer>> findByLongRunningQuery(String lastname);
}

然後,執行是基於 Spring 對非同步方法呼叫的支援。我們還在研究更高階的非同步執行模型,例如承諾(可能基於Project Reactor),以便在未來的 Spring Data 版本中新增。

地理空間型別

MongoDB 模組(以及其他模組)一直支援 Mongo 的地理空間操作——無論是在 MongoTemplate 中還是在儲存庫抽象中。在當前版本中,我們將核心值型別(例如 Shape, Point, Box, Distance 等)移到了 Spring Data Commons 模組中。這將允許您普遍地引用地理型別並與所有支援地理空間功能的 Spring Data 模組互動,而無需將這些型別相互對映。檢視 JavaDoc 瞭解詳情。

Slice(切片)

Spring Data 在儲存庫程式設計模型中一直支援分頁功能。它允許您按頁訪問資料以迭代大型資料集。除了純頁面內容,Page 介面還暴露了 API 以查詢總元素數和總頁數。

計算這個數字可能非常消耗資源(因為通常需要執行額外的查詢),而且很多時候,關於頁面的唯一有趣元資訊是它是否還有下一頁,以便客戶端可以繼續檢索更多資料。例如,您可以在 Facebook 時間線中看到這種模式。

Dijkstra 版本現在引入了 Page 的簡化版本,稱為 Slice,它允許您瞭解當前切片後面是否還有更多內容。

interface BlogPostRepository extends Repository<BlogPost, Long> {

  Slice<BlogPost> findByAuthorOrderByDateDesc(Author author, Pageable pageable);
}

您現在可以使用 Pageable 來表示您想要獲取的頁碼和大小,Spring Data 基礎設施將比請求多讀取一個專案,並使用它的存在或不存在來指示下一個切片是否可用。

派生的刪除查詢

查詢方法派生功能現在支援 deleteBy…removeBy… 方法字首,以便根據給定的條件派生用於刪除受管域型別的查詢。

interface BlogPostRepository extends Repository<BlogPost, Long> {

  int deleteByDateBefore(Date date);
}

此功能已在 Dijkstra 的 JPA、MongoDB 和 Solr 模組中實現,未來將新增到其他儲存模組中。

JPA 2.1 支援

在 Dijkstra 的 JPA 模組開發期間,一個核心主題是對 JavaEE 7 / JPA 2.1 功能的支援。我們在本次釋出中解決的核心領域是對查詢執行中的實體圖(entity graphs)支援以及儲存過程的執行。

實體圖(Entity graphs)

假設我們有以下域型別定義

@Entity
@NamedEntityGraph(name = "summary", attributeNodes = { 
  @NamedAttributeNode("firstname"),
  @NamedAttributeNode("lastname")})
class Customer {
  // properties ommitted
}

我們現在可以透過 @EntityGraph 註解引用命名實體圖,以指示我們希望將此圖應用於正在執行的查詢。

interface CustomerRepository extends Repository<Customer, Long> {

  @EntityGraph("summary")
  Optional<Customer> findByEmailAddress(EmailAddress emailAddress);
}

這將導致只有 firstnamelastname 屬性被急切載入,而其他所有屬性都準備在訪問時延遲載入。

儲存過程

JPA 2.1 添加了透過 EntityManager API 執行儲存過程的能力。與上面的示例類似,儲存過程的元資料可以在域型別上宣告。假設您想要觸發一個為客戶隨機建立密碼的儲存過程

@Entity
@NamedStoredProcedureQuery(name = "Customer.generateNewPassword", 
  procedureName = "generateNewPassword", parameters = {
    @StoredProcedureParameter(
      mode = ParameterMode.IN, name = "username", type = String.class),
    @StoredProcedureParameter(
      mode = ParameterMode.OUT, name = "password", type = String.class)})
class Customer {
  // properties ommitted
}

儲存過程可以使用這樣的儲存庫查詢方法執行

interface CustomerRepository extends Repository<Customer, Long> {

  @Procedure
  String generateNewPassword(@Param("username") String username);
}

預設情況下,我們將使用透過著名的 DomainType.methodName 模式找到的儲存過程元資料,並將其與方法宣告匹配。對於像此處所示的非常簡單的過程對映,您甚至可以省略元資料宣告,因為所有元資料都可以從方法名派生。在參考文件中查詢有關儲存過程支援的更多資訊。

Redis 的事務支援

Redis 模組的最新版本添加了累積一組可批次執行操作的功能。為了實現這一點,RedisTemplate 現在可以透過將 enableTransactionSupport 屬性配置為 true 來與 Spring 的事務同步整合(預設為 false)。

啟用此功能將導致 RedisConnection 繫結到當前 Thread 併發出 MULTI 命令,從而允許底層 Redis 驅動程式潛在地執行命令排隊。如果事務沒有錯誤地完成,將發出 EXEC 命令;如果失敗,則使用 DISCARD 命令放棄累積的命令。

一旦啟用,連線將繫結到當前執行緒,確保每個寫入操作都透過同一個連線傳送並排隊等待周圍事務完成。讀取操作(例如 KEYS 命令)仍將透過使用新的、非執行緒繫結的連線立即執行。

@Bean
public StringRedisTemplate redisTemplate() {

	StringRedisTemplate template = 
	  new StringRedisTemplate(redisConnectionFactory());
	// Enable transaction synchronization support
	template.setEnableTransactionSupport(true);

	return template;
}

像這樣配置的 RedisTemplate 然後可以與以下語義一起使用

// Executed on thread bound connection
template.opsForValue().set("foo", "bar");

// Read operation executed on separate connection
template.keys("*");

// Returns null as values set within transaction are not visible
// prior to transaction flush
template.opsForValue().get("foo");

Spring Data Solr 中的複雜查詢支援

使用 Spring Data Solr 的 criteria API 建立更復雜的查詢的需求已經存在一段時間了。因此,我們決定重寫部分實現,同時保持 API 與之前版本相容。

基本上,我們從相當扁平的表示形式轉向了樹狀模型,同時保留了我們習慣的流暢 API 風格。

Solr 查詢 q=name:solr OR (type:spring AND category:data) 現在可以表示為

new SimpleQuery(
  where("name").is("solr").or(
    where("type").is("spring").and("category").is("data")));

Spring Data REST 中的投影

Spring Data REST 暴露的 REST 資源的一個非常常見的需求是能夠建立自定義表示。這意味著,使用者希望減少響應中呈現的屬性數量,或者內聯關聯實體以節省伺服器往返。在 Spring Data REST 2.1 中,我們現在提供了在伺服器端定義自定義投影的可能性。為此,您宣告一個介面來包含您想要暴露的屬性

@Projection(name = "summary", types = Order.class)
interface OrderSummary {
	
  LocalDate getOrderedDate();

  CustomerSummary getCustomer();

  @Value("#{@shop.calculateTotal(target)}")
  Money getTotal();
}

此介面可以放在 Order 所在的同一包中(或子包中),並將由 Spring Data REST 自動檢測到。這將導致所有暴露單個訂單或訂單集合的資源在 URI 模板中攜帶一個額外的引數,以指示投影能力。

{ _links : {
    orders : { href : "…/orders{?projection}", templated : true }
  }
}

如果客戶端現在使用 summary 擴充套件模板中的 projection 引數,我們將在伺服器端建立一個代理,該代理將交給 Jackson 進行編組。每個 getter 都將被轉發到實際目標類(在此示例中為 Order)上的屬性查詢。

在上面的示例中,getCustomer() 指的是一個相關實體,在非投影場景下它只會被暴露為一個連結。透過使用投影,我們檢測到方法的返回型別不是 Customer。這反過來會導致建立一個投影代理,以便您可以完全控制暴露的屬性。投影介面當然可以攜帶 Jackson 註解來進一步定製呈現的表示。

對於高階用例,您甚至可以使用 @Value 註解修飾投影方法,以便將 SpEL 表示式的結果返回給編組器。在我們這裡的示例中,我們呼叫一個名為 shop 的 Spring Bean 上的方法,並將代理目標例項傳遞給它來計算訂單總額,這可能考慮了折扣、稅費等。

總結

希望透過這些精選示例,我能夠激發您探索 Dijkstra 發行火車中所包含模組的好奇心。我們現在將透過啟動下一個名為 Evans 的發行火車,繼續我們的使命,以簡化資料訪問層的實現。

獲取 Spring 新聞簡報

訂閱 Spring 新聞簡報保持聯絡

訂閱

搶先一步

VMware 提供培訓和認證,助您加速發展。

瞭解更多

獲取支援

Tanzu Spring 透過一個簡單的訂閱,為 OpenJDK™、Spring 和 Apache Tomcat® 提供支援和二進位制檔案。

瞭解更多

即將到來的活動

檢視 Spring 社群中所有即將到來的活動。

檢視全部