원하는 기능을 가지는 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 구분, 핵심 비즈니스 로직과 아닌 것의 구분
<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가 관리해줌!! ( 더 깔끔하게 )
@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> 에 추가하면, 우리가 원하는 대로 생성/수정자를 지정할 수 있게 된다.
한가지 세팅이 필요함!!
이렇게@Bean
에AuditorAware<>
을 등록해줘야함.
- 해당 함수는
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 에 자동으로 매칭되는 것들!!
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;
}