Spring Data(一)概念和倉庫的定義
Spring Data的主要任務是為數據訪問提供一個相似的、一致的、基于Spring的編程模型,同時又保留著下面各個數據存儲的特征。它使得使用數據訪問技術非常的簡單,包括關系型和非關系型數據庫、map-reduce框架、云數據服務等。這是一個傘項目,它包含許多指定數據庫的子項目。這個項目是許多公司和開發者一起開發而成的,他們是這項令人興奮的技術的幕后作者。
特征:
- 強大的倉庫和定制的實體映射抽象
- 從倉庫方法名字衍生出的動態查詢
- 提供了基礎屬性實現的基礎類
- 支持透明的審計(創建、最終修改)
- 整合指定倉庫代碼的可能性
- 通過JavaConfig和指定的xml命名空間非常容易的進行Spring整合
- 用Spring MVC controller進行先進的整合
- 交叉存儲持久化的實驗性的支持
主要的模塊
- Spring Data Commons 每一個Spring Data項目的核心基礎概念
- Spring Data Gemfire 提供了從Spring應用的簡單的配置和訪問Gemfire
- Spring Data JPA 提供了非常簡單的基于JPA倉庫的實現
- Spring Data JDBC 基于JDBC的倉庫
- Spring Data KeyValue 基于Map的倉庫和非常簡單的創建鍵-值存儲的模塊
- Spring Data LDAP 為Spring LDAP提供倉庫支持
- Spring Data MongoDB 為MongoDB提供基于Spring的文檔實體和存儲
- Spring Data REST 作為超媒體RESTful資源輸出Spring Data存儲
- Spring Data Redis 提供簡單的配置和從Spring應用到redis的訪問
- Spring Data for Apache Cassandra ——Apache Cassandra的Spring Data模塊
- Spring Data for Apache Solr ——Apache Solr 的Spring Data模塊
社區模塊
- Spring Data Aerospike
- Spring Data ArangoDB
- Spring Data Couchbase
- Spring Data Azure DocumentDB
- Spring Data DynamoDB
- Spring Data Elasticsearch
- Spring Data Hazelcast
- Spring Data Jest
- Spring Data Neo4j
- Spring Data Vault
相關模塊
- Spring Data JDBC Extensions 在Spring框架內提供了JDBC的擴展
- Spring for Apache Hadoop 提供統一的配置模型、為HDFS, MapReduce, Pig,和 Hive提供API,簡化了Hadoop。
- Spring Content 使內容和你的Spring Data實體發生聯系,存儲在不同的存儲介質中,File-system, S3, Database 或者MongoDB
- Spring Boot 啟動器
如果你正在使用SpringBoot,你將繼承每一個項目的預定義版本。你可以配置spring-data-releasetrain.version 插入你想要的版本。
Spring Data顯著減小了樣板化代碼的數量,為各個持久化存儲實現了數據訪問層。
Spring Data Commons
Spring Data Commons項目是所有Spring Data子項目的基礎,它為許多關系型和非關系型數據庫提供開發解決方案。由于各個Spring Data模塊的起始日期不同,他們中的大多數都有著主要版本和次要版本,找到適合的版本的最簡單的方法是依賴Spring Data 版本串BOM,它是我們用最合適的版本定義的。在Maven項目中,你可以在<dependencyManagement />中聲明依賴,如下:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-releasetrain</artifactId>
<version>${release-train}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
如果你正在使用Spring Boot時,它已經給你選擇了一個最近的Spring Data版本。
Spring Data倉庫抽象化的中心接口是Repository,它使用域的類和ID的類型作為泛型參數。這個接口作為標記接口的角色,捕獲你要使用的類型,并幫助你發現繼承此類型的接口,CrudRepository為管理的實體類提供了復雜的CRUD功能。
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
<S extends T> S save(S entity);
Optional<T> findById(ID primaryKey);
Iterable<T> findAll();
long count();
void delete(T entity);
boolean existsById(ID primaryKey);
// … more functionality omitted.
}
我們也提供了特殊技術的持久化抽象,例如:JpaRepository、MongoRepository等。這些接口都繼承了CrudRepository,并且輸出了各自持久化技術的能力。
PagingAndSortingRepository接口繼承了CrudRepository,并添加了一些額外的分頁功能:
public interface PagingAndSortingRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
訪問一個實體第二頁的例子如下:
PagingAndSortingRepository<User, Long> repository = // … get access to a bean Page<User>
users = repository.findAll(new PageRequest(1, 20));
通過這些方法,還衍生出了刪除和統計的功能,如下:
interface UserRepository extends CrudRepository<User, Long> {
long countByLastname(String lastname);
}
interface UserRepository extends CrudRepository<User, Long> {
long deleteByLastname(String lastname);
List<User> removeByLastname(String lastname);
}
查詢方法:
標準的CRUD方法在底層的數據存儲上都有對應的查詢,使用SpringData,聲明這些查詢分為4步:
1、聲明接口繼承Repository或者Repository的子類,并標注實體類型和ID類型
interface PersonRepository extends Repository<Person, Long> { … }
2、在接口中聲明方法:
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}
3、設置Spring生成接口代理
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
@EnableJpaRepositories
class Config {}
或者使用xml的方式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<jpa:repositories base-package="com.acme.repositories"/>
</beans>
上面的例子中,使用了Jpa的命名空間,如果你使用其他倉庫的抽象,換成其他倉庫對應的命名空間即可,例如:你正在使用MongoDB,可以換成MongoDB對應的命名空間。
值得注意的是,JavaConfig并沒有默認配置注解類的路徑作為包路徑,在xml中,掃描包路徑的參數配置在base-package參數中,對應的javaConfig將使用注解@Enable-*。
4、獲取注入的Repository實例,并使用它
class SomeClient {
private final PersonRepository repository;
SomeClient(PersonRepository repository) {
this.repository = repository;
}
void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}
接下來我們將詳細介紹每一步。
定義倉庫接口
第一步,你定義一個指定實體類的倉庫接口,這個接口必須繼承Repository并且定義了實體類型和ID類型,如果你想輸出Crud方法,你要繼承CrudRepository,不要繼承Repository。
典型的,你的倉庫接口繼承Repository, CrudRepository 或者 PagingAndSortingRepository。要不然,如果你不想繼承Spring Data的接口,你也可以使用@RepositoryDefinition注解你自己的倉庫接口。繼承CrudRepository將輸出一套完成的方法集來操作你的實體,如果你想選擇一些方法輸出,最簡單的方法是從CrudRepository中復制你想要輸出的方法到你自己的倉庫中。這些將允許你在Spring Data倉庫方法中的最頂端定義你自己的抽象。
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
Optional<T> findById(ID id);
<S extends T> S save(S entity);
}
interface UserRepository extends MyBaseRepository<User, Long> {
User findByEmailAddress(EmailAddress emailAddress);
}
在第一步中,你定義了一個公用的基礎接口,并且輸出了findById和save方法。這些方法將路由到你選擇的存儲的基礎倉庫實現中。這個例子中,如果你定義了SimpleJpaRepository,因為它匹配了CrudRepository方法中的特性,所以UserRepository可以保存users,通過id查找users或者通過email查找users。值得注意的是,中間倉庫接口使用@NoRepositoryBean注解,確保你給所有的倉庫接口添加注解,Spring Data在運行期將不會創建實例。
倉庫方法中的null處理
作為Spring Data2.0,CRUD方法返回一個使用java8的Optional的獨立的聚合實例,標明值的潛在缺少。除此之外,Spring Data支持在查詢方法上返回其他的封裝類型。或者,查詢方法可以選擇根本不使用封裝類型。缺少查詢接口將通過返回null標明。倉庫方法返回集合、封裝類型和流來保護不返回null。
空值注解
你可以使用Spring的空值注解來表達倉庫的空值約束。它提供了在運行期的空值檢查。
- @NonNullApi 在包級別使用,標明參數的默認行為,返回的結果不接受和生成null值。
- @NonNull 在參數或者返回值上使用,他們不允許為null。
- @Nullabe 在參數或者返回值上使用,他們允許為null。
Spring注解是用JSR305的元注解,JSR 305允許工具方IDEA、Eclipse等在通用的方法中提供空安全性的支持,不必提供Spring注解的硬編碼支持。為了提供運行期的空值約束檢查,你需要使非空值活動在包級別中,在package-info.java中使用@NonNullApi。
@org.springframework.lang.NonNullApi
package com.acme;
一旦非空定義在這個地方,倉庫的查詢方法在運行期將得到一個空約束的驗證。如果查詢結果違反了約束,將會拋出異常,例如,在一些條件下方法返回了null,但是已經聲明了非空。如果你想選擇性的使用空結果,選擇@Nullable注解,使用前面提到的封裝類型將繼續按照希望的那樣工作,例如空結果將會轉入到那個值中。
使用不同空約束的例子:
package com.acme;
import org.springframework.lang.Nullable;
interface UserRepository extends Repository<User, Long> {
User getByEmailAddress(EmailAddress emailAddress);
@Nullable User findByEmailAddress(@Nullable EmailAddress emailAdress);
Optional<User> findOptionalByEmailAddress(EmailAddress emailAddress);
}
在前面的例子中,我們已經在package-info.java中,定義了非空的行為。
第一個方法,查詢的執行沒有產生結果,將拋出EmptyResultDataAccessException異常,emailAddress如果傳入空,將拋出IllegalArgumentException。
第二個方法如果沒有查詢結果將返回null,傳入的參數也接受null。
第三個方法如果沒有查詢結果將返回Optional.empty(),如果傳入空參數將拋出IllegalArgumentException。
多Spring Data模塊中使用Repository
在你的項目中使用唯一的Spring Data模塊是非常簡單的,定義范圍內的所有倉庫接口都綁定到Spring Data模塊。有時,應用需要使用多個Spring Data模塊。這種情況下,需要倉庫定義區分不同的持久化技術。Spring Data記錄嚴格的倉庫配置模型,因為它在類路徑下檢測到多個倉庫配置元素。嚴格的配置需要在倉庫或者實體類上的細節決定Spring Data綁定哪個倉庫定義。
如果倉庫定義繼承了指定的模塊倉庫,它是一個有效的特殊的Spring Data模塊的申請者。
如果實體類中使用了指定模塊的注解,它是一個有效的特殊的Spring Data模塊的申請者。Spring Data接受第三方的注解(如:jpa)或者自己提供的注解(如:mongodb)。
倉庫定義使用指定模塊接口:
interface MyRepository extends JpaRepository<User, Long> { }
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> { … }
interface UserRepository extends MyBaseRepository<User, Long> { … }
MyBaseRepository和UserRepository都繼承了JpaRepository,所以他們都是有效的Jpa模塊的申請者。
倉庫定義使用通用的接口:
interface AmbiguousRepository extends Repository<User, Long> { … }
@NoRepositoryBean
interface MyBaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> { … }
interface AmbiguousUserRepository extends MyBaseRepository<User, Long> { … }
AmbiguousRepository和AmbiguousUserRepository只繼承了Repository和CrudRepository,這種情況在使用了唯一的Spring Data模塊時是可行的,在多模塊的情況,它是不能區分使用哪個具體模塊的。
倉庫定義使用實體類注解:
interface PersonRepository extends Repository<Person, Long> { … }
@Entity
class Person { … }
interface UserRepository extends Repository<User, Long> { … }
@Document
class User { … }
PersonRepository相關的Person使用了Jpa的@Entity注解,所以,它的倉庫屬于Jpa。UserRepository使用注解了@Document的User,所以它屬于MongoDB。
倉庫定義使用混合注解實體類:
interface JpaPersonRepository extends Repository<Person, Long> { … }
interface MongoDBPersonRepository extends Repository<Person, Long> { … }
@Entity
@Document
class Person { … }
這個例子中,實體類即使用了Jpa注解,又使用了MongoDB注解。它定義了兩個倉庫:JpaPersonRepository和MongoDBPersonRepository。一個給Jpa使用,另一個給MongoDB使用。這種導致未定義的行為使Spring Data不再有能力區分倉庫的類型。
倉庫類型詳情和指定實體類注解,用來嚴格區分倉庫指向哪一個Spring Data模塊。在一個實體類中使用多個實體技術注解可以服用實體類,但是Spring Data將不能指定倉庫綁定哪一個Spring Data模塊。區分倉庫的最后一個方法是規范倉庫基礎包的路徑?;A包定義了掃描的開始點,倉庫接口都在適合的包中。
基礎包的注解驅動配置:
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }
定義查詢方法
倉庫代理有兩種方式導出指定倉庫的查詢。它可以從名字直接導出查詢,或者使用手工定義的查詢。可用的選項取決于實際的存儲??墒?,它有一個策略決定哪一個查詢被生成。
查詢查找策略
下面的策略是可用的倉庫解決查詢的基礎。你可以在命名空間配置策略,通過xml文件中的query-lookup-strategy參數或者Enable*的注解中的queryLookupStrategy參數。一些策略在特殊的存儲中不被支持。
CREATE嘗試從方法名中構造指定倉庫的查詢方法,大概的方法是從方法名中移除一個給定的眾所周知的前綴,然后解析剩余的部分。
USE_DECLARED_QUERY嘗試找到聲明的查詢,如果找不到,將拋出異常。查詢通過注解或其他方法的聲明定義。查看指定存儲的文檔找到可用的選項。如果倉庫不能找到存儲聲明的查詢,它將失敗。
CREATE_IF_NOT_FOUND結合了CREATE和USE_DECLARED_QUERY。它首先查找聲明的查詢,如果不能找到,它將生成一個基于命名的查詢。這是默認的查詢策略。它可以通過方法名字快速的生成查詢,也可以通過查詢的聲明生成查詢。
?這一章我們先介紹到這里,具體的方法命名規則將在下一篇中介紹。