2 minute read

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

  • 스프링 데이터 JPA 리포지토리는 인터페이스만 정의하고 구현체는 스프링이 자동 생성
  • 스프링 데이터 JPA가 제공하는 인터페이스를 직접 구현하면 구현해야 하는 기능이 너무 많음
  • 다양한 이유로 인터페이스의 메서드를 직접 구현하고 싶다면?
//사용자 정의 인터페이스//
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();

사용자 정의 구현 클래스

규칙: 리포지토리 인터페이스 이름 + Impl

스프링 데이터 JPA가 인식해서 스프링 빈으로 등록


Audting

엔티티를 생성, 변경할 때 변경한 사람과 시간을 추적하고 싶으면?

  • 등록일
  • 수정일
  • 등록자
  • 수정자
//순수 JPA 사용//
@MappedSuperclass
@Getter
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();
      }
}
  public class Member extends JpaBaseEntity {} 

// 확인 코드//
@Test
public void JpaEventBaseEntity() throws Exception {

      Member member = new Member("member1");
      memberRepository.save(member); //@PrePersist
      Thread.sleep(100);
      member.setUsername("member2");
      em.flush(); //@PreUpdate
      em.clear();

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

      System.out.println
      ("findMember.createdDate = " +findMember.getCreatedDate());

      System.out.println
      ("findMember.updatedDate = " +findMember.getUpdatedDate());
  }


//스프링 데이터 Auditing 적용 - 등록일, 수정일//
 @EntityListeners(AuditingEntityListener.class)
 @MappedSuperclass
 @Getter
 public class BaseEntity {
      @CreatedDate
      @Column(updatable = false)
      private LocalDateTime createdDate;

      @LastModifiedDate
      private LocalDateTime lastModifiedDate;
}

//스프링 데이터 Auditing 적용 - 등록자, 수정자//
 @EntityListener(AuditingEntityListener.class)
  @MappedSuperclass
  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;
  }

  //등록자, 수정자를 처리해주는 AuditorAware 스프링 빈 등록//
   @Bean
   public AuditorAware<String> auditorProvider() {
        return () -> Optional.of
        (UUID.randomUUID().toString());
    }


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

public class BaseTimeEntity {
        @CreatedDate
        @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;
  }


Web 확장 - 도메인 클래스 컨버터

//도메인 클래 컨버터 사용 전//
@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();
      }
}

//도메인 클래스 컨버터 사용 후//
@RestController
@RequiredArgsConstructor
public class MemberController {

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


Web 확장 - 페이징과 정렬

//페이징과 정렬 예제//
@GetMapping("/members")
public Page<Member> list(Pageable pageable) {
        Page<Member> page = 
        memberRepository.findAll(pageable);
        return page;
    }
 
//기본값, 글로벌 설정 : 스프링 부트//

//# 기본 페이지 사이즈//
spring.data.web.pageable.default-page-size=20 

///# 최대 페이지 사이즈//
spring.data.web.pageable.max-page-size=2000 

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

//접두사//
//페이징 정보가 둘 이상이면 접두사로 구분//
public String list(
      @Qualifier("member") Pageable memberPageable,
      @Qualifier("order") Pageable orderPageable, ...


Page 내용을 DTO로 변환하기

엔티티를 API로 노출하면 다양한 문제가 발생한다.

그래서 엔티티를 꼭 DTO로 변환해서 반환해야 한다.

Page는 map() 을 지원해서 내부 데이터를 다른 것으로 변경할 수 있다.

//MemberDTO//
@Data
  public class MemberDto {
      private Long id;
      private String username;
      public MemberDto(Member m) {
          this.id = m.getId();
          this.username = m.getUsername();
      }
  }

//Page.Map()사용//
@GetMapping("/members")
public Page<MemberDto> list(Pageable pageable) {
      Page<Member> page = 
      memberRepository.findAll(pageable);
      Page<MemberDto> pageDto = 
      page.map(MemberDto::new);
      return pageDto;
}

//코드 최적화//
@GetMapping("/members")
public Page<MemberDto> list
(Pageable pageable) {
      return memberRepository.findAll
      (pageable).map(MemberDto::new);
  }

댓글남기기