2 분 소요


사용자 정의 Repository

  • 스프링 데이터 JPA Repository는 인터페이스만 정의하고, 구현체는 스프링이 자동 생성
  • 스프링 데이터 JPA가 제공하는 인터페이스를 직접 구현하면 구현해야 하는 기능이 너무 많음
  • 다양한 이유로 인터페이스의 메서드를 직접 구현하고 싶다면..
    • JPA 직접 사용(EntityManager)
    • 스프링 JDBC Template 사용
    • MyBatis 사용
    • 데이터베이스 커넥션 직접 사용
    • Querydsl 사용

구현

사용자 정의 인터페이스

public interface MemberRepositoryCustom {
    List<Member> findMemberCustom();
}

사용자 정의 인터페이스 구현 클래스

@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom {

    private final EntityManager em;

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

사용자 정의 인터페이스 상속

public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {

}

사용자 정의 메서드 호출

List<Member> result = memberRepository.findMemberCustom();

사용자 정의 구현 클래스

  • 규칙 : Repository 인터페이스 이름 + Impl
  • 스프링 데이터 JPA가 인식해서 스프링 빈으로 등록

[참고]
실무에서는 주로 QueryDSL이나 SpringJdbcTemplate을 함께 사용할 때 사용자 정의 Repository 기능 자주 사용

[참고]
항상 사용자 정의 Repository가 필요한 것은 아니고, 그냥 임의의 Repository를 만들어도 된다.
예를들어 MemberQueryRepository를 인터페이스가 아닌 클래스로 만들고 스프링 빈으로 등록해서 그냥 직접 사용해도 된다. 이 경우에는 스프링 데이터 JPA와는 아무런 관계 없이 별도로 동작한다.


사용자 정의 Repository(최신)

스프링 데이터 2.x부터는 사용자 정의 구현 클래스에 Repository 인터페이스 이름 + Impl을 적용하는 대신에 사용자 정의 인터페이스 명 + Impl 방식도 지원한다.
예를 들어 위 예제의 MemberRepositoryImpl대신에 MemberRepositoryCustomImpl같이 구현해도 된다.

최신 사용자 정의 인터페이스 구현 예제

@RequiredArgsConstructor
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom {

    private final EntityManager em;

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

기존 방식보다 이 방식이 사용자 정의 인터페이스 이름과 구현 클래스 이름이 비슷하므로 더 직관적이다. 추가로 여러 인터페이스를 분리해서 구현하는 것도 가능하기 때문에 새롭게 변경된 이 방식을 사용하는 것을 더 권장한다.


Auditing

  • 엔티티를 생성, 변경할 때 변경한 사람과 시간을 추적하고 싶다면..
    • 등록일
    • 수정일
    • 등록자
    • 수정자

순수 JPA

등록일과 수정일 적용

@MappedSuperclass
@Getter
public class JpaBaseEntity {

    @Column(updatable = false)
    private LocalDateTime createDate;
    private LocalDateTime updateDate;

    @PrePersist
    public void prePersist() {
        LocalDateTime now = LocalDateTime.now();
        createDate = now;
        updateDate = now;
    }

    @PreUpdate
    public void preUpdate() {
        updateDate = LocalDateTime.now();
    }
}
public class Member extends JpaBaseEntity {}

확인 코드

@Test
void JpaBaseEntity() throws InterruptedException {
    // given
    Member member = new Member("member1");
    memberRepository.save(member);  // @PrePersist

    Thread.sleep(100);
    member.setUsername("member2");

    em.flush(); // @preUpdate
    em.clear();

    // when
    Member findMember = memberRepository.findById(member.getId()).get();

    // then
    System.out.println("findMember.createdDate = " + findMember.getCreatedDate());
    System.out.println("findMember.updatedDate = " + findMember.getUpdatedDate());
}

출력 값

findMember.createDate = 2022-06-25T17:07:06.533483
findMember.updateDate = 2022-06-25T17:07:06.634725

JPA 주요 이벤트 어노테이션

  • @PrePersist, @PostPersist
  • @PreUpdate, @PostUpdate

스프링 데이터 JPA

설정
@EnableJpaAuditing -> 스프링 부트 설정 클래스에 적용해야함
@EntityListeners(AuditingEntityListener.class) -> 엔티티에 적용

사용 어노테이션

  • @CreatedDate
  • @LastModifiedDate
  • @CreatedBy
  • @LastModifiedBy

스프링 데이터 Auditing 적용

등록일, 수정일, 등록자, 수정자

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
@Getter
public class BaseEntity {

    @CreatedDate
    @Column(updatable = false)
    private LocalDateTime createDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;

    @CreatedBy
    @Column(updatable = false)
    private String createBy;

    @LastModifiedBy
    private String lastModifiedBy;
}

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

DataJpaApplication.java

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

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

[참고]
실무에서 대부분의 엔티티는 등록시간, 수정시간이 필요하지만, 등록자, 수정자는 없을 수도 있다. 그래서 다음과 같이 Base 타입을 분리하고, 원하는 타입을 선택해서 상속한다.

public class BaseTimeEntity {
    @CreateDate
    @Column(updatable = false)
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}

---

public class BaseEntity extends BaseTimeEntity {
    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String lastModifiedBy;
}


<출처 : 인프런 - 실전! 스프링 데이터 JPA(김영한)>

댓글남기기