https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
Spring Data JPA - Reference Documentation
Example 119. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del
docs.spring.io
잘못된 내용이 있다면 알려주세요 🤔
Projections
Spring Data query method는 루트나 여러 인스턴스를 반환할 수 있다.
이런 type의 특정 특성을 기반으로 projection을 만들면 전용 return type을 모델링할 수 있다
예제코드
class Person {
@Id UUID id;
String firstname, lastname;
Address address;
static class Address {
String zipCode, city, street;
}
}
interface PersonRepository extends Repository<Person, UUID> {
Collection<Person> findByLastname(String lastname);
}
만약 여기서 실제 필요한 내용이 이름뿐이라면
또는 이름을 제외한 나머지를 보안상 가져오고 싶지 않다면 어떻게 해야할까?
Interface-based Projections
첫번째 방법은 인터페이스를 선언하여 가져오는 것이다
interface NamesOnly {
String getFirstname();
String getLastname();
}
이렇게 repository 를 만들면된다
interface PersonRepository extends Repository<Person, UUID> {
Collection<NamesOnly> findByLastname(String lastname);
}
The query execution engine creates proxy instances of that interface at runtime for each element returned and forwards calls to the exposed methods to the target object.
default method를 재정의하는 method가 선언되면 Repository는 선언된 return type과 관계없이 기본 메서드가 호출된다
projection에서는 이 기본 메서드를 사용할 수 없고 호환된 반환 type을 사용해야한다
@Query 로 재정의할 수 있도록 지원한다
사용하다보니 도시 정보도 필요하다고 한다.
그러면 Projection을 recursively하게 사용하면 된다
interface PersonSummary {
String getFirstname();
String getLastname();
AddressSummary getAddress();
interface AddressSummary {
String getCity();
}
}
interface NamesOnly {
@Value("#{target.firstname + ' ' + target.lastname}")
String getFullName();
…
}
해당 target 변수에 접근할 수 있기 때문에 해당 인터페이스는 @Value를 사용한 Open projection이 된다
쿼리 실행 최적화에 적용할 수 없다
그렇기 때문에 이런 방식 보다는
closed projection을 사용하고 커스텀 로직을 작성하는게 좋다
interface NamesOnly {
String getFirstname();
String getLastname();
default String getFullName() {
return getFirstname().concat(" ").concat(getLastname());
}
}
조금더 유연하게 사용하려면 이런 방법을 쓴다
@Component
class MyBean {
String getFullName(Person person) {
…
}
}
interface NamesOnly {
@Value("#{@myBean.getFullName(target)}")
String getFullName();
…
}
메서드 매개 변수는 args라는 개체 배열을 통해 사용할 수 있다.
args 배열에서 메소드 매개변수를 가져오는 방법이다.
interface NamesOnly {
@Value("#{args[0] + ' ' + target.firstname + '!'}")
String getSalutation(String prefix);
}
*** 복잡한 표현식은 Spring Bean을 사용해야하며, 표현식으로 method를 호출해야한다
Nullable wrapper 를 사용하면 null 안전성을 향상시킨다
옵셔널을 사용하면 된다
- java.util.Optional
- com.google.common.base.Optional
- scala.Option
- io.vavr.control.Option
interface NamesOnly {
Optional<String> getFirstname();
}
If the store optimizes the query execution by limiting the fields to be loaded, the fields to be loaded are determined from the parameter names of the constructor that is exposed.
class NamesOnly {
private final String firstname, lastname;
NamesOnly(String firstname, String lastname) {
this.firstname = firstname;
this.lastname = lastname;
}
String getFirstname() {
return this.firstname;
}
String getLastname() {
return this.lastname;
}
// equals(…) and hashCode() implementations
}
@Value
class NamesOnly {
String firstname, lastname;
}
Fields are private final by default, and the class exposes a constructor that takes all fields and automatically gets equals(…) and hashCode() methods implemented.
Dynamic Projections
지금까지는 return typeEhsms dyth dbguddmfh projection type을 사용하고 있었으나
호출 시에 사용할 유형을 선택할 수도 있다(which makes it dynamic)
interface PersonRepository extends Repository<Person, UUID> {
<T> Collection<T> findByLastname(String lastname, Class<T> type);
}
void someMethod(PersonRepository people) {
Collection<Person> aggregates =
people.findByLastname("Matthews", Person.class);
Collection<NamesOnly> aggregates =
people.findByLastname("Matthews", NamesOnly.class);
}
타입 지정해서 받기, 이거 너무 좋은데? 한번 나도 써봐야겠다
Query parameters of type Class are inspected whether they qualify as dynamic projection parameter.
If the actual return type of the query equals the generic parameter type of the Class parameter, then the matching Class parameter is not available for usage within the query or SpEL expressions.
If you want to use a Class parameter as query argument then make sure to use a different generic parameter, for example Class<?>.
'Web > spring' 카테고리의 다른 글
[Spring Framework integration] 6.Cache Abstraction (1) (0) | 2023.02.27 |
---|---|
[Spring REST] Setting up Your Tests (0) | 2023.02.26 |
[Spring Framework Core] 4.3. Language Reference (3) (0) | 2023.02.24 |
[Spring Framework Core] 4.3. Language Reference (2) (0) | 2023.02.23 |
[Spring Framework Core] 4.3. Language Reference (1) (0) | 2023.02.22 |