Spring Data Fowler 有什麼新功能?

工程 | Thomas Darimont | 2015 年 3 月 26 日 | ...

Spring Data release train Fowler 的 GA 版本標誌著 6 個月開發的終點線。 現在是時候向您介紹此版本的內容以及各個功能的簡要概述。 Fowler release train 的主要主題是效能改進和增強的 Java 8 支援,主要體現在 Spring Data JPA 和 MongoDB 模組中,但許多其他模組也看到了顯著的改進。

升級到 Spring Data Fowler release train 最簡單的方法是使用 Spring Boot 並將 spring-data-releasetrain.version 屬性配置為 Fowler-RELEASE。 如果您還沒有使用 Spring Boot,請將 Spring Data BOM 添加到您的 Maven POM 的 <dependencyManagement /> 區段中。

一般主題

repository 方法中的 Java 8 streams

Java 8 的一個很棒的新功能是 Stream API,它允許 Java 開發人員定義要在物件流上執行的操作管道,但只有最終操作實際上會觸發從 Stream 消費元素。

在資料存取的情況下,將查詢執行的結果作為 Stream 提供是一種極其有用的使用案例,因為它可以防止查詢方法的呼叫者在讀取所有項目之前被阻止。 更不用說這裡更有效的記憶體使用。

在 Fowler 版本中,我們引入了對 Java 8 Streams 的支援,作為 repository 中 finder 方法的傳回型別。 在 MongoDB 和 JPA 模組中,您現在可以開始宣告像這樣的 finder 方法

interface CustomerRepository extends Repository<Customer, Long> {

  Stream<Customer> findByLastname(String lastname);
}

呼叫此方法將執行支援 repository 方法的查詢,並在第一個結果可用時立即傳回。 為了使用 JPA 實現這一點,我們使用了特定於持久性提供者的 API,因為 JPA 本身僅提供將查詢結果作為 List 取得的方法。 Repository 客戶端現在可以繼續並在 try-with-resources 區塊中使用方法呼叫的結果。 這將確保保持開啟以能夠遍歷流的資源最終將被關閉

try (Stream<Customer> customers = repository.findByLastname("Matthews")) {
  customers.filter(…).map(…).collect(…);
}

JSR-310 和 ThreeTen Backport 支援

為了輕鬆地從 domain 物件中持久化非時區 JSR-310 類型(JDK 8 中新引入的日期/時間 API),我們為 MongoDB 和 JPA 模組中的相關類型新增了轉換器。 對於無法升級到 Java 8 的開發人員,我們為 ThreeTen Backport 專案 新增了一組類似的轉換器,以便您即使仍在 Java 7 上也可以開始在程式碼中使用這些類型。 將來切換到 Java 8 將只是套件名稱中的簡單切換。

在 MongoDB 模組中,相應的 Converter 實現會自動提供。 對於 JPA,您可以簡單地使用持久性提供者註冊 Jsr310JpaConvertersThreeTenBackPortJpaConverters。 如果您使用 LocalContainerEntityManagerFactoryBean 在 Spring 中設定您的 JPA 環境,只需將 org.springframework.data.jpa.convert.threeten….threetenbp 新增到要掃描的套件。 使用 Spring Boot,只需將上述類別新增到 @EntityScan 宣告中即可

@EntityScan(
  basePackageClasses = { Application.class, Jsr310JpaConverters.class }
)
@SpringBootApplication
class Application { … }

此設定將確保掃描您的應用程式套件和 Spring Data JPA 的 JSR-310 轉換器,並將其交給持久性提供者。 在我們的 Spring Data 範例 repository 中找到一個完整的範例。 請注意,由於轉換器只是將 JSR-310 類型轉換為舊版 Date 實例,僅支援非時區(例如 LocalDateTime 等)。

MongoDB

3.0 伺服器和驅動程式支援

Spring Data Fowler 支援最新最好的 MongoDB 3.0 伺服器世代。 雖然可以使用 2.13.0 版本的 MongoDB Java 驅動程式使用該世代,但我們也確保 Spring Data MongoDB 可以與即將推出的 3.0 版本的 Java 驅動程式完美配合。 因此,開發人員可以自由選擇他們要使用的版本,或者他們何時想要升級到新的驅動程式。 有關驅動程式和伺服器版本之間相容性的一般資訊,請務必查看 MongoDB 文件 請注意,後續開發將明確關注伺服器和驅動程式的 3.0 系列。

通常,我們鼓勵大家在 JavaConfig 中使用 MongoClient 而不是 Mongo,或者使用新引入的 XML 元素 <mongo:mongo-client /><mongo:client-options />。 有關更多資訊,請參閱 參考文件

GeoJSON 支援

MongoDB 引入 GeoJSON 作為處理地理結構的格式已經有一段時間了。 這些資料結構在類似地球的球體上運行,因此不能與 2D 索引一起使用。 也就是說,使用起來非常簡單,因為我們提供了專用的類型來支援 GeoJSON。 這些可用於您的 domain 類型以及查詢參數。

@Document
class Store {

  @Id String id;

  /**
   * The location is stored in GeoJSON format:
   * { "type" : "Point", "coordinates" : [ x, y ] }
   */
  GeoJsonPoint location;
}

interface StoreRepository extends CrudRepository<Store, String> {

  List<Store> findByLocationWithin(Polygon polygon);
}

repo.findByLocationWithin(
  new GeoJsonPolygon(
    new Point(-73.992514, 40.758934),
    new Point(-73.961138, 40.760348),
    new Point(-73.991658, 40.730006),
    new Point(-73.992514, 40.758934)));

這會建立以下要在 MongoDB 中執行的查詢

{
  "location": {
    "$geoWithin": {
      "$geometry": {
        "type": "Polygon",
        "coordinates": [[
           [-73.992514,40.758934],
           [-73.961138,40.760348],
           [-73.991658,40.730006],
           [-73.992514,40.758934]
        ]]
      }
    }
  }
}

請注意,StoreRepository.findByLocationWithin(…) 仍然採用 Polygon。 將 GeoJsonPolygonfindByLocationWithin(…) 搭配使用將使用 $geometry 運算子以及 GeoJSON 表示法建立查詢。 有關使用方法和限制的更多詳細資訊,請參閱 MongoDB 手冊

執行 MongoDB 儲存的腳本

MongoDB 允許通過直接發送原始來源腳本或呼叫先前儲存的腳本來在伺服器上執行 JavaScript 函數。 我們通過新引入的 ScriptOperations 介面公開此功能,該介面可以從 MongoOperations 獲得。

ScriptOperations ops = mongoOperations.scriptOps();
ExecutableMongoScript script = new ExecutableMongoScript("function(x) { return x; }");
Object r1 = ops.execute(script, "Direct function execution.")

ops.register(new NamedMongoScript("echo", script));
Object r2 = ops.call("echo", "Call stored function.");

伺服器端腳本支援將在後續版本中得到增強,我們將添加傳回類型轉換、用於從 repository 方法呼叫過程的註解以及對 $where 運算子的支援。

物件到儲存轉換的效能改進

物件到儲存映射子系統在效能改進方面經歷了重大的改進。我們分析了 Commons 和儲存模組,在這裡和那裡引入了一些快取,並且與 Evans 發布系列相比,實際上可以獲得令人印象深刻的每秒操作次數的增加(儘管大多數改進也被移植回了 Evans 的服務版本)。

Performance improvements in Spring Data Fowler

正如您所看到的,我們將讀取存取的每秒操作次數增加了一倍以上,並且在寫入操作中也接近這個數字。

當從資料儲存讀取大量物件時,會花費大量的時間透過反射建立物件實例。在 Fowler 發布系列中,我們引入了一個新的預設 EntityInstantiator,它透過在執行時使用 ASM 為網域物件建立一個 factory 類別來解決這個瓶頸。這個 factory 類別直接呼叫網域類別的建構子,這比透過反射執行要快得多。如果您對這些細節感興趣,這裡是為我們完成這項工作的類別。

Redis

HyperLogLog

Redis HyperLogLog 指令 提供了一種有效的解決方案來計算唯一事物,而無需記住已經遇到的元素。例如,這適用於計算按 IP 的唯一頁面訪問量。

HyperLogLogOperations hll = redisTemplate.opsForHyperLogLog();

hll.add(today(), "8.8.8.8", "8.8.4.4");
hll.size(today()); // Unique page visits today = 2

hll.add(today(), "198.153.192.40", "8.8.8.8");
hll.size(today()); // Unique page visits today = 3
hll.size(today(), yesterday()); // Unique page visits today and yesterday

Gemfire

到目前為止,GemFire 模組最顯著的變化是完全支援 GemFire 8。GemFire 8 自 7.0.2 以來引入了 多項新更改,包括新的 基於叢集的配置服務

啟用該服務後,開發人員可以在 Gfsh 中記錄他們的操作和類似綱要的更改,因為他們執行操作,例如新增區域、建立索引、配置磁碟儲存等。當開發人員在叢集中啟動一個新的 GemFire 對等點時,該成員將自動從託管在定位器中的新基於叢集的配置服務中獲取其配置。

雖然 Spring Data GemFire 的 基於 XML 命名空間的配置 仍然是一個流行的選擇,尤其是在高度迭代的短回饋週期中進行開發時,Spring Data GemFire 增加了對新的 基於叢集的配置 的支援,其行為類似於 Spring Data Gemfire 對 GemFire 的 原生 `cache.xml 格式的支援。

要在 Spring 配置的 GemFire 節點中啟用基於叢集的配置,開發人員只需要在 <gfe:cache /> 元素上設定 use-cluster-configuration 屬性,就像這樣

<gfe:cache id="gemfireCache" use-cluster-configuration="true" … />

Spring Data GemFire 將首先請求並應用叢集範圍的配置,然後再應用 XML 命名空間特定的配置。您可以將 XML 配置元資料視為擴充叢集配置服務傳送的叢集配置。

有關 GemFire 新的叢集配置服務的更多資訊,請參閱 GemFire 使用者指南SGF-226 以獲取更多詳細資訊。

Spring Data REST

Spring Data REST 在 Fowler 發布系列中也進行了廣泛的改進。最值得注意的改進之一是檢測和使用更多實體元資料來填充回應標頭。例如,透過 @Version 註解支援樂觀鎖定的儲存現在將取得用作 ETag 標頭的實體版本,以便客戶端可以利用它們來觸發條件式 GET 請求。

相當相關的是,使用 Spring Data 稽核支援的實體將自動將其最後修改日期傳播到項目資源回應的 LastModified 標頭中

class Customer {

  @Version Long version;
  @LastModifiedDate LocalDate lastModifiedDate;
}
curl -v http://…/customers/1

Etag: 1
Last-Modified: Tue, 24 Mar 2015 12:34:56 GMT

JSON 綱要

Spring Data REST 的 Fowler 發布系列還提供了改進的 JSON 綱要支援。預設情況下,綱要指向網域類型公開的 ALPS 文件中的表示描述符。在 Starbucks 範例 中,您可以看到連結的呈現方式如下

curl http://…/alps/stores

{
  "version": "1.0",
  "descriptors": [ {
    "id": "store-representation",
    "href": "https://127.0.0.1:8080/stores/schema",
    "descriptors": [ … ]
  }],
  …
}

追蹤連結將顯示儲存的 JSON 綱要文件

{
  "title": "example.stores.Store",
  "properties": {
    "address": {
      "$ref": "#/descriptors/address"
    },
    "name": {
      "type": "string"
    }
  },
  "descriptors": {
    "address": {
      "type": "object",
      "properties": {
        "zip": {
          "type": "string"
        },
        "city": {
          "type": "string"
        },
        "street": {
          "type": "string"
        },
        "location": {
          "$ref": "#/descriptors/point"
        }
      }
    },
    "point": {
      "type": "object",
      "properties": {
        "x": {
          "type": "number"
        },
        "y": {
          "type": "number"
        }
      }
    }
  },
  "type": "object",
  "$schema": "https://json-schema.dev.org.tw/draft-04/schema#"
}

請注意,我們如何從網域類型及其 Jackson 映射中推導出綱要的基本特徵。可以使用 @JsonProperty(required = true) 來確定必要的屬性,正確地發現和宣告日期/時間類型。您可以在 RepositoryRestConfiguration.metadataConfiguration() 上註冊自定義 JSON 綱要格式或模式

@Configuration
static class SampleConfiguration extends RepositoryRestMvcConfiguration {

  @Override
  protected void configureRepositoryRestConfiguration(
    RepositoryRestConfiguration config) {
    config.metadataConfiguration().
      registerJsonSchemaFormat(JsonSchemaFormat.EMAIL, EmailAddress.class);
  }
}

假設 EmailAddress 是一個值物件,您已經調整 Jackson 將其呈現為純 String,此配置將導致所有類型為 EmailAddress 的屬性在 JSON 綱要文件中顯示時,將 format 設定為 email

Solr

文件分數改進

Fowler 發布系列增加了對即時取得的支援,允許從索引中讀取未提交的更改。getByIdSolrTemplate 上可用。同樣值得注意的是增加了 @Score,它的靈感來自 Spring Data MongoDB 的 @TextScore。該註解允許檢索文件分數,並將隱式新增必要的參數,因此在檢索文件查詢符合分數時,不再需要顯式新增 @Query(fields={"*", "score"}

其他

提升投射

Spring Data REST 在 Evans 發布系列中發布了一個名為投射的功能。在 Fowler 中,我們將支援它的基礎架構移至 Spring Data Commons 並進行了一些調整,以便其他專案可以在沒有進一步依賴項的情況下使用它。該功能的非常核心是一個 ProjectionFactory,它允許您為由某些其他物件(例如 Map)支援的介面建立物件實例。

interface Customer {

  String getFirstname();

  String getLastname();

  @Value("#{target.firstname + ' ' + target.lastname}")
  String getFullName();
}

現在可以使用 ProjectionFactory 將此介面轉換為物件

Map<String, Object> map = new HashMap<>();
map.put("firstname", "Dave");
map.put("lastname", "Matthews");

ProjectionFactory factory = new SpelAwareProjectionFactory();
Customer customer = factory.createProjection(Customer.class, map)

assertThat(customer.getFirstname(), is("Dave"));
assertThat(customer.getLastname(), is("Matthews"));
assertThat(customer.getFullName(), is("Dave Matthews"));

正如您所看到的,我們選擇了 Map 來支援建立的投射實例。在幕後,創建了一個配備方法攔截器的 JDK 代理,如果 Map 支持代理,則將對存取子的呼叫委託給 Map 中的屬性查找。使用 @Value 註解的方法將評估註解的 SpEL 表達式。如果您在 SpelAwareProjectionFactory 上配置了 BeanFactory,您甚至可以從這些表達式中引用 Spring bean,從而觸發更複雜的計算。

如果後端查找未返回可分配給宣告的回傳類型的數值,則標準的 ConversionService 用於簡單的轉換,然後是遞歸的投射步驟。

有關如何在 Spring MVC 控製器中使用投射的範例,請參閱 StackOverflow 上的這個答案

Spring MVC 中的投影

現在 Spring MVC 控制器實作可以使用投影機制,僅使用介面來建立表單備份物件。在您的 Spring 設定中使用 @EnableSpringDataWebSupport(在 Boot 中自動啟用),將會解析一個 ProxyingHandlerMethodArgumentResolver,它會自動為介面建立一個代理實例,並將相應的請求參數綁定到它。

interface Form {

  @NotBlank String getName();
  @NotBlank String getText();
}

@Controller
@RequestMapping(value = "/guestbook")
class GuestbookController {

  @RequestMapping(method = RequestMethod.GET)
  String guestbook(Form form, Model model) { … }

  @RequestMapping(method = RequestMethod.POST)
  String guestbook(@Valid Form form, Errors errors, Model model) { … }
}

請參閱如何在使用接受 GET 請求的方法中使用該介面,以向即將呈現的檢視提供一個空的表單備份物件。接收 POST 請求的方法使用 Form 來表明它想要取得綁定到代理實例的表單資料,並且套用驗證。

總結

儘管這篇文章很長,但我們幾乎沒有觸及 Spring Data Fowler 版本的所有新功能。您可能需要瀏覽 release train wiki 以尋找更多寶石,並依次遍歷連結到工單和相關提交的連結,因為它們包含的測試案例通常可以很好地演示個別功能。

此外,已經提到的 Spring Data 範例儲存庫 有很多東西可以玩耍和探索。

獲取 Spring 電子報

與 Spring 電子報保持聯繫

訂閱

搶先一步

VMware 提供培訓和認證,以加速您的進度。

了解更多

獲取支援

Tanzu Spring 在一個簡單的訂閱中提供對 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔案。

了解更多

即將舉行的活動

查看 Spring 社群中所有即將舉行的活動。

查看全部