我們在前幾次的單元中提到了延遲初始化的概念,但延遲初始化其實並不是解決問題的根本方法,當這個議題產生時,通常想要解決的問題就是──我們一次抓出太多不必要的資料了,
那有沒有一個方法,可以讓我們選擇只抓出部分的資料而不是全部呢?有的!去找吧!我把所有的資源都放在那裡了!那我們今天的分享就到這裡──
嘖!不行嗎?好吧。
Spring Projection 主要想要解決的問題,就是避免撈出不必要的資料,透過在Repository 回傳一個介面而不是物件的形式,比方說以下程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Entity @Getter @Setter public class Address { @Id private Long id; @OneToOne private Person person; private String state; private String city; private String street; private String zipCode; }public interface AddressView { String getZipCode () ; @Value("#{target.city + ' ' + target.state}") String getCityAndState () ; }public interface AddressRepository extends Repository <Address, Long> { List<AddressView> getAddressByState (String state) ; }AddressView addressView = addressRepository.getAddressByState("CA" ).get(0 ); assertThat(addressView.getZipCode()).isEqualTo("90001" ); assertThat(addressView.getCityAndState()).isEqualTo("Los Angeles CA" );
在上面的範例中,於JPA的Repository中建立了getAddressByState()方法,並指定回傳物件為List的AddressView這個自訂物件,這樣的查詢好處是只會選擇到被使用的欄位,避免選擇到不需要的物件造成資源的浪費。
接著我們再看一個例子。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @Entity @Getter @Setter public class Person { @Id private Long id; private String firstName; private String lastName; @OneToOne(mappedBy = "person") private Address address; }public interface PersonView { String getFirstName () ; String getLastName () ; @Value("#{target.firstName + ' ' + target.lastName}") String getFullName () ; }public interface AddressView { PersonView getPerson () ; }AddressView addressView = addressRepository.getAddressByState("CA" ).get(0 );PersonView personView = addressView.getPerson(); assertThat(personView.getFirstName()).isEqualTo("John" ); assertThat(personView.getLastName()).isEqualTo("Doe" );
我們修改了AddressView,使他可以回傳關聯物件(Person)的資料,但回傳的形式使用了另外一個Interface接,透過PesonView介面,我就可以訪問Person類別的firstName與lastName。
此外,Projection還有提供 動態投影的效果,比方說可以在上面的Repository加入這一行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class PersonDto { private String firstName; private String lastName; public PersonDto (String firstName, String lastName) { this .firstName = firstName; this .lastName = lastName; } }public interface PersonView { String getFirstName () ; String getLastName () ; @Value("#{target.firstName + ' ' + target.lastName}") String getFullName () ; }public interface PersonRepository extends Repository <Person, Long> { <T> T findByLastName (String lastName, Class<T> type) ; }Person person = personRepository.findByLastName("Doe" , Person.class);PersonView personView = personRepository.findByLastName("Doe" , PersonView.class);PersonDto personDto = personRepository.findByLastName("Doe" , PersonDto.class); assertThat(person.getFirstName()).isEqualTo("John" ); assertThat(personView.getFirstName()).isEqualTo("John" ); assertThat(personDto.getFirstName()).isEqualTo("John" );
你可以使用各種類別或介面去封裝回傳的物件。當然,要確保屬性的命名與SpringDataJPA的風格一致。
那今天的部分就先到這邊吧,明天見了。
參考資料:
https://docs.spring.io/spring-data/jpa/reference/repositories/projections.html