Spring Data Fowler 有什麼新功能?

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

Spring Data Fowler 釋出列車的 GA(通用可用性)版本標誌著 6 個月開發的終點線。是時候讓您瞭解此版本的內容並簡要概述各項功能了。Fowler 釋出列車的主要主題是效能改進和增強的 Java 8 支援,這些主要體現在 Spring Data JPA 和 MongoDB 模組中,但許多其他模組也看到了顯著的改進。

升級到 Spring Data Fowler 釋出列車的最簡單方法是使用 Spring Boot 並將 spring-data-releasetrain.version 屬性配置為 Fowler-RELEASE。如果您尚未使用 Spring Boot,請將 Spring Data BOM 新增到您的 Maven POM 的 <dependencyManagement /> 部分。

總體主題

Java 8 Stream 在 Repository 方法中的支援

Java 8 的一個重要新特性是 Stream API,它允許 Java 開發者定義一個操作管道,用於對物件流進行操作,但只有最終的操作才會真正觸發 Stream 中元素的消費。

在資料訪問的上下文中,將查詢執行結果以 Stream 形式提供是一個非常有用的用例,因為它可以防止查詢方法的呼叫者阻塞,直到所有專案都被讀取。更不用說這裡更高效的記憶體使用。

透過 Fowler 版本,我們引入了對 Java 8 Stream 作為 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 支援

為了方便地在領域物件中持久化 無時區 JSR-310 型別(JDK 8 中新引入的日期/時間 API),我們在 MongoDB 和 JPA 模組中添加了針對相關型別的轉換器。對於尚未能升級到 Java 8 的開發者,我們為 ThreeTen Backport 專案 添加了一組類似的轉換器,這樣即使您仍然使用 Java 7,也可以在程式碼中開始使用這些型別。將來切換到 Java 8 只需簡單地更改包名即可。

在 MongoDB 模組中,相應的 Converter 實現會自動可用。對於 JPA,您只需將 Jsr310JpaConvertersThreeTenBackPortJpaConverters 註冊到您的永續性提供程式。如果您在 Spring 中使用 LocalContainerEntityManagerFactoryBean 設定 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 示例倉庫 中找到一個完整的示例。請注意,由於轉換器只是將 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。這些型別既可以用在您的領域型別中,也可以用作查詢引數。

@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 在執行時為領域物件建立工廠類來解決這個瓶頸。這個工廠類直接呼叫領域類的建構函式,這比透過反射這樣做要快得多。如果您對這些巧妙的細節感興趣,這裡 是為我們實現這一功能的類。

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 節點時,該成員將自動從新的基於叢集的配置服務(託管在 locator 中)獲取其配置。

雖然 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 Schema

Spring Data REST 的 Fowler 版本還提供了改進的 JSON Schema 支援。預設情況下,ALPS 文件中為領域型別公開的表示描述符會指向 Schema。在 Starbucks 示例 中,您可以看到連結的呈現方式如下

curl http://…/alps/stores

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

點選該連結將顯示商店的 JSON Schema 文件

{
  "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": "http://json-schema.org/draft-04/schema#"
}

請注意我們如何從領域型別及其 Jackson 對映中推匯出 Schema 的基本特徵。必需屬性可以透過使用 @JsonProperty(required = true) 來確定,日期/時間型別被正確發現和宣傳。您可以在 RepositoryRestConfiguration.metadataConfiguration() 上註冊自定義的 JSON Schema 格式或模式

@Configuration
static class SampleConfiguration extends RepositoryRestMvcConfiguration {

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

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

Solr

文件分數改進

Fowler 版本增加了對 realtime-get 的支援,允許從索引讀取未提交的更改。SolrTemplate 上提供了 getById 方法。另一個值得注意的增加是 @Score 註解,其靈感來自於 Spring Data MongoDB 中可用的 @TextScore。該註解允許檢索文件分數,並將隱式新增必要的引數,這樣在檢索文件查詢匹配分數時就不再需要顯式新增 @Query(fields={"*", "score"} 了。

其他

提升 Projection

Spring Data REST 在 Evans 釋出列車中釋出了一項稱為 projections 的功能。在 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 來支援建立的 projection 例項。底層建立了一個配備方法攔截器的 JDK 代理,該攔截器(如果代理由 Map 支援)會將對訪問器(accessor)的呼叫委託給 Map 中的屬性查詢。使用 @Value 註解的方法將對其註解的 SpEL 表示式進行求值。如果您在 SpelAwareProjectionFactory 上配置了 BeanFactory,您甚至可以從這些表示式中引用 Spring bean,從而觸發更復雜的計算。

如果支援查詢沒有返回可賦值給宣告的返回型別的值,則會使用標準的 ConversionService 進行簡單轉換,然後進行遞迴 projection 步驟。

有關如何在 Spring MVC 控制器中使用 projections 的示例,請參閱 StackOverflow 上的此回答

Spring MVC 中的 Projections

現在,Spring MVC 控制器實現可以使用 projection 機制,僅透過介面建立表單支援物件。在您的 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 釋出的所有新功能的皮毛。您可能想探索 釋出列車維基 尋找更多亮點,並依次檢視指向工單和相關提交的連結,因為它們通常包含能夠很好地展示各項功能的測試用例。

此外,前面提到的 Spring Data 示例倉庫 有很多可供玩耍和探索的東西。

獲取 Spring 新聞通訊

訂閱 Spring 新聞通訊,保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲取支援

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

瞭解更多

即將舉辦的活動

檢視 Spring 社群所有即將舉辦的活動。

檢視全部