※ 확장 기능
ㅁ 사용자 정의 리포지토리 구현
- 스프링 데이터 JPA 리포지토리는 인터페이스만 정의하고 구현체는 스프링이 자동 생성
- 인터페이스를 직접 구현하려면 구현해야하는 기능이 너무 많음
- 다양한 이유로 인터페이스를 메소 직접 구현하고 싶다면?
> JPA 직접 사용 (EntityManager)
> 스프링 JDBC Template 사용
> MyBatis 사용
> 데이터베이스 커넥션 직접 사용
> Querydsl 사용
- QueryDsl 사용할때 커스텀 리포지토리로 만들어서 구현함
- 네이밍을 맞춰주는 작업도 필요함 -> 인터페이스+Impl
> Impl 이 아닌 다른것으로도 변경이 가능하긴한데, 권장하지 않음
- 커스텀 리포지토리에 모든 로직을 생성하여 담기보다는, 핵심로직에 따른 repository와 특별한 쿼리들을 분리하는 방식으로도 생각 필요
* CQRS ( Command and Query Responsibility Segregation )
> 명령과 조회의 책임 분리
> 시스템의 상태를 변경하는 작업과 시스템의 상태를 반환하는 작업의 책임을 분리하는 것입니다.
- 서비스와 리포지토리 1대1 연결은 안티패턴 (실제 많이 사용되는 패턴이지만 비효율적이거나 비생산적인 패턴) 이다
ㅁ Auditing
- JPA 사용
> @PrePersist , @PreUpdate 사용 + @MappedSuperclass
> 이후 Entity에 상속
@MappedSuperclass
@Getter @Setter
public class JpaBaseEntity {
@Column(updatable = false)
private LocalDateTime createdDate;
private LocalDateTime updatedDate;
@PrePersist
public void prePersist(){
LocalDateTime now = LocalDateTime.now();
createdDate = now;
updatedDate = now;
}
@PreUpdate
public void preUpdate(){
updatedDate = LocalDateTime.now();
}
}
- 스프링 데이터 JPA 사용
> @EnableJpaAuditing 사용
> AuditorAware 를 등록하여 등록자 수정자 자동으로 등록
>> AuditingEntityListener 설정도 필요 ( META-INF/orm.xml 설정하여 글로벌 설정도 가능)
@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdDate;
@LastModifiedDate
private LocalDateTime lastModifiedDate;
@CreatedBy
@Column(updatable = false)
private String createdBy;
@LastModifiedBy
private String lastModifiedBy;
}
- 실무에서는 BaseTimeEntity, BaseEntity로 나누어 등록하여,
시간만 필요할때는 BaseTimeEntity 상속
등록자도 함께 필요할때는 BaseEntity 상속
>> BaseEntity는 BaseTimeEntity 상속
ㅁ Web 확장 - 도메인 클래스 컨버터
- 조회용으로만 사용하자
> update 할때도 사용할 수 있지만, 예외사항이 많다.
@GetMapping("/members2/{id}")
public String findMember(@PathVariable("id") Member member){
return member.getUsername();
}
ㅁ Web 확장 - 페이징과 정렬
- web mvc 에서도 pageable을 사용할 수 있다.
> Pageable을 컨트롤러 메소드에서 파라미터로 사용하면 PageRequest를 자동으로 스프링이 구현해서 만들어준다
- 아래와 같이 page 관련 default값 설정 가능 (global) - application.yml
data:
web:
pageable:
default-page-size: 10
max-page-size: 2000
- local하게 하고 싶으면 @PageableDefault(size = 5) 를 Pageable 파라미터 앞에 사용
- 페이징 처리를 하더라도, Page<Dto>로 반환하도록 하자
=> map 함수 사용
- 페이징 인덱스를 1부터 시작하게 하는 방법 2개가 있음
> 새로 Page를 구현함
> spring.data.web.pageable.one-indexed-parameters를 true로 설정
>> totalCount 등과 같은것은 0일때의 인덱스로 계산되어 문제가 있음
>> 결과적으로 0으로 인덱스 계산하는것을 추천함
※ 스프링 데이터 JPA 분석
ㅁ 스프링 데이터 JPA 구현체 분석
- SimpleJpaRepositry 가 JPA 구현체!
- JDBC나 JPA 오류 (하부기술 오류) 도 Spring 오류로 변경되어 출력됨
- SpringJpaRepository에 @Transactionnal 이 전부 있음
- @Transactional(readOnly=true) 로 할 경우, 플러시를 하지 않아 영속성 컨텍스트 항목이 DB에 반영 안됨
> 성능 최적화에 도움이 될 수 있음
- save 메서드
> 새로운 객체가 아닐 경우, merge 사용 (조회 하여 컨텍스트에 올린 후, 트랜스액션 끝날때 저장됨)
> 가급적이면 머지를 쓰면 안됨
>> 영속상태 엔티티가 영속상태를 벗어났을때 다시 영속상태로 만들때만 쓰는게 머지임
>> 영속상태의 엔티티는 merge를 사용하지 않는게 좋음
ㅁ 새로운 엔티티를 구별하는 방법
- EntityManager가 persist 할때 @GeneratedValue가 ID를 넣어줌
- id를 @GeneratedValue 를 빼고, 임의로 넣고 저장할 경우 어떻게 될까?
> persist를 호출하지 않고, merge를 진행하게 됨.. (select문을 호출)
> 없는것을 확인 한 후 , 새거임을 알아서 새로 저장
> 새로 저장하는데도 merge를 쓰기때문에 좋지 않음
>>> 엔티티에서 Persistable<Keytype> 을 구현함
>>> isNew를 createdDate 사용하여 구현
@Override
public boolean isNew() {
return createdDate == null;
}
※ 나머지 기능들
ㅁ Specifications (명세)
- JPA Criteria 는 비추 - 실무에서 어려움 (금지시키는 중) : 읽고 해석하기가 어렵다
- 동적쿼리 사용위해 이렇게 작성하긴한건데, QueryDsl 을 사용하면 더 쉬움
ㅁ Query By Example
- 실무 사용은 어려울듯
- JpaRepository 가 QueryByExampleRepository 상속
- 객체 자체를 쿼리로 만들 수 있음
- 아래와 같이 구성
ExampleMatcher matcher = ExampleMatcher.matching().withIgnorePaths("age");
Example<Member> example = Example.of(member,matcher);
List<Member> result = memberRepository.findAll(example);
- 문제점은 JOIN이 해결이 안됨
> Inner Join은 가능하나, Left Join (OuterJoin) 은 안됨
ㅁ Projection
- 엔티티 대신에 DTO를 편리하게 조회할 때 사용
> 전체 엔티티가 아니라 만약 회원의 이름만 조회하고 싶다면?
- 엔티티 객체가 아닌 프록시 객체가 나옴 (JdkDynamicAopProxy)
- 인터페이스에 특정 getter만 입력 해놓으면 스프링 데이터 jpa 가 구현체를 만들어서, 특정 field만 가져오게 쿼리를 날리게됨
> close projection
- open projection -> 엔티티를 모두 가져와서, 애플리케이션에서 연산함
import org.springframework.beans.factory.annotation.Value;
public interface UsernameOnly {
@Value("#{target.username + ' ' + target.age}")
String getUsername();
}
- nestedClosedProjection을 통해 중첩 구조도 해결할 수 있음
> user에 관해서는 정확하게 조회, 단 team은 엔티티로 가져와서 연산 하게됨
> 이것이 한계점!
- 프로젝션 대상이 root 엔티티면 유용하나, root 엔티티를 넘어가면 JPQL SELECT 최적화가 안됨
> 단순할때만 사용하고 복잡하면 querydsl을 사용하자
ㅁ 네이티브 쿼리
- @Query(nativeQuery = true, ) 통해 작성
- 동적쿼리 불가, 반환타입이 어려움
> 네이티브 쿼리 사용하고 싶을경우, JDBC Template이나 mybatis 사용하자
- 그나마 쓸만한 것
'Spring' 카테고리의 다른 글
[Spring] 실전! Querydsl - 2 (0) | 2021.09.09 |
---|---|
[Spring] 실전! Querydsl - 1 (0) | 2021.09.09 |
[Spring] 실전! 스프링 데이터 JPA - 2 (0) | 2021.09.03 |
[Spring] 실전! 스프링 데이터 JPA - 1 (0) | 2021.09.02 |
[Spring] 스프링 기반 REST API 개발 - 3 (0) | 2021.08.06 |