學習Spring-Data-Jpa(六)---spring-data-commons中的repository


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM