가급적 네이티브 쿼리는 사용하지 않는게 좋다. 정말 어쩔 수 없을 때 사용한다 

스프링 데이터 Projections를 활용한다 

1. 스프링 데이터 JPA기반 네이티브 쿼리

1) 페이징 지원

2) 반환타입: Object[],Tuple,DTO(스프링 데이터 인터페이스 Projections 지원)

3) 제약:

sort 파라미터를 통한 정렬이 정상 동작하지 않을 수 있음(믿지 말고 직접 처리 하자)

JPQL처럼 애플리케이션 로딩 시점에 문법 확인 불가

동적 쿼리 불가 

 

2. JPA 네이티브 SQL 지원

public interface MemberRepository extends JpaRepository<Member, Long> {

      @Query(value = "select * from member where username = ?", nativeQuery = true)
      Member findByNativeQuery(String username);
 }

1) JPQL은 위치 기반 파라미터를 1부터 시작하지만, 네이티브 쿼리는 0부터 시작

2) 네이티브 쿼리를 엔티티가 아닌 DTO로 변환 하려면

DTO 대신 JPA TUPLE 조회

DTO 대신 MAP 조회

@SqlResultSetMapping -> 복잡

Hibernate ResultTransformer를 사용해야함 -> 복잡 

네이티브 쿼리를 DTO로 조회할 때는 JdbcTemplate or myBatis 권장

3. Projections 활용

스프링데이터 JPA 네이티브 쿼리 + 인터페이스 기반 Projections 활용

closed인가?

@Query(value = "SELECT m.member_id as id, m.username, t.name as teamName " +
              "FROM member m left join team t",
              countQuery = "SELECT count(*) from member",
              nativeQuery = true)
  Page<MemberProjection> findByNativeProjection(Pageable pageable);

 

4. 동적 네이티브 쿼리

하이버네이트를 직접 활용

스프링 JdbcTemplate, myBatis,jooq 같은 외부 라이브러리를 사용한다

 

예시) 하이버네이트를 직접 활용

//given
  String sql = "select m.username as username from member m";
  List<MemberDto> result = em.createNativeQuery(sql)
          .setFirstResult(0)
          .setMaxResults(10)
          .unwrap(NativeQuery.class)
          .addScalar("username")
          .setResultTransformer(Transformers.aliasToBean(MemberDto.class))
          .getResultList();
          
}

1. 도메인 클래스 컨버터

도메인 클래스 컨버터는 HTTP 파라미터로 넘어온 엔티티의 아이디로 엔티티 객체를 찾아서 바인딩하는 것이다. 

도메인 클래스 컨버터 사용전

id를 받아서 회원 객체를 찾은 후, 회원의 이름을 반환한다 

@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberRepository memberRepository;
    
    @GetMapping("/members/{id}")
    public String findMember(@PathVariable("id") Long id) {
    
        Member member = memberRepository.findById(id).get();
        return member.getUsername();
    }
}

 

도메인 클래스 컨버터 사용후 

처음부터 회원 객체를 받아서 회원의 이름을 반환한다 

HTTP 요청(Request)은 회원 id를 받지만 도메인 클래스 컨버터가 중간에 동작해서 회원 엔티티 객체를 반환한다

도메인 클래스 컨버터도 리파지토리를 사용해서 엔티티를 찾는다

 

id로 요청이 오면, 도메인 클래스 컨버터가 Id를 기준으로 리포지토리에서 찾은다음 엔티티를 반환하는 건가?

@RestController
@RequiredArgsConstructor
public class MemberController {

    private final MemberRepository memberRepository;
    
    @GetMapping("/members/{id}")
    public String findMember(@PathVariable("id") Member member) {
        return member.getUsername();
        
      }
}

주의: 도메인 클래스 컨버터로 엔티티를 파라미터로 받으면, 이 엔티티는 단순 조회용으로만 사용해야 한다

(트랜잭션 범위 밖에서 엔티티를 조회했으므로, 엔티티를 변경해도 DB에 반영되지 않는다)

 

2. 페이징과 정렬

예제 

pageable은 인터페이스이므로, 실제는 pagerequest 객체를 생성한다 

 

요청 파라미터는 /members?page=0&size=3&sort=id,desc&sort=username,desc로 온다 

pageable에는 page(현재 페이지,0부터 시작), size(한 페이지에 노출할 데이터 건수), sort(정렬 조건, asc/desc)이 담겨있다 

@GetMapping("/members")
public Page<Member> list(Pageable pageable) {

    Page<Member> page = memberRepository.findAll(pageable);
    return page;
    
    }

 

개별 설정은 @PageableDefault 어노테이션을 사용한다 

@RequestMapping(value="/members_page", method= RequestMethod.GET)대신

@GetMapping(value="/members_page)로 하면 안되나? 

 

@RequestMapping(value = "/members_page", method = RequestMethod.GET)
public String list(@PageableDefault(size = 12, sort = “username”,direction = Sort.Direction.DESC) Pageable pageable) {
... 

}

엔티티를 생성, 변경할 때 변경한 사람과 시간을 추적하고 싶을 때 다음 과정을 따라하면 된다

 

1. 스프링 부트 설정 클래스(application.class)에 @EnableJpaAuditing 적용하기

2. 사용하려는 엔티티에 @EntityListeners(AuditingEntityListener.class)를 적용한다 

@MappedSuperclass도 적용한다 

 

스프링 데이터 Auditing 적용 (생성날짜, 수정날짜)

package study.datajpa.entity;

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity {
    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createdDate;
    
    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}

 

실무에서 수정시간, 생성시간을 대부분 필요하지만, 수정자와 등록자는 필요 없을 수 있어서

따로 클래스를 만든 후 BaseEntity를 상속하는 방법으로 진행한다 

public class BaseEntity extends BaseTimeEntity {

      @CreatedBy
      @Column(updatable = false)
      private String createdBy;
      
      @LastModifiedBy
      private String lastModifiedBy;
}

 

등록자, 수정자를 처리해주는 AuditorAware 스프링 빈을 등록한다.

실무에서는 세션 정보나, 스프링 시큐리티 로그인 정보에서 ID를 받는다 

@Bean
    public AuditorAware<String> auditorProvider() {
        return () -> Optional.of(UUID.randomUUID().toString());
    }

member와 team은 지연로딩 관계이다. 따라서 team의 데이터를 조회할때(1번), member를 조회하는 쿼리가 실행된다(n번)

 

-참고: 지연로딩 여부 확인하는 방법 

//Hibernate 기능으로 확인
Hibernate.isInitialized(member.getTeam())
  
//JPA 표준 방법으로 확인
PersistenceUnitUtil util = em.getEntityManagerFactory().getPersistenceUnitUtil(); 
util.isLoaded(member.getTeam());

 

위의 1+N 문제를 해결하기 위해서는 페치 조인이 필요하다 

 

-@EntityGraph

연관된 엔티티들을 SQL 한번으로 조회하는 방법으로, 페치 조인의 간편 버전이다

LEFT OUTER JOIN을 사용한다 

//공통 메서드 오버라이드
@Override
@EntityGraph(attributePaths = {"team"}) 
List<Member> findAll();

//JPQL + 엔티티 그래프 
@EntityGraph(attributePaths = {"team"}) 
@Query("select m from Member m") 
List<Member> findMemberEntityGraph();

//메서드 이름으로 쿼리에서 특히 편리하다. 
@EntityGraph(attributePaths = {"team"}) 
List<Member> findByUsername(String username)

 

-참고:

즉시로딩으로 findAll()을 조회하면 내부에서 left outer join이 걸리지 않음-> findAll()은 JPQL 쿼리가 나간다. JPQL쿼리가 실행되면, JPA는 JPQL 쿼리에 최적화를 실행하지 않는다 

즉시로딩으로 findById()로 처리하면 left outer join이 걸림-> em.find() 메서드가 호출되고, 이때 즉시로딩으로 설정되어있으면 left outer join으로 최적화가 진행된다 

 

--> 되도록이면 지연로딩을 사용하자 

 

+ Recent posts