spring-data-jpa 中文文檔(1)

spring-data-jpa 中文文檔(1)

  • 簡介
  • 爲了讓Spring Data的版本保持一致,可以使用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 Boot 會選擇一個較新的版本,但是假使你想升級到一個更新的版本,你可以只配置spring-data-releasetrain.version屬性爲下列屬性值中的一個.

    BUILD-SNAPSHOT - current snapshots
    M1, M2 etc. - milestones
    RC1, RC2 etc. - release candidates
    RELEASE - GA release
    SR1, SR2 etc. - service releases

  • 開始使用Spring Data Repositories.

    • 核心概念 核心接口是Repository.它以domaindomain的id類型作爲參數進行管理 . CrudRepository 接口提供了CRUD功能.
  •     public interface Repository<T, ID extends Serializable> {
    
        }
    
        public interface CrudRepository<T, ID extends Serializable>
        extends Repository<T, ID> {
    
        <S extends T> S save(S entity); 
    
        T findOne(ID primaryKey);       
    
        Iterable<T> findAll();          
    
        Long count();                   
    
        void delete(T entity);          
    
        boolean exists(ID primaryKey);  
    
        // … more functionality omitted.
    }

    Spring Data 也提供持久化的具體抽象接口 比如說JpaRepositoryMongoRepository 這些接口擴展CrudRepository 並暴露出底層的持久化技術,但是CrudRepository等類似的比較通用的持久性與具體技術無關(沒有直接的實現)的接口並不包含在內.其只提供要實現的方法.

    接着CrudRepository有一個PagingAndSortingRepository的抽象接口.其有一些分頁相關的功能.

    public interface PagingAndSortingRepository<T, ID extends Serializable>
      extends CrudRepository<T, ID> {
    
      Iterable<T> findAll(Sort sort);
    
      Page<T> findAll(Pageable pageable);
    }

    獲取一個每頁20條第二頁的User 信息,你可以只是簡單的如此做:

    PagingAndSortingRepository<User, Long> repository = // … get access to a bean
    Page<User> users = repository.findAll(new PageRequest(1, 20));

    除查詢的方法,查詢數量和刪除的語句也可以用這樣的方式實現.

    public interface UserRepository extends CrudRepository<User, Long> {
    
      Long countByLastname(String lastname);
    }
    public interface UserRepository extends CrudRepository<User, Long> {
    
      Long deleteByLastname(String lastname);
    
      List<User> removeByLastname(String lastname);
    
    }
    • 查詢方法(Query Methods)

      • 標準的CRUD功能倉庫實現的查詢比較底層.用Spring Data,定義這些查詢變成了四步:

        • 定義一實現了Repository接口或者它的子接口的接口,並且它將會綁定輸入domain類domain類的ID類型.

          interface PersonRepository extends Repository<Person, Long> {
                List<Person> findByLastname(String lastname);
          }
        • 定義查詢的方法

          interface PersonRepository extends Repository<Person, Long> {
            List<Person> findByLastname(String lastname);
          }
        • spring爲這些接口創建代理實例

          import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
          
          @EnableJpaRepositories
          class Config {}
        • 獲得repository實例並且使用

          public class SomeClient {
          
            @Autowired
            private PersonRepository repository;
          
            public void doSomething() {
              List<Person> persons = repository.findByLastname("Matthews");
            }
          }

        接下來詳細解釋每一步

    • 定義repository接口
      第一部中你定義一個特定domain類型的repository接口.這個接口你必須繼承Repository接口並且定義domain類ID類型.如果你想暴露CRUD方法,你可以繼承CrudRepository.

      • 讓Repository定義的更有規則
        通常,你的repository接口會繼承Repository,CrudRepository或者PagingAndSortingRepository.如果你不想繼承Spring Data interfaces 你也可以用@RepositoryDefinition自己定義repository接口.繼承CrudRepository 暴露了完整的管理你的實體的方法,如果你更喜歡自己定義哪些方法需要去暴露,只需要把要暴露的方法從CrudRepository中複製出來就可以了.

        @NoRepositoryBean
        interface MyBaseRepository<T, ID extends Serializable> extends Repository<T, ID> {
        
          T findOne(ID id);
        
          T save(T entity);
        }
        
        interface UserRepository extends MyBaseRepository<User, Long> {
          User findByEmailAddress(EmailAddress emailAddress);
        }

        你需要確保你自己定義的repository接口有@NoRepositoryBean註解.這樣可以保證Spring Data可以實例化它

      • 利用multiple Spring Data modules來使用Repositories
        使用應用程序中的一個獨特的Spring Data Module 讓事情變得很簡單.因此在定義範圍內的所有repository接口都會綁定到 Spring Data Module .有時候,應用程序需要多個Spring Data Module,這種情況下,它需要用持久化技術來區分不同的repository.Spring Data Module進入strict repository mode ,因爲它檢測到在類路徑上有多個資源庫的工廠.strict repository mode要求在repository或者domain的細節來決定一個repository定義的Spring Data module綁定:

        • 如果repository定義 繼承the module-specific repository
        • 如果domain被the module-specific type annotation註解.例如JPA’s @Entity,或者說Spring Data MongoDb/Spring Data Elasticsearch的@Document.

          interface MyRepository extends JpaRepository<User, Long> { }
          
          @NoRepositoryBean
          interface MyBaseRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
            …
          }
          
          interface UserRepository extends MyBaseRepository<User, Long> {
            …
          }

          MyRepository 和UserRepository 繼承了JpaRepository 在他們的類型結構中,這是有效的.

          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在他們的類型結構中,雖然這也是非常好的,multiple modules不能區分哪一個特定的Spring Data是repositories要綁定的.

          interface PersonRepository extends Repository<Person, Long> {
           …
          }
          
          @Entity
          public class Person {
            …
          }
          
          interface UserRepository extends Repository<User, Long> {
           …
          }
          
          @Document
          public class User {
            …
          }

          domain類被第三方(如@Entity或者@Document)註解註解了的.

    • 定義查詢方法

      SpringData通過方法名有兩種方式去解析出用戶的查詢意圖:一種是直接通過方法的命名規則去解析,第二種是通過Query去解析,那麼當同時存在幾種方式時,SpringData怎麼去選擇這兩
      種方式呢?好了,SpringData有一個策略去決定到底使用哪種方式:

      • 查詢策略
        接下來我們將介紹策略的信息,你可以通過配置<repository>query-lookup-strategy屬性來決定。或者通過Java config的Enable${store}Repositories註解的queryLookupStrategy屬性來指定:

        • CREATE 通過解析方法名字來創建查詢。這個策略是刪除方法中固定的前綴,然後再來解析其餘的部分。
        • USE_DECLARED_QUERY 它會根據已經定義好的語句去查詢,如果找不到,則會拋出異常信息。這個語句可以在某個註解或者方法上定義。根據給定的規範來查找可用選項,如果在方法被調用時沒有找到定義的查
          詢,那麼會拋出異常。
        • CREATE_IF_NOT_FOUND 這個策略結合了以上兩個策略。他會優先查詢是否有定義好的查詢語句,如果沒有,就根據方法的名字去構建查詢。這是一個默認策略,如果不特別指定其他策略,那麼這個策略會在項目
          中沿用。
      • 構建查詢
        查詢構造器是內置在SpringData中的,他是非常強大的,這個構造器會從方法名中剔除掉類似find…By, read…By, 或者get…By的前綴,然後開始解析其餘的名字。你可以在方法名中加入更多的表達式,例如你需要Distinct的約束,那麼你可以在方法名中加入Distinct即可。在方法中,第一個By表示着查詢語句的開始,你也可以用And或者Or來關聯多個條件。

        public interface PersonRepository extends Repository<User, Long> {
        List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
        // 需要在語句中使用Distinct 關鍵字,你需要做的是如下
        List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
        List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
        // 如果你需要忽略大小寫,那麼你要用IgnoreCase 關鍵字,你需要做的是如下
        List<Person> findByLastnameIgnoreCase(String lastname);
        // 所有屬性都忽略大小寫呢?AllIgnoreCase 可以幫到您
        List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
        // 同樣的,如果需要排序的話,那你需要:OrderBy
        List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
        List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
        }

        根據方法名解析的查詢結果跟數據庫是相關,但是,還有幾個問題需要注意:
        多個屬性的查詢可以通過連接操作來完成,例如And,Or。當然還有其他的,例如Between,LessThan,GreaterThan,Like。這些操作時跟數據庫相關的,當然你還需要看看相關的數據庫文檔是否支持這些操作。
        你可以使用IngoreCase來忽略被標記的屬性的大小寫,也可以使用AllIgnoreCase來忽略全部的屬性,當然這個也是需要數據庫支持才允許的。
        你可以使用OrderBy來進行排序查詢,排序的方向是Asc跟Desc,如果需要動態排序,請看後面的章節。

      • 屬性表達式
        具體的方法名解析查詢需要怎樣的規則呢?這種方法名查詢只能用在被管理的實體類上,就好像之前的案例。假設一個類Person中有個Address,並且Address還有ZipCode,那麼根據ZipCode來查詢這個Person需要怎麼做呢?

        List<Person> findByAddressZipCode(ZipCode zipCode);

        在上面的例子中,我們用x.address.zipCode去檢索屬性,這種解析算法會在方法名中先找出實體屬性的完整部分(AddressZipCode),檢查這部分是不是實體類的屬性,如果解析成功,則按
        照駝峯式從右到左去解析屬性,如:AddressZipCode將分爲AddressZipCode,在這個時候,我們的屬性解析不出Code屬性,則會在此用同樣的方式切割,分爲AddressZipCode(如果
        第一次分割不能匹配,解析器會向左移動分割點),並繼續解析。
        爲了避免這種解析的問題,你可以用“_”去區分,如下所示:

        List<Person> findByAddress_ZipCode(ZipCode zipCode);
      • 特殊參數處理
        上面的例子已經展示了綁定簡單的參數,那麼除此之外,我們還可以綁定一些指定的參數,如PageableSort來動態的添加分頁、排序查詢。

        Page<User> findByLastname(String lastname, Pageable pageable);
        List<User> findByLastname(String lastname, Sort sort);
        List<User> findByLastname(String lastname, Pageable pageable);

        第一個方法通過傳遞org.springframework.data.domain.Pageable來實現分頁功能,排序也綁定在裏面。如果需要排序功能,那麼需要添加參數org.springframework.data.domain.Sort,如第二行中,返回的對象可以是List,當然也可以是Page類型的。

      • 限制查詢結果
        查詢結果可以通過first或者top來進行限制,first或者top是可以替換的.可以用一個數字追加在top/first後邊以指定返回結果的條數.如果這個數字在first/top左邊,則返回結果大小爲1.

        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);

        表達的限制條件也可以使用Distinct關鍵字

      • 將查詢結果放入到Stream
        查詢結果可以用Java 8 Stream<T> 作爲返回結果類型進行處理.

        @Query("select u from User u")
        Stream<User> findAllByCustomQueryAndStream();
        
        Stream<User> readAllByFirstnameNotNull();
        
        @Query("select u from User u")
        Stream<User> streamAllPaged(Pageable pageable);

        但並不是所有的 Spring Data modules都能正確的支持Stream<T>作爲返回結果類型.

      • 異步查詢結果
        Repository查詢可以通過Spring的異步處理方法 異步執行.這意味着查詢方法會立即返回.真是的查詢會被作爲一個task放入到Spring TaskExecutor中.

        @Async
        Future<User> findByFirstname(String firstname);               
        
        @Async
        CompletableFuture<User> findOneByFirstname(String firstname); 
        
        @Async
        ListenableFuture<User> findOneByLastname(String lastname);    
        Use java.util.concurrent.Future as return type.
        Use a Java 8 java.util.concurrent.CompletableFuture as return type.
        Use a org.springframework.util.concurrent.ListenableFuture as return type.
    • 創建Repository實體
      創建已定義的Repository接口,最簡單的方式就是使用Spring配置文件,當然,需要JPA的命名空間。

      • XML配置
        你可以使用JPA命名空間裏面的repositories去自動檢索路徑下的repositories元素:

        <?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能夠通過base-package檢測出指定路徑下所有繼承Repository或者其子接口的接口(有點繞口)。每找到一個接口的時候,FactoryBean就會創建一個合適的代理去處理以及調用裏面的查詢方法。每個註冊的Bean的名稱都是源於接口名稱,例如:UserRepository將會被註冊爲userRepository。base-package允許使用通配符作爲掃描格式。

        • 使用過濾器
          在默認的設置中,將使用全路徑掃描的方式去檢索接口,當然,你在業務上可能需要更細緻的操作,這時候,你可以在<repositories>中使用<include-filter>或者<exclude-filter>。這樣的話,
          你可以指定掃描的路徑包含或者不包含指定的路徑。

          <repositories base-package="com.acme.repositories">
              <context:exclude-filter type="regex" expression=".*SomeRepository" />
          </repositories>

          這個例子中,我們排除了所有以SomeRepository結尾的接口。

      • JavaConfig
        可以通過在一個JavaConfig 類上用@Enable${store}Repositories註解來觸發.

        @Configuration
        @EnableJpaRepositories("com.acme.repositories")
        class ApplicationConfiguration {
        
          @Bean
          public EntityManagerFactory entityManagerFactory() {
            // …
          }
        }
      • 獨立使用
        你可以不在Spring容器裏面使用repository。但是你還需要Spring的依賴包在你的classpath中,你需要使用RepositoryFactory來實現,代碼如下:

        RepositoryFactorySupport factory = ... // 初始化
        UserRepository repository = factory.getRepository(UserRepository. class);
    • 自定義Repository實現
      我們可以自己實現repository的方法。

      • 在repository中添加自定義方法

        • 自定義接口:

          interface UserRepositoryCustom {
            public void someCustomMethod(User user);
          }
        • 自定義接口的實現類

          class UserRepositoryImpl implements UserRepositoryCustom {
          
            public void someCustomMethod(User user) {
              // Your custom implementation
            }
          }
        • 擴展CRUDRepository

          interface UserRepository extends CrudRepository<User, Long>, UserRepositoryCustom {
            // Declare query methods here
          }

          這樣的話,就能夠在常用的Repository中實現自己的方法。

      • 配置
        在XML的配置裏面,框架會自動搜索base-package裏面的實現類,這些實現類的後綴必須滿足repository-impl-postfix中指定的命名規則,默認的規則是:Impl

        <repositories base- package ="com.acme.repository" />
        <repositories base- package ="com.acme.repository" repository-impl-postfix="FooBar" />

        第一個配置我們將找到com.acme.repository.UserRepositoryImpl,而第二個配置我們將找到com.acme.repository.UserRepositoryFooBar。

      • 人工裝配
        前面的代碼中,我們使用了註釋以及配置去自動裝載。如果你自己定義的實現類需要特殊的裝載,那麼你可以跟普通bean一樣聲明出來就可以了,框架會手工的裝載起來,而不是創建本身。
        <repositories base-package="com.acme.repository"/>
        <beans:bean id="userRepositoryImpl" class="…">
        </beans:bean>
    • 爲所有的repository添加自定義方法
      假如你要爲所有的repository添加一個方法,那麼前面的方法都不可行。你可以這樣做:

      1. 你需要先聲明一箇中間接口,然後讓你的接口來繼承這個中間接口而不是Repository接口,代碼如下:
        中間接口:

        @NoRepositoryBean
        public interface MyRepository<T, ID extends Serializable>
          extends PagingAndSortingRepository<T, ID> {
        
          void sharedCustomMethod(ID id);
        }
      2. 這時候,我們需要創建我們的實現類,這個實現類是基於Repository中的基類的,這個類會作爲Repository代理的自定義類來執行。

        public class MyRepositoryImpl<T, ID extends Serializable>
          extends SimpleJpaRepository<T, ID> implements MyRepository<T, ID> {
        
          private final EntityManager entityManager;
        
          public MyRepositoryImpl(JpaEntityInformation entityInformation,
                                  EntityManager entityManager) {
            super(entityInformation, entityManager);
        
            // Keep the EntityManager around to used from the newly introduced methods.
            this.entityManager = entityManager;
          }
        
          public void sharedCustomMethod(ID id) {
            // implementation goes here
          }
        }
      3. 配置自定義Repository的base class

        1. 用JavaConfig類

          @Configuration
          @EnableJpaRepositories(repositoryBaseClass = MyRepositoryImpl.class)
          class ApplicationConfiguration { … }
        2. 用XML

          <repositories base-package="com.acme.repository" repository-base-class="….MyRepositoryImpl" />
    • Spring Data 擴展
      這部分我們將會把SpringData擴展到其他框架中,目前我們繼承的目標是SpringMVC。

      • Querydsl擴展
        public interface QueryDslPredicateExecutor<T> {
        
            T findOne(Predicate predicate);             
        
            Iterable<T> findAll(Predicate predicate);   
        
            long count(Predicate predicate);            
        
            boolean exists(Predicate predicate);        
        
            // … more functionality omitted.
        }

      Finds and returns a single entity matching the Predicate.
      Finds and returns all entities matching the Predicate.
      Returns the number of entities matching the Predicate.
      Returns if an entity that matches the Predicate exists.

      Predicate簡介

      • 利用Querydsl,在你的Repository接口上繼承QueryDslPredicateExecutor

        interface UserRepository extends CrudRepository<User, Long>, QueryDslPredicateExecutor<User> {}
      • 然後可以用Querydsl Predicate寫類型安全的查詢

        Predicate predicate = user.firstname.equalsIgnoreCase("dave")
        .and(user.lastname.startsWithIgnoreCase("mathews"));
        userRepository.findAll(predicate);
      • Web支持
        SpringData支持很多web功能。當然你的應用也要有SpringMVC的Jar包,有的還需要繼承Spring HATEOAS。
        通常來說,你可以在你的JavaConfig配置類中加入@EnableSpringDataWebSupport即可:

            @Configuration
            @EnableWebMvc
            @EnableSpringDataWebSupport
            class WebConfiguration { }

        這個註解註冊了幾個功能,我們稍後會說,他也能檢測Spring HATEOAS,並且註冊他們。
        如果你用XML配置的話,那麼你可以用下面的配置:
        在xml中配置:

        <bean class="org.springframework.data.web.config.SpringDataWebConfiguration" />
        <!-- If you're using Spring HATEOAS as well register this one *instead* of the former -->
        <bean class="org.springframework.data.web.config.HateoasAwareSpringDataWebConfiguration" />
        • 基本的web支持
          上面的配置註冊了以下的幾個功能:
          1. DomainClassConverter將會讓SpringMVC能從請求參數或者路徑參數中解析出來。
          2. HandlerMethodArgumentResolver 能讓SpringMVC從請求參數中解析出Pageable(分頁)與Sort(排序)。
        • DomainClassConverter
          這個類允許你在SpringMVC控制層的方法中直接使用你的領域類型(Domain types),如下:

          @Controller
          @RequestMapping("/users")
          public class UserController {
          
            @RequestMapping("/{id}")
            public String showUserForm(@PathVariable("id") User user, Model model) {
          
              model.addAttribute("user", user);
              return "userForm";
            }
          }

          正如你所見,上面的方法直接接收了一個User對象,你不需要做任何的搜索操作,這個轉換器自動的設id的值進去對象中,並且最終調用了findOne方法查詢出實體。(注:當前的Repository
          必須實現CrudRepository)

        • HandlerMethodArgumentResolver分頁排序
          這個配置項同時註冊了PageableHandlerMethodArgumentResolver 和 SortHandlerMethodArgumentResolver,使得Pageable跟Sort能作爲控制層的參數使用:

          @Controller
          @RequestMapping("/users")
          public class UserController {
          
            @Autowired UserRepository repository;
          
            @RequestMapping
            public String showUsers(Model model, Pageable pageable) {
          
              model.addAttribute("users", repository.findAll(pageable));
              return "users";
            }
          }

          這個配置會讓SpringMVC傳遞一個Pageable實體參數,下面是默認的參數:

          參數名 說明
          page 你要獲取的頁數
          size 一頁中最大的數據量
          sort 需要被排序的屬性(格式:屬性1,屬性2(ASC/DESC)),默認是ASC,使用多個字段排序,你可以使用sort=first&sort=last,asc

          如果你需要對多個表寫多個分頁或排序,那麼你需要用@Qualifier來區分,請求參數的前綴是${qualifire}_,那麼你的方法可能變成這樣:

          public String showUsers(Model model,
          @Qualifier("foo") Pageable first,
          @Qualifier("bar") Pageable second) { … }

          你需要填寫foo_page和bar_page等。
          默認的Pageable相當於new PageRequest(0,20),你可以用@PageableDefaults註解來放在Pageable上。

        • 超媒體分頁
          Spring HATEOAS有一個PagedResources類,他豐富了Page實體以及一些讓用戶更容易導航到資源的請求方式。Page轉換到PagedResources是由一個實現了Spring HATEOAS
          ResourceAssembler接口的實現類:PagedResourcesAssembler提供轉換的。

          @Controller
          class PersonController {
            @Autowired PersonRepository repository;
            @RequestMapping(value = "/persons", method = RequestMethod.GET)
            HttpEntity<PagedResources<Person>> persons(Pageable pageable,
              PagedResourcesAssembler assembler) {
              Page<Person> persons = repository.findAll(pageable);
              return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
            }
          }

          上面的toResources方法會執行以下的幾個步驟:

          1. Page對象的內容會轉換成爲PagedResources對象。
          2. PagedResources會的到一個PageMetadata的實體附加,包含Page跟PageRequest。
          3. PagedResources會根據狀態得到prev跟next鏈接,這些鏈接指向URI所匹配的方法中。分頁參數會根據PageableHandlerMethodArgumentResolver配置,以讓其在後面的方法中
          4. 解析使用。
            假使我們現在有30個Person實例在數據庫中,你可以通過GET http://localhost:8080/persons 並且 你會得到像下邊這樣的一些反饋:
            { "links" : [ { "rel" : "next",
                      "href" : "http://localhost:8080/persons?page=1&size=20 }
              ],
              "content" : [
                 … // 20 Person instances rendered here
              ],
              "pageMetadata" : {
                "size" : 20,
                "totalElements" : 30,
                "totalPages" : 2,
                "number" : 0
              }
            }
        • Querydsl web支持
          它可以從Request的query string中提取出一些屬性,並轉換成Querydsl樣式.
          這意味着它可以把

          ?firstname=Dave&lastname=Matthews

          這樣的query string 解析成:

          QUser.user.firstname.eq("Dave").and(QUser.user.lastname.eq("Matthews"))
        • 使用QuerydslPredicateArgumentResolver.
          在未來當Querydsl在classpath中被發現時,僅僅使用@EnableSpringDataWebSupport就可以激活
          在方法上增加一個@QuerydslPredicate將會提供一個可以通過QueryDslPredicateExecutor來執行的Predicate
          使用QueryDslPredicateExecutor中的root屬性來確定@QuerydslPredicate的返回值類型

          @Controller
          class UserController {
          
            @Autowired UserRepository repository;
          
            @RequestMapping(value = "/", method = RequestMethod.GET)
            String index(Model model, @QuerydslPredicate(root = User.class) Predicate predicate,    
                    Pageable pageable, @RequestParam MultiValueMap<String, String> parameters) {
          
              model.addAttribute("users", repository.findAll(predicate, pageable));
          
              return "index";
            }
          }

          爲User解析query string來匹配Predicate
          默認的綁定方式如下:

          1. Object on simple properties as eq.
          2. Object on collection like properties as contains.
          3. Collection on simple properties as in.
            這樣的綁定可以通過@QuerydslPredicatebingdings屬性來定製,也可以使用Java 8 的default methods 在Repository接口上追加QuerydslBinderCustomizer
            interface UserRepository extends CrudRepository<User, String>,
                                          QueryDslPredicateExecutor<User>,                
                                          QuerydslBinderCustomizer<QUser> {
              @Override
              default public void customize(QuerydslBindings bindings, QUser user) {
                bindings.bind(user.username).first((path, value) -> path.contains(value))    
                bindings.bind(String.class)
                  .first((StringPath path, String value) -> path.containsIgnoreCase(value)); 
                bindings.excluding(user.password);                                           
              }
            }
        • QueryDslPredicateExecutorPredicate 提供具體的查找方法.

        • 在Repository 接口中QuerydslBinderCustomizer 的定義和@QuerydslPredicate(bindings=…​)的快捷方式將會被自動抓取
        • username 屬性的綁定就是一個簡單的contains綁定.
        • 默認的String屬性綁定是不區分大小寫的匹配contains
        • Exclude the password property from Predicate resolution.
      • Repository填充
        如果你用過Spring JDBC,那麼你肯定很熟悉使用SQL去填寫數據源(DataSource),在這裏,我們可以使用XML或者Json去填寫數據,而不再使用SQL填充。
        假如你有一個data.json的文件,如下:

        [ { "_class" : "com.acme.Person",
         "firstname" : "Dave",
          "lastname" : "Matthews" },
          { "_class" : "com.acme.Person",
         "firstname" : "Carter",
          "lastname" : "Beauford" } ]

        要PersonRepository填充這些數據進去,你需要做如下的聲明:

        <?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:repository="http://www.springframework.org/schema/data/repository"
          xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/data/repository
            http://www.springframework.org/schema/data/repository/spring-repository.xsd">
        <repository:jackson2-populator locations="classpath:data.json" />
        </beans>

        這個聲明使得data.json能夠通過Jackson ObjectMapper被其他地方讀取,反序列化。

      • Legacy Web(傳統web)支持

        • 在SpringMVC中綁定領域類(Domain class)
          你在開發web項目的時候,你經常需要從URL或者請求參數中解析領域類中的ID,你可能是這麼做得:

          @Controller
          @RequestMapping("/users")
          public class UserController {
          
            private final UserRepository userRepository;
          
            @Autowired
            public UserController(UserRepository userRepository) {
              Assert.notNull(repository, "Repository must not be null!");
              this.userRepository = userRepository;
            }
          
            @RequestMapping("/{id}")
            public String showUserForm(@PathVariable("id") Long id, Model model) {
          
              // Do null check for id
              User user = userRepository.findOne(id);
              // Do null check for user
          
              model.addAttribute("user", user);
              return "user";
            }
          }

          首先你要注入一個UserRepository ,然後通過findOne查詢出結果。幸運的是,Spring提供了自定義組件允許你從String類型到任意類型的轉換。

        • PropertyEditors(屬性編輯器)
          在Spring3.0之前,Java的PropertyEditor已經被使用。現在我們要集成它,SpringData提供了一個DomainClassPropertyEditorRegistrar類,他能在ApplicationContext中查找SpringData的
          Repositories,並且註冊自定義的PropertyEditor。

          <bean class="….web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
            <property name="webBindingInitializer">
              <bean class="….web.bind.support.ConfigurableWebBindingInitializer">
                <property name="propertyEditorRegistrars">
                  <bean class="org.springframework.data.repository.support.DomainClassPropertyEditorRegistrar" />
                </property>
              </bean>
            </property>
          </bean>

          如果你做了上面的工作,那麼你在前面的例子中,會大大減少工作量:

              @Controller
              @RequestMapping("/users")
              public class UserController {
              @RequestMapping("/{id}")
              public String showUserForm(@PathVariable("id") User user, Model model) {
              model.addAttribute("user", user);
              return "userForm";
              }
              }
      • 轉換服務
        在Spring3以後,PropertyEditor已經被轉換服務取代了,SpringData現在用DomainClassConverter模仿
        DomainClassPropertyEditorRegistrar中的實現。你可以使用如下的配置:

            <mvc:annotation-driven conversion-service="conversionService"/>
            <bean class="org.springframework.data.repository.support.DomainClassConverter">
            <constructor-arg ref="conversionService"/>

        如果你是用JavaConfig,你可以集成SpringMVC的WebMvcConfigurationSupport並且處理FormatingConversionService,那麼你可以這麼做:

        class WebConfiguration extends WebMvcConfigurationSupport {
        // 省略其他配置
        @Bean
        public DomainClassConverter<?> domainClassConverter() {
        return new DomainClassConverter<FormattingConversionService>(mvcConversionService());
        }
        }
    發表評論
    所有評論
    還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
    相關文章