3.1. 核心概念
CrudRepository包含增刪查改基礎(chǔ)功能
public interface CrudRepository<T, ID extends Serializable>
extends Repository<T, ID> {
<S extends T> S save(S entity); //保存給定實(shí)體
Optional<T> findById(ID primaryKey); //根據(jù)給定主鍵id查找實(shí)體
Iterable<T> findAll(); //返回所有實(shí)體
long count(); //返回實(shí)體數(shù)量
void delete(T entity); //刪除給定實(shí)體
boolean existsById(ID primaryKey); //根據(jù)給定實(shí)體判斷一個(gè)實(shí)體是否存在
// … more functionality omitted.
}
PagingAndSortingRepository 繼承自 CrudRepository并提供分頁和排序功能
public interface PagingAndSortingRepository<T, ID extends Serializable>
extends CrudRepository<T, ID> {
Iterable<T> findAll(Sort sort);
Page<T> findAll(Pageable pageable);
}
如果要在以20為一頁的結(jié)果中,獲取第2頁結(jié)果,則如下使用:
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));
除了根據(jù)方法查詢僚祷,還可以查詢數(shù)量和刪除缎脾。
interface UserRepository extends CrudRepository<User, Long> {
//根據(jù) lastname來得到數(shù)量贤重,相當(dāng)于 select count(*) from table t where t.lastname= lastname
long countByLastname(String lastname);
}
interface UserRepository extends CrudRepository<User, Long> {
//根據(jù)lastname來刪除赏殃,返回刪除的數(shù)量,類似于 delete from table t where t.lastname = lastname
long deleteByLastname(String lastname);
//根據(jù)lastname來刪除皇耗,返回被刪除的實(shí)體集合
List<User> removeByLastname(String lastname);
}
3.2 方法查詢
步驟:
- 聲明一個(gè)接口萌丈,繼承 Repository 或它的一個(gè)子類赞哗,你要加上對(duì)應(yīng)的實(shí)體類和對(duì)應(yīng)的主鍵、
interface PersonRepository extends Repository<Person, Long> { … }
- 在接口中定義方法查詢
interface PersonRepository extends Repository<Person, Long> {
List<Person> findByLastname(String lastname);
}
- 創(chuàng)建接口的代理實(shí)例來讓Spring管理辆雾,可以通過 Java方式來配置
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的命名空間需要你自己作對(duì)應(yīng)的修改肪笋。
- 注入 repository 實(shí)例并使用
class SomeClient {
private final PersonRepository repository;
SomeClient(PersonRepository repository) {
this.repository = repository;
}
void doSomething() {
List<Person> persons = repository.findByLastname("Matthews");
}
}
下面的部分將詳細(xì)說明每一步。
3.3 定義repository接口
定義實(shí)體類的repository接口度迂,必須繼承Repository并加上相應(yīng)的實(shí)體類和ID類型藤乙,如果你實(shí)體想獲得 CRUD 方法,那就繼承 CrudRepository
惭墓,而不是 Repository
3.3.1
通常坛梁,你的 repository接口會(huì)繼承 Repository
,CrudRepository
或 PagingAndSortingRepository
腊凶。當(dāng)然了划咐,如果你不想繼承這些Spring Data 內(nèi)置的接口毅人,你可以通過在repository上使用 @RepositoryDefinition注解。繼承 CrudRepository 可以暴露一些操作實(shí)體的方法尖殃,你可以從 CrudRepository 中將你想要的功能復(fù)制進(jìn)你的 repository中。
這將使你能定義自己的頂層 Spring Data Repository划煮。
選擇性地暴露CRUD方法
@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);
}
包配置自動(dòng)
@EnableJpaRepositories(basePackages = "com.acme.repositories.jpa")
@EnableMongoRepositories(basePackages = "com.acme.repositories.mongo")
interface Configuration { }
3.4 查詢方法
repository可以通過方法名(這個(gè)好哇~)或手動(dòng)查詢(@Query注解)來執(zhí)行查詢
3.4.1
interface PersonRepository extends Repository<User, Long> {
List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
// 啟用去重查詢
List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
// 某個(gè)屬性忽略大小寫
List<Person> findByLastnameIgnoreCase(String lastname);
// 所有屬性忽略大小寫
List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
// 查詢中使用 Order By
List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}
- 在表達(dá)式中還可以使用
AND
OR
Between
LessThan
GreaterThan
Like
等送丰,請(qǐng)參閱手冊(cè) - IgnoreCase 單個(gè)屬性忽略大小寫,AllIgnoreCase 所有屬性忽略大小寫
- OrderBy 排序 Asc 或 Desc(降序)
3.4.3 屬性表達(dá)式
如果 Person 中的 Address屬性有 ZipCode屬性弛秋,則可以如下:
List<Person> findByAddressZipCode(ZipCode zipCode);
會(huì)解析為 x.address.zipCode
器躏,算法從右邊開始檢測(cè),會(huì)先將它分為 AddressZip
和 Code
蟹略,但是匹配失敗登失,于是分為 Address
和 ZipCode
。
在第一次匹配成功的話挖炬,便會(huì)忽略下一個(gè)可能的匹配揽浙,假如 Person 還有一個(gè)屬性 為 addressZip
, 而算法檢測(cè)出 addressZip 無 Code 屬性意敛,于是報(bào)錯(cuò)馅巷。
為了避免混淆,可以使用 _
來分開它們草姻。
List<Person> findByAddress_ZipCode(ZipCode zipCode);
雖然這也能解析正確钓猬,但是我們最好還是按Java命名規(guī)范來命名屬性(別使用_,而使用駝峰記法)
3.4.4 特殊的處理參數(shù)
Example 14 在查詢方法中使用 Pageable,Slice和Sort
Page<User> findByLastname(String lastname, Pageable pageable);
Slice<User> findByLastname(String lastname, Pageable pageable);
List<User> findByLastname(String lastname, Sort sort);
List<User> findByLastname(String lastname, Pageable pageable);
Page包今所有元素的數(shù)量和頁撩独,它通過觸發(fā)查詢數(shù)量來實(shí)現(xiàn)敞曹,所以這有可能會(huì)花費(fèi)很長時(shí)間,取決于存儲(chǔ)的數(shù)據(jù)量综膀。而Slice就不一樣了澳迫,它只知道是否有下一個(gè)Slice可用,在大量數(shù)據(jù)集中這很實(shí)用僧须。
3.4.5 限制查詢結(jié)果
查詢方法的結(jié)果可以通過關(guān)鍵字first或top進(jìn)行限制纲刀,可以互換使用。 可以在
first/top 后附加到指定要返回的最大結(jié)果大小担平。 如果該數(shù)字被遺漏示绊,則假設(shè)結(jié)果大小為1。
Example 15暂论,Top和First來限制結(jié)果集大小
User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
Slice<User> findTop3ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
限制表達(dá)式還支持 Distinct
關(guān)鍵字面褐。 此外,對(duì)于將結(jié)果集限制為一個(gè)實(shí)例的查詢取胎,支持將結(jié)果包裝為可選 Optional
展哭。
如果分頁或切片(Slice)應(yīng)用于限制查詢分頁(以及可用頁數(shù)的計(jì)算)湃窍,則在有限的結(jié)果內(nèi)應(yīng)用。
請(qǐng)注意匪傍,通過Sort參數(shù)將結(jié)果與動(dòng)態(tài)排序結(jié)合使用您市,可以表達(dá)'K'最小以及'K'最大元素的查詢方法。
3.4.6 流式查詢結(jié)果
可以通過使用Java 8 Stream <T>作為返回類型來逐步處理查詢方法的結(jié)果役衡。 不是將查詢結(jié)果簡單地包含在Stream數(shù)據(jù)存儲(chǔ)中茵休,而是使用特定的方法來執(zhí)行流。
示例16.使用Java 8 Stream <T>流式傳輸查詢的結(jié)果
@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
Stream<User> readAllByFirstnameNotNull();
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);
流可能包裝底層數(shù)據(jù)存儲(chǔ)特定資源手蝎,因此必須在使用后關(guān)閉榕莺。 您可以使用close()方法或使用Java 7 try-with-resources塊手動(dòng)關(guān)閉Stream。
示例17. 在一個(gè)try-with-resources塊中使用Stream <T>
try (Stream<User> stream = repository.findAllByCustomQueryAndStream()) {
stream.forEach(…);
}
并不是所有的Spring數(shù)據(jù)模塊目前都支持Stream <T>作為返回類型棵介。
3.4.7 異步查詢結(jié)果####
可以使用Spring的異步方法執(zhí)行功能異步執(zhí)行存儲(chǔ)庫(respository)查詢钉鸯。 這意味著方法將在調(diào)用時(shí)立即返回,并且實(shí)際的查詢執(zhí)行將發(fā)生在已提交給Spring TaskExecutor的任務(wù)中邮辽。
//java.util.concurrent.Future作為返回類型
@Async
Future<User> findByFirstname(String firstname);
//返回類型為Java 8的 java.util.concurrent.CompletableFuture
@Async
CompletableFuture<User> findOneByFirstname(String firstname);
//返回類型為org.springframework.util.concurrent.ListenableFuture
@Async
ListenableFuture<User> findOneByLastname(String lastname);
3.5 創(chuàng)建存儲(chǔ)庫(respository)實(shí)例
在本節(jié)中唠雕,您將為定義的存儲(chǔ)庫接口創(chuàng)建實(shí)例和bean定義。 一種方法是使用支持存儲(chǔ)庫機(jī)制的每個(gè)Spring數(shù)據(jù)模塊附帶的Spring命名空間吨述,盡管我們通常建議使用Java-Config風(fēng)格配置及塘。
3.5.1 XML 配置
每個(gè)Spring Data模塊都包含一個(gè)存儲(chǔ)庫元素,它允許您簡單地定義Spring為您掃描的基礎(chǔ)包锐极。
示例18.通過XML啟用Spring數(shù)據(jù)存儲(chǔ)庫
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="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">
<repositories base-package="com.acme.repositories" />
</beans:beans>
在上述示例中笙僚,指示Spring掃描com.acme.repositories及其所有子包,用于擴(kuò)展Repository或其一個(gè)子接口的接口灵再。 對(duì)于發(fā)現(xiàn)的每個(gè)接口肋层,基礎(chǔ)架構(gòu)將注冊(cè)持久性技術(shù)特定的FactoryBean,以創(chuàng)建處理查詢方法調(diào)用的相應(yīng)代理翎迁。 每個(gè)bean都是根據(jù)從接口名稱派生的bean名稱注冊(cè)的栋猖,所以UserRepository的接口將被注冊(cè)在userRepository下。 base-package屬性允許通配符汪榔,以便您可以定義掃描包的模式蒲拉。
使用過濾器
默認(rèn)情況下,框架會(huì)自動(dòng)檢查每個(gè)接口痴腌,并擴(kuò)展位于配置的基礎(chǔ)包下方的持久化特定的Repository子接口雌团,并為其創(chuàng)建一個(gè)bean實(shí)例。 但是士聪,您可能需要對(duì)要為其創(chuàng)建的接口bean實(shí)例進(jìn)行更細(xì)粒度的控制锦援。 為此,您可以在<repositories />中使用<include-filter />和<exclude-filter />元素剥悟。 語義與Spring的上下文命名空間中的元素完全相同灵寺。 有關(guān)詳細(xì)信息曼库,請(qǐng)參閱 Spring參考文檔。
例如略板,要將某些接口從存儲(chǔ)庫實(shí)例化中排除毁枯,可以使用以下配置:
示例19.使用exclude-filter元素
<repositories base-package="com.acme.repositories">
<context:exclude-filter type="regex" expression=".*SomeRepository" />
</repositories>
此示例將以實(shí)例化方式排除以SomeRepository結(jié)尾的所有接口。
3.5.2 Java配置
也可以在Java配置類上使用特定于存儲(chǔ)的@ Enable $ {store}Repositories 注解觸發(fā) repository 架構(gòu)叮称。 有關(guān)Spring容器的基于Java的配置的介紹后众,請(qǐng)參閱參考文檔。1
啟用Spring Data repository 的示例配置看起來像這樣颅拦。
示例20.基于注解的存儲(chǔ)庫配置例子
@Configuration
@EnableJpaRepositories("com.acme.repositories")
class ApplicationConfiguration {
@Bean
EntityManagerFactory entityManagerFactory() {
// …
}
}
該示例使用JPA特定注釋,您可以根據(jù)實(shí)際使用的 repository 模塊進(jìn)行更改教藻。 這同樣適用于EntityManagerFactory bean 的定義距帅。 請(qǐng)參閱涵蓋 存儲(chǔ) 配置的部分。
3.5.3 單獨(dú)使用
您還可以使用Spring容器外的存儲(chǔ)庫基礎(chǔ)結(jié)構(gòu)括堤,例如 在CDI環(huán)境中碌秸。 您在類路徑中仍然需要一些Spring庫,但通城那裕可以通過編程方式設(shè)置存儲(chǔ)庫讥电。 提供存儲(chǔ)庫支持的Spring數(shù)據(jù)模塊提供了一個(gè)持久化特定的RepositoryFactory,可以如下所示來使用RepositoryFactory轧抗。
示例21.存儲(chǔ)庫工廠的獨(dú)立使用
RepositoryFactorySupport factory = … // 實(shí)例化 factory
UserRepository repository = factory.getRepository(UserRepository.class);
3.6 Spring Data Repository 的自定義實(shí)現(xiàn)
在本節(jié)中恩敌,您將了解自定義Repository以及如何形成復(fù)合存儲(chǔ)庫。
當(dāng)查詢方法需要不同的行為或不能通過查詢推導(dǎo)實(shí)現(xiàn)横媚,很有必要地提供自定義實(shí)現(xiàn)纠炮。 Spring Data Repository 可以輕松地允許您提供自定義 Repository 代碼,并將其與通用CRUD抽象和查詢方法功能集成灯蝴。
3.6.1 定制 Repository
為了豐富具有自定義功能的 Repository恢口,您首先要為自定義功能定義一個(gè)片段接口和一個(gè)實(shí)現(xiàn)。 然后讓您的 Repository 接口從該片段接口擴(kuò)展穷躁。
示例22.自定義Repository接口方法
interface CustomizedUserRepository {
void someCustomMethod(User user);
}
示例23.實(shí)現(xiàn)自定義的 Repository 接口方法
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
public void someCustomMethod(User user) {
// Your custom implementation
}
}
該類的最重要的點(diǎn)是與片段接口相比的名稱多了Impl后綴耕肩。
示例24.更改您的 Repository 接口
interface UserRepository extends CrudRepository<User, Long>, CustomizedUserRepository {
// Declare query methods here
}
讓您的 Repository 接口繼承多一個(gè)接口。 這樣做結(jié)合了CRUD和自定義功能问潭,并將其提供給客戶端猿诸。
Spring Data 提供了很多 Repository ,都可以互相組合來增強(qiáng)你的接口狡忙。 基礎(chǔ) Repository 提代了很多的方法两芳,再加上你自定義的方法,那會(huì)使你的自定義接口更強(qiáng)大去枷。 每次向Repository接口添加新接口時(shí)怖辆,都可以增強(qiáng)你的組合是复。 基礎(chǔ) Repository 接口和相關(guān)實(shí)現(xiàn)由相應(yīng)的 Spring Data 模塊提供。(也就是通過繼承Spring Data提供的一些Repository接口竖螃,可以組成強(qiáng)大的Repository接口淑廊,而且Spring Data提代的Repository接口不用你再去實(shí)現(xiàn),并且很多接口里面已有很多好用的方法 )
示例25.實(shí)現(xiàn)類片段
interface HumanRepository {
void someHumanMethod(User user);
}
class HumanRepositoryImpl implements HumanRepository {
public void someHumanMethod(User user) {
// Your custom implementation
}
}
interface EmployeeRepository {
void someEmployeeMethod(User user);
User anotherEmployeeMethod(User user);
}
class ContactRepositoryImpl implements ContactRepository {
public void someContactMethod(User user) {
// Your custom implementation
}
public User anotherContactMethod(User user) {
// Your custom implementation
}
}
示例26.更改你的Repository 接口
interface UserRepository extends CrudRepository<User, Long>, HumanRepository, ContactRepository {
// Declare query methods here
}
存儲(chǔ)庫可以由根據(jù)其聲明的順序?qū)氲亩鄠€(gè)自定義實(shí)現(xiàn)構(gòu)成特咆。 自定義實(shí)現(xiàn)具有比基本實(shí)現(xiàn)和存儲(chǔ)庫方面更高的優(yōu)先級(jí)季惩。 這個(gè)順序允許您覆蓋基本存儲(chǔ)庫和方面方法,并且如果兩個(gè)片段貢獻(xiàn)相同的方法簽名腻格,則可以解析歧義画拾。 存儲(chǔ)庫片段不限于在單個(gè)存儲(chǔ)庫界面中使用。 多個(gè)存儲(chǔ)庫可以使用片段接口在不同的存儲(chǔ)庫之間重用定制菜职。
示例27 覆蓋save(...)
interface CustomizedSave<T> {
<S extends T> S save(S entity);
}
class CustomizedSaveImpl<T> implements CustomizedSave<T> {
public <S extends T> S save(S entity) {
// Your custom implementation
}
}
示例28.定制的存儲(chǔ)庫接口
interface UserRepository extends CrudRepository<User, Long>, CustomizedSave<User> {
}
interface PersonRepository extends CrudRepository<Person, Long>, CustomizedSave<Person> {
}
- 配置
如果使用命名空間配置青抛,repository 基礎(chǔ)框架會(huì)嘗試通過掃描我們找到存儲(chǔ)庫的包下面的類來自動(dòng)檢測(cè)自定義實(shí)現(xiàn)片段。這些類需要遵循將命名空間元素的屬性repository-impl-postfix附加到找到的命名約定 片段接口名稱酬核。 此后綴默認(rèn)為Impl蜜另。
示例29.配置示例
<repositories base-package="com.acme.repository" />
<repositories base-package="com.acme.repository" repository-impl-postfix="FooBar" />
第一個(gè)配置示例將嘗試查找一個(gè)類com.acme.repository.CustomizedUserRepositoryImpl作為自定義存儲(chǔ)庫實(shí)現(xiàn),而第二個(gè)示例將嘗試查找com.acme.repository.CustomizedUserRepositoryFooBar嫡意。
- 解決歧義
如果在不同的包中找到具有匹配類名的多個(gè)實(shí)現(xiàn)举瑰,Spring Data將使用bean名稱來標(biāo)識(shí)哪個(gè)才是正確的。
給定上面介紹的CustomizedUserRepository的以下兩個(gè)自定義實(shí)現(xiàn)蔬螟,第一個(gè)實(shí)現(xiàn)將被選中此迅。 它的bean名稱是customUserRepositoryImpl匹配片段接口(CustomizedUserRepository)加上后綴Impl。
示例30.解決實(shí)現(xiàn)類歧義
package com.acme.impl.one;
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
package com.acme.impl.two;
@Component("specialCustomImpl")
class CustomizedUserRepositoryImpl implements CustomizedUserRepository {
// Your custom implementation
}
如果您在 UserRepository 接口上使用 @Component(“specialCustom”)
注解旧巾,則 bean 名稱加上Impl匹配為 com.acme.impl.two
中的存儲(chǔ)庫實(shí)現(xiàn)定義邮屁,它將被選擇而不是第一個(gè)。(有注解就按注解的名來匹配~)
- 手動(dòng)關(guān)聯(lián)
如果您的自定義實(shí)現(xiàn)僅使用基于注釋的配置和自動(dòng)布線菠齿,那么剛才顯示的方法效果很好佑吝,因?yàn)樗鼘⒈灰暈槿魏纹渌鸖pring bean。 如果您的實(shí)現(xiàn)片段bean需要特殊布線绳匀,則只需聲明bean并按照剛才描述的約定命名該bean芋忿。 然后,底層將通過名稱引用手動(dòng)定義的bean疾棵,而不會(huì)自動(dòng)去創(chuàng)建它戈钢。
實(shí)例31.自定義實(shí)現(xiàn)的手動(dòng)關(guān)聯(lián)
<repositories base-package="com.acme.repository" />
<beans:bean id="userRepositoryImpl" class="…">
<!-- further configuration -->
</beans:bean>
3.6.2 自定義 Base Repository
當(dāng)您要自定義 Base Repository 的方法時(shí),需要自定義所有存儲(chǔ)庫接口是尔,因此所有存儲(chǔ)庫都會(huì)受到影響殉了。 要更改所有存儲(chǔ)庫的行為,您需要?jiǎng)?chuàng)建一個(gè)基于特定實(shí)體類的存儲(chǔ)庫基類的實(shí)現(xiàn)拟枚。 然后薪铜,此類將用作存儲(chǔ)庫代理的自定義基類众弓。
示例32. 自定義 repository 基類
class MyRepositoryImpl<T, ID extends Serializable>
extends SimpleJpaRepository<T, ID> {
private final EntityManager entityManager;
MyRepositoryImpl(JpaEntityInformation entityInformation,
EntityManager entityManager) {
super(entityInformation, entityManager);
//使 EntityManager 可以在新引入的方法中使用。
this.entityManager = entityManager;
}
@Transactional
public <S extends T> S save(S entity) {
// implementation goes here
}
}
該類需要具有專門的存儲(chǔ)庫工廠實(shí)現(xiàn)使用的超類的構(gòu)造函數(shù)隔箍。 一般來說存儲(chǔ)庫基類具有多個(gè)構(gòu)造函數(shù)谓娃,只要覆蓋其中使用了EntityInformation和底層對(duì)象(例如,EntityManager或模板類)的構(gòu)造函數(shù)即可蜒滩。
最后一步是使Spring Data 架構(gòu)識(shí)別到你定制的基類滨达。 在JavaConfig中,這是通過使用 @ Enable ... Repositories
注解的 repositoryBaseClass
屬性實(shí)現(xiàn)的:
示例33.使用JavaConfig配置自定義存儲(chǔ)庫基類
@Configuration
@EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
class ApplicationConfiguration { … }
相應(yīng)的屬性在XML命名空間中可用俯艰。
示例34.使用XML配置自定義存儲(chǔ)庫基類
<repositories base-package="com.acme.repository"
base-class="….MyRepositoryImpl" />
3.7 從 aggregate roots 建立事件
存儲(chǔ)庫管理的實(shí)體是聚合根(aggregate roots)捡遍。 在 Domain 驅(qū)動(dòng)設(shè)計(jì)應(yīng)用程序中,這些聚合根通常會(huì)發(fā)布域事件竹握。 Spring Data 提供了一個(gè)注釋 @DomainEvents
画株,您可以在聚合根的方法上加上它來使該發(fā)布盡可能簡單。
示例35.從聚合根中暴露域事件
class AnAggregateRoot {
@DomainEvents ?
Collection<Object> domainEvents() {
// … return events you want to get published here
}
@AfterDomainEventPublication ?
void callbackMethod() {
// … potentially clean up domain events list
}
}
? 使用@DomainEvents的方法可以返回單個(gè)事件實(shí)例或事件集合涩搓。 它不能采取任何論據(jù)。
?在所有事件發(fā)布之后劈猪,使用 @AfterDomainEventPublication
注釋的方法昧甘。 可用于清理要發(fā)布的事件列表。
每當(dāng)調(diào)用一個(gè) Spring data repository 的save(...)方法時(shí)战得,都會(huì)調(diào)用這些方法充边。
3.8 空安全
存儲(chǔ)庫方法可以提高空安全性,以便在編譯時(shí)處理空值常侦,而不是在運(yùn)行時(shí)碰到 NullPointerException
浇冰。 這使您的應(yīng)用程序通過清理可空性聲明更安全,表達(dá)“值或無價(jià)值”語義聋亡,而無需使用如 Optional
之類的包裝器肘习。
您可以使用Spring Framework的注釋來表達(dá)空安全的存儲(chǔ)庫方法。 它們提供了一個(gè)工具友好的方法坡倔,并在運(yùn)行期間選擇進(jìn)行空檢查:
- @NonNullApi 注解漂佩,在包級(jí)別將非null作為默認(rèn)行為聲明
- @Nullable 注解,其中特定參數(shù)或返回值可以為null罪塔。
兩個(gè)注釋都使用JSR-305元注釋進(jìn)行元注釋(一種休眠的JSR投蝉,但由像IDEA,Eclipse征堪,F(xiàn)indbugs等工具所支持)可以為Java開發(fā)人員提供有用的警告瘩缆。
如果您打算使用自己的元注釋,請(qǐng)確保在類路徑中包含包含JSR-305的@Nonnull注釋的JAR文件佃蚜。
在程序包級(jí)或Kotlin中聲明的空聲明范圍內(nèi)的存儲(chǔ)庫查詢方法的調(diào)用在運(yùn)行時(shí)都會(huì)被驗(yàn)證庸娱。 將null值傳遞給不可為空的查詢方法參數(shù)被異常拒絕着绊。 不產(chǎn)生結(jié)果且不可為空的查詢方法將拋出 EmptyResultDataAccessException 而不是返回null。
示例36.激活包的非空默認(rèn)值
@org.springframework.lang.NonNullApi
package com.example;
示例37.聲明參數(shù)和返回值的可空性
package com.example; ?
interface UserRepository extends Repository<User, String> {
List<User> findByLastname(@Nullable String firstname); ?
@Nullable
User findByFirstnameAndLastname(String firstname, String lastname); ?
}
? @NonNullApi在包級(jí)別上聲明此包中的所有API默認(rèn)為非空涌韩。
? @Nullable允許在特定參數(shù)上使用空值畔柔。 每個(gè)可空參數(shù)都必須注釋。
? 可能返回null的方法用@Nullable注釋臣樱。
如果您使用Kotlin聲明存儲(chǔ)庫接口靶擦,則可以使用Kotlin的空安全來表示可空性。
示例38.聲明Kotlin中參數(shù)和返回值的可空性
interface UserRepository : Repository<User, String> {
fun findByLastname(username: String?): List<User>
fun findByFirstnameAndLastname(firstname: String, lastname: String): User?
}
Kotlin代碼對(duì)字節(jié)碼進(jìn)行編譯雇毫,該字節(jié)碼不能使用方法簽名表示可空性聲明玄捕,而不是對(duì)元數(shù)據(jù)編譯。 確保包含kotlin反射棚放,以便啟用Kotlin的可空性聲明枚粘。
3.9 Spring Data 擴(kuò)展
本節(jié)介紹了一組Spring數(shù)據(jù)擴(kuò)展,可在各種上下文中啟用Spring數(shù)據(jù)使用飘蚯。 目前大部分的集成針對(duì)Spring MVC馍迄。
3.9.1 Querydsl擴(kuò)展
Querydsl是一個(gè)框架,可以通過流暢的API構(gòu)建靜態(tài)類型的類SQL查詢局骤。
有若干個(gè)Spring數(shù)據(jù)模塊通過QueryDslPredicateExecutor提供與Querydsl的集成攀圈。
示例39. QueryDslPredicateExecutor接口
public interface QueryDslPredicateExecutor<T> {
Optional<T> findById(Predicate predicate); ?
Iterable<T> findAll(Predicate predicate); ?
long count(Predicate predicate); ?
boolean exists(Predicate predicate); ?
// … more functionality omitted.
}
? 查找并返回與 predicate 匹配的單個(gè)實(shí)體。
? 查找并返回與 predicate 匹配的所有實(shí)體峦甩。
? 返回與 predicate 匹配的實(shí)體數(shù)赘来。
? 如果與謂詞匹配的實(shí)體存在,則返回凯傲。
要使用Querydsl支持犬辰,只需在您的存儲(chǔ)庫接口上擴(kuò)展QueryDslPredicateExecutor。
示例40.在存儲(chǔ)庫上進(jìn)行Querydsl集成
interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {
}
以上使用Querydsl Predicate可以編寫類型安全的查詢冰单。
Predicate predicate = user.firstname.equalsIgnoreCase("dave")
.and(user.lastname.startsWithIgnoreCase("mathews"));
userRepository.findAll(predicate);