1、spring-data-commons項目
spring-data-commons項目是所有spring-data項目的核心,我們來看一下該項目下的repository包中的接口和注解。
2、Repository<T,ID>接口和@RepositoryDefinition注解
當中最重要的就是Repository接口了。它是做數據庫操作的最底層的抽象接口、最頂級的父類,打開Repository接口看其源碼,發現里面其實什么方法都沒有,僅僅起到一個標識作用。捕獲要管理的域類型和域類的id類型。用途是保存類型信息,並能夠在類路徑掃描期間發現繼承該接口的接口,幫助我們創建代理類。
@Indexed public interface Repository<T, ID> { }
@Indexed 我們發現在Repository接口上有一個@Indexed 注解,是Spring5提供的注解,用於提升應用啟動性能。這個注解單獨存在不起作用,要想使其生效的話,要添加spring-context-indexer依賴。在編譯時會將@CompoentScan掃描指定package中要生成的bean寫在METE-INF/spring.components文件中,當項目啟動時,就會讀取這個文件,不會再掃描指定的package了,從而提升性能。
我們只要繼承Repository接口,並根據它的規則來命名接口方法,就可以進行數據庫操作。(與繼承Repository接口相等價的就是,在接口上添加@RepositoryDefinition注解)
示例:
/**
* 書籍持久層
* @author caofanqi
* 使用@RepositoryDefinition注解與繼承Repository具有相同的效果
*/
//@RepositoryDefinition(domainClass = Book.class,idClass = Long.class)
public interface BookRepository extends Repository<Book,Long>{
/**
* 根據書名查找書籍
* @param bookName 書籍名稱
* @return 該書籍名稱的書列表
*/
List<Book> findBooksByBookNameContains(String bookName);
}
@Transactional
@Rollback(false) @SpringBootTest class BookRepositoryTest { @Resource private BookRepository bookRepository; @Test void findBooksByBookNameContains() { System.out.println("bookRepository : " + bookRepository.getClass().getName()); List<Book> books = bookRepository.findBooksByBookNameContains("Java"); System.out.println(books); } }
這樣就會根據我們傳入的參數去like查詢符合條件的書籍,生成的sql語句如下:
3、CrudRepository<T,ID>接口與注解@NoRepositoryBean
CrudRepository是Repository接口的子接口,提供了一套通用的CRUD方法。接口上的@NoRepositoryBean注解的意思是,不讓Spring生成該類的代理類。
@NoRepositoryBean //不讓Spring為該類生成代理類,僅僅提供一套通用的CRUD方法。 public interface CrudRepository<T, ID> extends Repository<T, ID> { /** * 保存方法*/ <S extends T> S save(S entity); /** * 保存*/ <S extends T> Iterable<S> saveAll(Iterable<S> entities); /** * 根據id進行查詢*/ Optional<T> findById(ID id); /** * 判斷給定id的數據是否存在*/ boolean existsById(ID id); /** * 查詢全部,數據量很大的時候,謹慎使用*/ Iterable<T> findAll(); /** * 根據給定ids查詢符合條件的數據 */ Iterable<T> findAllById(Iterable<ID> ids); /** * 統計條數*/ long count(); /** * 根據id刪除*/ void deleteById(ID id); /** * 刪除給定實體*/ void delete(T entity); /** * 刪除給定實體*/ void deleteAll(Iterable<? extends T> entities); /** * 刪除全部 */ void deleteAll(); }
我們要想使用這些方法,只需自己的Repository繼承該接口即可。
4、PagingAndSortingRepository<T,ID>接口
是CrudRepository的子接口,也不會生成代理,只是提供分頁和排序的方法。
@NoRepositoryBean public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> { /** * 根據sort去所有對象排序的集合*/ Iterable<T> findAll(Sort sort); /** * 根據pageable進行分頁,pageable中可以包含sort*/ Page<T> findAll(Pageable pageable); }
分頁相關對象:
Sort查詢的排序選項,至少提供一個屬性列表來進行排序,不能是null或空,默認是升序ASC;
可以通過Sort.by 來構建Sort對象:
public static Sort by(String... properties) 根據給定的屬性列表進行升序排序;
public static Sort by(Direction direction, String... properties) 指定屬性和排序方向,Direction.ASC(升序)、Direction.DESC(降序);
public static Sort by(Order... orders)/public static Sort by(List<Order> orders) 根據給定的一組order進行排序。
可以通過Sort.and()方法將多個sort組合再一起,通過Sort.ascending()/Sort.descending指定排序方向。
還可以通過Sort.sort(Class<T> type) 來構造類型安全的排序對象:
public static <T> TypedSort<T> sort(Class<T> type) 根據指定class類型構造該類型的typeSort排序對象;
通過TypedSort.by 方法來構建排序字段,通過ascending()/descending指定排序方向,使用and()方法進行連接。
Order,實現一個排序對,提供方向和屬性,為sort提供輸入。也就是說,可以針對每一個屬性設置不同的升序或降序。
可以通過一下方式來構建Order對象:
public static Order by(String property) ,指定屬性返回order對象,默認使用升序。
public static Order asc(String property),返回指定屬性升序的order對象。
public static Order desc(String property),返回指定屬性降序的order對象。
Pageable分頁信息的抽象接口,實現類是PageRequest;
可以通過一下方式來構建Pageable對象:
public static PageRequest of(int page, int size),創建一個未排序的PageRequest,page從0開始;
public static PageRequest of(int page, int size, Direction direction, String... properties),創建一個根據給定方向和屬性排序的分頁對象。
public static PageRequest of(int page, int size, Sort sort),創建一個根據sort進行排序的分頁對象。
Page,封裝分頁結果信息,可以通過如下方法獲取分頁信息。
page.getContent() ,分頁查詢結果列表;
page.getNumberOfElements(),當前分頁結果列表中的元素個數;
page.getTotalElements(),當前條件下總條數;
page.getTotalPages(),總頁數;
page.getNumber(),我們自己傳的page;
page.getSize(),我們自己傳入的size。
代碼示例:
@Test
void testPagingAndSortingRepository(){
// Sort.Order id = Sort.Order.by("id");
// Sort.Order bookName = Sort.Order.desc("bookName");
// Sort sort = Sort.by(id,bookName);
//等價於上面三句代碼
// Sort sort = Sort.by("id").ascending().and(Sort.by("bookName").descending());
//使用類型安全的排序
Sort.TypedSort<Book> bookTypedSort = Sort.sort(Book.class);
Sort sort = bookTypedSort.by(Book::getId).ascending()
.and(bookTypedSort.by(Book::getBookName).descending());
Pageable pageable = PageRequest.of(2,2, sort);
Page<Book> page = bookRepository.findAll(pageable);
System.out.println("分頁查詢結果列表:" + page.getContent());
System.out.println("當前分頁結果列表中的元素個數:" + page.getNumberOfElements());
System.out.println("當前條件下總條數:" + page.getTotalElements());
System.out.println("總頁數:" +page.getTotalPages());
System.out.println("我們自己傳的page:" +page.getNumber());
System.out.println("我們自己傳入的size:" +page.getSize());
}
5、QueryByExampleExecutor<T>接口
該接口位於spring-data-commons項目的repository.query包中,允許通過實例來進行查詢,可以通過該接口來進行簡單的動態查詢。使用的話,自己的repository繼承QueryByExampleExecutor並指定域類型,就可以使用它提供的功能了。
public interface QueryByExampleExecutor<T> { /** * 根據Example查找一個對象*/ <S extends T> Optional<S> findOne(Example<S> example); /** * 根據Example查找一批對象*/ <S extends T> Iterable<S> findAll(Example<S> example); /** * 根據Example查找一批對象,並排序*/ <S extends T> Iterable<S> findAll(Example<S> example, Sort sort); /** * 根據Example查找一批對象,並分頁排序*/ <S extends T> Page<S> findAll(Example<S> example, Pageable pageable); /** * 根據Example查找,返回符合條件的對象個數*/ <S extends T> long count(Example<S> example); /** * 判斷符合給定Example的對象是否存在*/ <S extends T> boolean exists(Example<S> example); }
ExampleMatcher源碼分析:
package org.springframework.data.domain; import lombok.AccessLevel; import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import lombok.experimental.FieldDefaults; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** * 用於示例查詢(QBE)的屬性路徑匹配規范 */ public interface ExampleMatcher { /** * 創建一個新的匹配器,默認情況下,probe中所有的非空屬性都匹配。即所有的屬性條件用and連接。 */ static ExampleMatcher matching() { return matchingAll(); } /** * 創建一個新的匹配器,probe中所有的非空屬性匹配一個即可。即所有的屬性條件用or連接。 */ static ExampleMatcher matchingAny() { return new TypedExampleMatcher().withMode(MatchMode.ANY); } /** *創建一個新的匹配器,probe中所有的非空屬性都匹配。即所有的屬性條件用and連接。 */ static ExampleMatcher matchingAll() { return new TypedExampleMatcher().withMode(MatchMode.ALL); } /** * 返回一個新的匹配器,忽略給定路徑的匹配。屬性傳入這個方法,生成的條件中就不會包含該屬性。一般用於基本數據類型。 */ ExampleMatcher withIgnorePaths(String... ignoredPaths); /** * 返回一個新的匹配器,設置默認的字符串的匹配規則。默認是StringMatcher.DEFAULT(=) 。 * */ ExampleMatcher withStringMatcher(StringMatcher defaultStringMatcher); /** * 返回一個新的匹配器,忽略大小寫匹配(還要看數據庫是否支持大小寫區分)。 */ default ExampleMatcher withIgnoreCase() { return withIgnoreCase(true); } /** * 返回一個新的匹配器,設置是否忽略大小寫匹配(還要看數據庫是否支持大小寫區分)。 */ ExampleMatcher withIgnoreCase(boolean defaultIgnoreCase); /** * 返回一個新的匹配器,設置指定屬性的匹配方式(使用lambda方式)。 */ default ExampleMatcher withMatcher(String propertyPath, MatcherConfigurer<GenericPropertyMatcher> matcherConfigurer) { Assert.hasText(propertyPath, "PropertyPath must not be empty!"); Assert.notNull(matcherConfigurer, "MatcherConfigurer must not be empty!"); GenericPropertyMatcher genericPropertyMatcher = new GenericPropertyMatcher(); matcherConfigurer.configureMatcher(genericPropertyMatcher); return withMatcher(propertyPath, genericPropertyMatcher); } /** * 返回一個新的匹配器,設置指定屬性的匹配方式(原始方式)。 */ ExampleMatcher withMatcher(String propertyPath, GenericPropertyMatcher genericPropertyMatcher); /** * 屬性轉換器 */ ExampleMatcher withTransformer(String propertyPath, PropertyValueTransformer propertyValueTransformer); /** * 返回一個新的匹配器,指定屬性忽略大小寫。 */ ExampleMatcher withIgnoreCase(String... propertyPaths); /** * 返回一個新的匹配器,將probe中的null屬性也作為過濾條件。如:bookName is null。 * */ default ExampleMatcher withIncludeNullValues() { return withNullHandler(NullHandler.INCLUDE); } /** * 返回一個新的匹配器,將probe中的null屬性忽略,不作為過濾條件。 */ default ExampleMatcher withIgnoreNullValues() { return withNullHandler(NullHandler.IGNORE); } /** * 返回一個新的匹配器,設置null值處理器。 */ ExampleMatcher withNullHandler(NullHandler nullHandler); /** * 獲得null處理器。 */ NullHandler getNullHandler(); /** * 獲取默認的字符串匹配器。 */ StringMatcher getDefaultStringMatcher(); /** * 如果忽略字符串大小寫,返回true。 */ boolean isIgnoreCaseEnabled(); /** * 判斷是否是忽略屬性。 */ default boolean isIgnoredPath(String path) { return getIgnoredPaths().contains(path); } /** * 獲取忽略屬性集合。 */ Set<String> getIgnoredPaths(); /** * 屬性特定查詢方式。 */ PropertySpecifiers getPropertySpecifiers(); /** * 是否是全匹配。 */ default boolean isAllMatching() { return getMatchMode().equals(MatchMode.ALL); } /** * 是否是任意匹配。 */ default boolean isAnyMatching() { return getMatchMode().equals(MatchMode.ANY); } /** * 獲取匹配方式。 */ MatchMode getMatchMode(); /** * null處理器枚舉。 */ enum NullHandler { INCLUDE, IGNORE } /** * 回調配置匹配器。 */ interface MatcherConfigurer<T> { void configureMatcher(T matcher); } /** * 通用屬性匹配。 */ @EqualsAndHashCode class GenericPropertyMatcher { @Nullable StringMatcher stringMatcher = null; @Nullable Boolean ignoreCase = null; PropertyValueTransformer valueTransformer = NoOpPropertyValueTransformer.INSTANCE; public GenericPropertyMatcher() {} /** * 通過字符串匹配器,是否忽略大小寫構建GenericPropertyMatcher。 */ public static GenericPropertyMatcher of(StringMatcher stringMatcher, boolean ignoreCase) { return new GenericPropertyMatcher().stringMatcher(stringMatcher).ignoreCase(ignoreCase); } /** *通過字符串匹配器構建GenericPropertyMatcher。 */ public static GenericPropertyMatcher of(StringMatcher stringMatcher) { return new GenericPropertyMatcher().stringMatcher(stringMatcher); } /** * 設置忽略大小寫。 */ public GenericPropertyMatcher ignoreCase() { this.ignoreCase = true; return this; } /** * 設置是否忽略大小寫。 */ public GenericPropertyMatcher ignoreCase(boolean ignoreCase) { this.ignoreCase = ignoreCase; return this; } /** * 設置區分大小寫。 */ public GenericPropertyMatcher caseSensitive() { this.ignoreCase = false; return this; } /** * 包含給定屬性值。 */ public GenericPropertyMatcher contains() { this.stringMatcher = StringMatcher.CONTAINING; return this; } /** * 以給定屬性值結尾。 */ public GenericPropertyMatcher endsWith() { this.stringMatcher = StringMatcher.ENDING; return this; } /** * 以給定屬性值開頭。 */ public GenericPropertyMatcher startsWith() { this.stringMatcher = StringMatcher.STARTING; return this; } /** * 精確匹配。 */ public GenericPropertyMatcher exact() { this.stringMatcher = StringMatcher.EXACT; return this; } /** * 默認規則。 */ public GenericPropertyMatcher storeDefaultMatching() { this.stringMatcher = StringMatcher.DEFAULT; return this; } /** * 正則匹配。 */ public GenericPropertyMatcher regex() { this.stringMatcher = StringMatcher.REGEX; return this; } /** * 給定string匹配器。 */ public GenericPropertyMatcher stringMatcher(StringMatcher stringMatcher) { Assert.notNull(stringMatcher, "StringMatcher must not be null!"); this.stringMatcher = stringMatcher; return this; } /** * 設置屬性轉換器 */ public GenericPropertyMatcher transform(PropertyValueTransformer propertyValueTransformer) { Assert.notNull(propertyValueTransformer, "PropertyValueTransformer must not be null!"); this.valueTransformer = propertyValueTransformer; return this; } } /** * 用於創建GenericPropertyMatcher。 */ class GenericPropertyMatchers { /** * 忽略大小寫的。 */ public static GenericPropertyMatcher ignoreCase() { return new GenericPropertyMatcher().ignoreCase(); } /** * 不忽略大小寫的。 */ public static GenericPropertyMatcher caseSensitive() { return new GenericPropertyMatcher().caseSensitive(); } /** * 包含。 */ public static GenericPropertyMatcher contains() { return new GenericPropertyMatcher().contains(); } /** * 以結尾。 */ public static GenericPropertyMatcher endsWith() { return new GenericPropertyMatcher().endsWith(); } /** * 以開始。 */ public static GenericPropertyMatcher startsWith() { return new GenericPropertyMatcher().startsWith(); } /** * 精確匹配。 */ public static GenericPropertyMatcher exact() { return new GenericPropertyMatcher().exact(); } /** * 默認方式。 */ public static GenericPropertyMatcher storeDefaultMatching() { return new GenericPropertyMatcher().storeDefaultMatching(); } /** * 正則。 */ public static GenericPropertyMatcher regex() { return new GenericPropertyMatcher().regex(); } } /** * 字符串匹配模式。 */ enum StringMatcher { /** * 默認,效果同EXACT。 */ DEFAULT, /** * 精確,相等。 */ EXACT, /** * 開頭匹配。 */ STARTING, /** * 結尾匹配。 */ ENDING, /** * 包含,模糊匹配。 */ CONTAINING, /** * 正則匹配。 */ REGEX; } /** * 屬性轉換器,一般不需要設置。 */ interface PropertyValueTransformer extends Function<Optional<Object>, Optional<Object>> {} /** */ enum NoOpPropertyValueTransformer implements ExampleMatcher.PropertyValueTransformer { INSTANCE; @Override @SuppressWarnings("null") public Optional<Object> apply(Optional<Object> source) { return source; } } /** * 屬性特定查詢方式 */ @FieldDefaults(makeFinal = true, level = AccessLevel.PRIVATE) @RequiredArgsConstructor(access = AccessLevel.PRIVATE) @EqualsAndHashCode class PropertySpecifier { String path; @Nullable StringMatcher stringMatcher; @Nullable Boolean ignoreCase; PropertyValueTransformer valueTransformer; PropertySpecifier(String path) { Assert.hasText(path, "Path must not be null/empty!"); this.path = path; this.stringMatcher = null; this.ignoreCase = null; this.valueTransformer = NoOpPropertyValueTransformer.INSTANCE; } public PropertySpecifier withStringMatcher(StringMatcher stringMatcher) { Assert.notNull(stringMatcher, "StringMatcher must not be null!"); return new PropertySpecifier(this.path, stringMatcher, this.ignoreCase, this.valueTransformer); } public PropertySpecifier withIgnoreCase(boolean ignoreCase) { return new PropertySpecifier(this.path, this.stringMatcher, ignoreCase, this.valueTransformer); } public PropertySpecifier withValueTransformer(PropertyValueTransformer valueTransformer) { Assert.notNull(valueTransformer, "PropertyValueTransformer must not be null!"); return new PropertySpecifier(this.path, this.stringMatcher, this.ignoreCase, valueTransformer); } public String getPath() { return path; } @Nullable public StringMatcher getStringMatcher() { return stringMatcher; } @Nullable public Boolean getIgnoreCase() { return ignoreCase; } public PropertyValueTransformer getPropertyValueTransformer() { return valueTransformer == null ? NoOpPropertyValueTransformer.INSTANCE : valueTransformer; } public Optional<Object> transformValue(Optional<Object> source) { return getPropertyValueTransformer().apply(source); } } /** * 特定屬性查詢方式集合 */ @EqualsAndHashCode class PropertySpecifiers { private final Map<String, PropertySpecifier> propertySpecifiers = new LinkedHashMap<>(); PropertySpecifiers() {} PropertySpecifiers(PropertySpecifiers propertySpecifiers) { this.propertySpecifiers.putAll(propertySpecifiers.propertySpecifiers); } public void add(PropertySpecifier specifier) { Assert.notNull(specifier, "PropertySpecifier must not be null!"); propertySpecifiers.put(specifier.getPath(), specifier); } public boolean hasSpecifierForPath(String path) { return propertySpecifiers.containsKey(path); } public PropertySpecifier getForPath(String path) { return propertySpecifiers.get(path); } public boolean hasValues() { return !propertySpecifiers.isEmpty(); } public Collection<PropertySpecifier> getSpecifiers() { return propertySpecifiers.values(); } } /** * 匹配方式。 */ enum MatchMode { ALL, ANY; } }
示例代碼:
@Test void testQueryByExampleExecutor(){ Book book = Book.builder().bookName("java").publishDate(LocalDate.of(2019,11,11)).id(1L).build(); ExampleMatcher matcher = ExampleMatcher.matching() .withIgnorePaths("id") //忽略id屬性,不管id有沒有值,都不作為查詢條件。 .withIgnoreNullValues() //忽略屬性為null的,不作為查詢條件。 .withMatcher("bookName",m -> m.startsWith().ignoreCase()) //設置bookName屬性,前包含,忽略大小寫。 .withTransformer("publishDate",value -> Optional.of(LocalDate.of(2019,11,12))); //轉換屬性值 Example<Book> example = Example.of(book,matcher); List<Book> books = bookRepository.findAll(example); }
生成的sql語句:
Spring-Data-Jpa官網的字符串匹配舉例
QueryByExampleExecutor最佳實踐:
首先要判斷是否需要我們自己構建匹配器,如果默認匹配器,可以完成,我們就不需要創建。
判斷null值是否要作為條件,一般都是忽略的,如果null值作為條件,將不想作為條件的null屬性添加到忽略列表。
基本類型是有默認值的,如果不作為條件,要加入到忽略列表。
不同的字符串屬性,如果需要不同的匹配方式,進行單獨設置。
不是特別復雜的動態查詢,使用QBE,還是很方便的。
源碼地址:https://github.com/caofanqi/study-spring-data-jpa