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

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

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

在 Fowler 版本中,我們引入了對 Java 8 Stream 作為儲存庫中查詢方法返回型別的支援。在 MongoDB 和 JPA 模組中,您現在可以這樣宣告查詢方法

interface CustomerRepository extends Repository<Customer, Long> {

  Stream<Customer> findByLastname(String lastname);
}

呼叫此方法將執行支援儲存庫方法的查詢,並在第一個結果可用時立即返回。為了在 JPA 中實現這一點,我們使用永續性提供程式特定的 API,因為 JPA 本身只提供以 List 形式獲取查詢結果的方法。儲存庫客戶端現在可以在 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。如果您使用 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 Examples 儲存庫。請注意,由於轉換器只是將 JSR-310 型別轉換為舊版 Date 例項,因此 只支援無時區 型別(例如 LocalDateTime 等)。

MongoDB

3.0 伺服器和驅動程式支援

Spring Data Fowler 支援最新最強大的 MongoDB 3.0 伺服器版本。雖然該版本已經可以透過 MongoDB Java 驅動程式 2.13.0 版本使用,但我們也確保 Spring Data MongoDB 將與即將推出的 Java 驅動程式 3.0 版本無縫協作。因此,開發人員可以自由選擇他們要使用的版本,或者何時升級到新驅動程式。有關驅動程式和伺服器版本之間相容性的通用資訊,請務必查閱 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.");

伺服器端指令碼支援將在後續版本中得到增強,我們將新增返回型別轉換、用於從儲存庫方法呼叫過程的註解以及對 $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 對等節點時,該成員將自動從新的基於叢集的配置服務(託管在定位器中)獲取其配置。

雖然 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 文件中公開的表示描述符指向,該文件用於域型別。在 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 對映中推匯出模式的基本特徵。所需屬性可以透過使用 @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 模式文件中以 format 設定為 email 出現。

Solr

文件評分改進

Fowler 版本增加了對即時獲取的支援,允許從索引中讀取未提交的更改。SolrTemplate 上提供了 getById 方法。另外值得注意的是添加了 @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 版本的所有新功能。您可能需要瀏覽 發行版系列 wiki 以尋找更多精彩內容,並依次遍歷指向票據和相關提交的連結,因為它們包含通常很好地演示各個功能的測試用例。

此外,前面提到的 Spring Data 示例倉庫 也包含大量可供您嘗試和探索的內容。

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

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

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

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

檢視所有