스프링 데이터 JPA - 2

김강현·2023년 4월 26일
0

스프링 데이터 JPA

목록 보기
3/4
post-thumbnail

Custom Repository

  • 아얘 다른 형태로 쿼리를 작성하고 싶을때!! (주로 QueryDSL)
  • 내가 따로 interface 구현체를 만들어서 모든 기능을 override 시켜야하나?

원하는 기능을 가지는 interface 를 만들어, 구현체까지 만든다.

<MemberRepositoryCustom.java>

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

<MemberRepositoryImpl.java>

@RequiredArgsConstructor
public class MemberRepositoryImpl implements  MemberRepositoryCustom{

    private final EntityManager em;
    @Override
    public List<Member> findMemberCustom() {
        return em.createQuery("select m from Member m"+
                " left join fetch m.team t", Member.class)
                .getResultList();
    }
}

<MemberRepository.java>

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

이런 식으로 JpaRespository 랑 같이 상속시켜주면 된다!!

주의사항!!

구현체의 경우 이름을 꼭 맞춰줘야함! 그래야지 Spring Data JPA 가 추적 가능!
(repository이름)Impl 으로 구현체 이름을 지정해주어야함!
xml 파일에 뒤에 붙을 이름 지정해줄 수 있다!! (왠만하면 관례 따르자)

  • 그렇다고 해서! Custom 에 기능들 다 때려박으면 안된다! (항상 필요한게 아님)
  • 유지보수 측면에서 Repository Class 를 나누어 관리하는 것이 좋을 때가 많음
    ex) MemberQueryRepository, MemberUpdateRepository 등등
    ex) Command 와 Query 구분, 핵심 비즈니스 로직과 아닌 것의 구분

Auditing

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

<JpaBaseEntity.java>

public class JpaBaseEntity {

    @Column(updatable = false)
    private LocalDateTime createdDate;
    private LocalDateTime updatedDate;

    @PrePersist // persist 하기 전에 event 발생
    public void prePersist(){
        LocalDateTime now = LocalDateTime.now();
        createdDate = now;
        updatedDate = now;
    }

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

상속을 했지만, 이게 JPA 에서 DB Table 과 연결한다는 뜻은 아님!!
그래서 @MappedSuperclass 을 추가해줘야함

@MappedSuperclass
@Getter // 아래 테스트 코드를 위함
public class JpaBaseEntity {...}
    @Test
    public void JpaEventBaseEntity() throws Exception{
        // given
        Member member = new Member("member1", 10);
        memberRepository.save(member); // @PrePersist 발생

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

        em.flush();
        em.clear();

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

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


공통 관심사인 JpaBaseEntity 이거를 모든 엔티티에 상속하기한 하면,
Table 에서 관리 알아서 가능함!! 엄청나게 강력함!!

그런데 이거를 Spring 데이터 JPA가 관리해줌!! ( 더 깔끔하게 )

Spring Data JPA Auditing

@EnableJpaAuditing 을 꼭 넣어줘야함!! (Spring 시작 클래스)

<BaseEntity.java>

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

    @LastModifiedDate
    private LocalDateTime lastModifiedDate;
}

@EntityListeners(AuditingEntityListener.class) 가 추가적으로 필요함!

위의 <JpaBaseEntity.java> 와 비교하면! 좀 간결함 (별 차이 없긴 하네...)

public class Member extends BaseEntity{...}

이렇게 하고 실행하면,

똑같이 Table 이 만들어지는 것을 확인 가능!! (이름이 좀 다르긴 함 last modified)

생성자/수정자

이런식으로 <BaseEntity.java> 에 추가하면, 우리가 원하는 대로 생성/수정자를 지정할 수 있게 된다.

한가지 세팅이 필요함!!

이렇게 @BeanAuditorAware<> 을 등록해줘야함.

  • 해당 함수는 Create, Modify 가 일어날때 계속 호출 됨
  • 위 예시는 랜덤 값이지만, 실제로는 세션에서 회원정보를 불러오는 식으로!!

각 BaseEntity 마다 따로 @EntityListeners 를 달아주지 않고, 전체 설정도 가능
pom.xml 파일에 지정.. (생략)

김영한 개발자님의 경우 어떻게 실무에서 쓰는가
<BaseTimeEntity.java> 를 만들고, <BaseEntity.java> 는 이를 상속하도록!!
특정 엔티티는 Time 만 필요하고, 생성/수정자 추적은 필요가 x

도메인 클래스 컨버터

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

    @GetMapping("/members2/{id}")
    public String findMember2(@PathVariable("id") Member member){
        return member.getUsername();
    }

받아오는 값이 엔티티의 PK 값이면, 바로 Member로 받아올 수 있게 해줌!
findMember2 처럼!!

(조회용 엔티티 이므로, 영속성컨텍스트에서 관리 되지는 않음!)

페이징과 정렬

findAll 함수는 pageable을 받을 수 있도록 Spring Data JPA 에서 제공!

	@GetMapping("/members")
    public Page<Member> list(Pageable pageable){
        return memberRepository.findAll(pageable);
    }

input 값으로 pageable을 받는건 어떤 의미?


해당 데이터들과 함께, 21 ~ 40 번째 데이터를 content 로 반환한다.
(default 값이 20개임. 다양한 방식으로 바꿀 수 있음!)
(글로벌 설정)

(로컬설정)

size 를 지정해줄 수도 있음!

아래처럼 여러 파라미터를 전달할 수 있음.
pageable 에 자동으로 매칭되는 것들!!

Entity 를 Page<> 에 넣어서 하지 x

DTO 로 넘기자.

DTO Constructor를 Member 와 연결시켜 두면,

public MemberDto{
	...
 	public MemberDto(Member member){
        this.id = member.getId();
        this.username = member.getUsername();
        this.teamName = member.getTeam().getName();
    }
}

이런식으로 축약이 가능하다!

	@GetMapping("/members")
    public Page<MemberDto> list(@PageableDefault(size = 5) Pageable pageable) {
        Page<Member> page = memberRepository.findAll(pageable);
//        Page<MemberDto> dtoPage = page.map(member -> new MemberDto(member.getId(), member.getUsername(), ""));
        Page<MemberDto> dtoPage = page.map(MemberDto::new);
        return dtoPage;
    }
profile
this too shall pass

0개의 댓글