Spring Data JPA2

유요한·2023년 12월 8일
0

JPA

목록 보기
9/10
post-thumbnail

페이징과 정렬

페이징과 정렬 파라미터

  • org.springframework.data.domain.Sort: 정렬기능
  • org.springframework.data.domain.Pageable : 페이징 기능(내부에 Sort 포함)

특별한 반환 타입

  • org.springframework.data.domain.Page : 추가 count 쿼리 결과를 포함하는 페이징
  • org.springframework.data.domain.Slice : 추가 count 쿼리 없이 다음페이지만 확인가능(내부적으로 limit +1 조회)

    자신의 페이지는 모르지만 11개째는 더보기를 눌러서 다음페이지를 가져오는 방법


벌크성 수정 쿼리

 @Modifying(clearAutomatically = true)
 @Transactional
 @Query("update MemberEntity m set m.age = m.age + 1 where m.age >= :age")
 int bulkAgePlus(@Param("age") int age);

여기서 보면 @Modifying이 있는데 Spring Data Jpa에서 변경할 때 꼭 넣어줘야 합니다.

그렇다면 이거는 언제 사용해줘야 할까?

@Query 어노테이션을 통해 작성된 변경이 일어나는 쿼리(INSERT, DELETE, UPDATE )를 실행할 때 사용된다. @Modifying을 변경이 일어나는 쿼리와 함께 사용해야 JPA에서 변경 감지와 관련된 처리를 생략하고 더 효율적인 실행이 가능하다. 즉, JPA에서 벌크 연산은 단 건 데이터를 변경(더티 체킹)하는 것이 아닌, 여러 데이터에 변경 쿼리를 날리는 작업을 말한다.

@Modifying 애노테이션은 기본적으로 @Transactional과 함께 사용된다.
변경 작업은 트랜잭션 내에서 실행되어야 하며, 완료되지 않은 변경 작업이 여러 작업에 영향을 줄 수 있기 때문이다. 이를 통해 데이터베이스에 대한 변경 작업을 수행할 때 원자성(Atomicity), 일관성(Consistency), 독립성(Isolation), 지속성(Durability)을 보장할 수 있게 된다.

주의점

JPA 에서는 1차 캐시라는 기능이 있다. 1차 캐시를 간단하게 설명하면 영속성 컨텍스트에 있는 1차 캐시를 통해 엔티티를 캐싱하고, DB의 접근 횟수를 줄임으로써 성능 개선 한다.

그런데 @Modifying과 @Query 를 사용한 벌크 연산에서 1차 캐시와 관련하여 문제가 발생한다. JPA 에서 조회를 실행할 시에 1차 캐시를 확인해서 해당 엔티티가 1차 캐시에 존재한다면 DB에 접근하지 않고, 1차 캐시에 있는 엔티티를 반환한다. 하지만 벌크 연산은 1차 캐시를 포함한 영속성 컨텍스트를 무시하고 바로 Query를 실행하기 때문에 영속성 컨텍스트는 데이터 변경을 알 수가 없다. 즉, 벌크 연산 실행 시, 1차 캐시(영속성 컨텍스트)와 DB의 데이터 싱크가 맞지 않게 되는 것이다. (만약 벌크 쿼리를 실행하고 다시 해당 데이터를 조회하면 영속성 컨텍스트에 과거 값이 남아 문제가 발생 )

이 경우 변경된 데이터를 사용하기 전에 영속성 컨텍스트를 비워주는 작업이 필요한데, @Modifying의 clearAutomatically=true 속성을 사용해 변경 후 자동으로 영속성 컨텍스트를 초기화 할 수 있다. 해당 속성을 추가하게 되면, 조회를 실행할 때 1차캐시에 해당 엔티티가 존재하지 않기 때문에 DB 조회 쿼리를 실행하게 된다. ( 데이터 동기화 문제를 해결 )

@Modifying(clearAutomatically = true)

@EntityGraph

    // findAll할 때 멤버도 조회하면서 팀까지 조회하고 싶을 때
    // 기본 적으로 findAll 을 제공하기 때문에 Override 하여 재정의 후 사용
    @Override
    // DataJpa 에서 fetch 조인을 하기 위한 설정
    @EntityGraph(attributePaths = {"team"})
    List<MemberEntity> findAll();

여기서 attributePaths = {"team"}이름은 private team 'team'; 을 적으시면 됩니다.

    // JPQL과 같이 사용하는 방법
    @EntityGraph(attributePaths = {"team"})
    @Query("select m from MemberEntity m")
    List<MemberEntity> findMemberEntityGraph();

이렇게 JPQL과 같이 사용할 수 있는 방법도 있습니다. 그렇다면 언제 JPQL에서 패치 조인을 하고 언제 @EntityGraph을 사용할까?

좀 복잡해지면 JPQL에서 패치조인을 사용하고 간단하면 @EntityGraph을 사용하면 됩니다.


JPA Hint & Lock

JPA Hint

JPA 쿼리 힌트(SQL 힌트가 아니라 JPA 구현체에게 제공하는 힌트)

    @QueryHints(value =  @QueryHint(name = "org.hibernate.readOnly", value = "true"))
    MemberEntity findReadOnly(String userName);

Hint는 스냅샷을 만들지 않기 때문에 메모리가 절약됩니다. 즉, 읽기 전용입니다. 이거와 비슷한 것이 @Transaction(readOnly=true)입니다. @Transaction(readOnly=true)는 트랜잭션 커밋 시점에 flush를 하지 않기 때문에 이로 인한 dirty checking 비용이 들지 않습니다. 따라서 cpu가 절약됩니다.

스프링 5.1 버전 이후를 사용하시면 @Transaction(readOnly=true)로 설정하시면, @QueryHint의 readOnly까지 모두 동작합니다. 즉, @Transaction(readOnly=true)로 대체해서 사용하면 됩니다.

Lock

    @Lock(LockModeType.PESSIMISTIC_WRITE)
    List<MemberEntity> findLockByUserName(String userName);

사용자 정의 레포지토리 구현

  • Spring Data JPA 레포지토리는 인터페이스만 정의하고 구현체는 스프링이 자동 생성

  • Spring Data JPA가 제공하는 인터페이스를 직접 구현해야 하는 기능이 너무 많음

  • 다양한 이유로 인터페이스의 메소드를 직접 구현하고 싶다면?

    • JPA 직접 사용(EntityManager)
    • 스프링 JDBC Template 사용
    • MyBatis 사용
    • 데이터베이스 커넥션 직접 사용
    • QueryDsl 사용 등
public interface MemberRepositoryCustom {
    // Spring Data JPA말고 직접 구현한 것을 사용하고 싶을 때
    List<MemberEntity> findMemberCustom();
}
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom{

    private final EntityManager em;

    @Override
    public List<MemberEntity> findMemberCustom() {
        return em.createQuery("select m from MemberEntity m")
                .getResultList();
    }
}

이렇게 순수한 ORM JPA를 사용하거나 네이티브 쿼리를 쓰고 싶은데 JPA기능이 아니라 약간 JDBC 템플릿을 사용하고 싶을 때 사용하면 된다. 하지만 한 가지를 더해줘야 합니다.

public interface MemberRepository extends JpaRepository<MemberEntity, Long>, MemberRepositoryCustom {
}

Spring Data JPA를 상속받은 인터페이스에 상속해줘야 합니다.

보통 실무에서는 복잡한 동적 쿼리같은 것들을 해결하기 위해 QueryDsl을 많이 구현하는 식으로 사용합니다.

참고

항상 사용자 정의 레포지토리가 필요한 것은 아니다. 그냥 임의의 레포지토리를 만들어도 된다. 예를 들어, MemberQueryRepository를 인터페이스가 아닌 클래스로 만들고 스프링 빈으로 등록해서 사용해도 된다. 물론 이 경우 Spring Data JPA와는 아무런 관계없이 별도로 동작한다.

이 말은 커스텀에 뭉쳐넣지 말고 실무에서 필요에따라 핵심 비즈니스로직인지 아니면 화면의 복잡한 것을 보여주는 로직인지 판별해서 나눠서 넣는 것이 좋습니다.

Business Logic?

비즈니스 로직(Business logic)은 컴비즈니스 로직은 주로 비즈니스 프로세스와 관련이 있습니다. 시스템이 수행하는 핵심 비즈니스 기능이나 규칙을 정의합니다. 예를 들어, 은행 시스템에서 계좌 이체, 입출금, 이자 계산 등과 같은 핵심 비즈니스 프로세스가 비즈니스 로직에 해당할 수 있습니다.

Domain Logic?

도메인 로직(Domain Logic)은 소프트웨어에서 가장 핵심적인 부분 중 하나로, 소프트웨어 시스템의 핵심 비즈니스 규칙을 포함하는 부분으로, 비즈니스 로직과 유사한 개념이지만 보다 추상적인 개념입니다. 도메인 로직은 계좌 잔액 체크, 이체 가능 여부 판단, 이체 기록 작성 등과 같은 비즈니스 규칙을 구현합니다.

Data Logic?

데이터 로직은 데이터의 저장, 검색, 수정, 삭제 등과 같은 데이터 관련 작업을 수행하는 코드를 말합니다. 이는 데이터베이스와 관련된 작업을 처리하는 데에 사용되며, 데이터에 대한 유효성 검사, 데이터 편집, 데이터베이스 연결 등을 수행합니다. 기본적인 CRUD 기능이라 생각하면 좋다!

데이터 로직은 도메인 모델을 이용하여 계좌 정보와 이체 기록을 데이터베이스에 저장합니다.

profile
발전하기 위한 공부

0개의 댓글