(JPA+Boot)레포지토리 구현
JPA 레포지토리 계층에서 인터페이스를 활용한 확장성 있는 데이터 접근 구조를 설계하고, Spring Data JPA와 일반 JPA를 효과적으로 조합하는 방법을 다루는 가이드입니다.
(레포지토리) Repository + 인터페이스
레포지토리는 DB에 데이터 처리하는 역할
확장성을 위해서 “레포지토리,서비스 단에 인터페이스를 활용”할 수 있다.
레포지토리는 Spring Data JPA + JPA
함께 사용 중! -> 게시물 “스프링 DB 관련” 챕터 필독!
-
Spring Data JPA는
JpaReository 인터페이스
로 볼 수 있고, - 상속 시 서비스 단에서 바로 사용이 가능하다.
-> 자동으로 구현체를 만들어 주기 때문 -
Sring Data JPA 기본 제공 CRUD표
단, 아래 예시 코드는 어댑터추가ver-type2이고, 본인은 보통 단순ver을 사용!
예시 코드 보기 - 레포지토리 어댑터추가ver
MemberRepository 인터페이스 + MemberRepositoryCustom 인터페이스 + MemberRepositoryCustomImpl 클래스 의 조합
/**
* MemberRepository 인터페이스 -> 서비스 계층에서 사용하게 될 본체
* 여기서 2개의 인터페이스를 상속 받는다.
* - JpaRepository<Member, Long> 이 바로 Spring Data JPA 이다.
* - MemberRepositoryCustom 이 바로 일반 JPA 이다.
*/
public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
//CRUD 자동 제공
}
/**
* MemberRepositoryCustom 인터페이스 -> 일반 JPA로 커스텀
*/
public interface MemberRepositoryCustom {
Member findOne(Long id);
Member findByUid(String uid);
List<FindMemberResponseDto> findAllWithPage(int pageId);
}
/**
* MemberRepositoryCustomImpl 클래스 -> 일반 JPA로 커스텀 구현체
* - save() 주석 -> JpaRepository 꺼 사용하려고
* - @Override -> 인터페이스 구현 때 당연히 필수 키워드
*/
@Repository
@RequiredArgsConstructor // 생성자 주입 + 엔티티매니저 주입 제공
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom {
private final EntityManager em;
/**
* save, findOne, findByUid, findAllWithPage
*/
// public void save(Member member) {
// if (member.getId() == null) {
// em.persist(member); // db 저장
// }
// }
@Override
public Member findOne(Long id) {
return em.find(Member.class, id);
}
@Override
public Member findByUid(String uid) {
List<Member> findMembers = em.createQuery("select m from Member m where m.uid = :uid",
Member.class)
.setParameter("uid", uid)
.getResultList(); // List로 반환 받아야 null처리가 쉬움
return findMembers.isEmpty() ? null : findMembers.get(0);
}
@Override
public List<FindMemberResponseDto> findAllWithPage(int pageId) {
int offset = (pageId - 1) * 10;
int limit = 10;
List<Object[]> objects = em.createNativeQuery("select m.member_id, m.nickname, e.level " +
"from (select * from member order by member_id desc limit " + offset + "," + limit + ") m "
+
"inner join character c on m.character_id=c.character_id " +
"inner join exp e on c.exp_id=e.exp_id;")
.getResultList();
return objects.stream()
.map(o -> new FindMemberResponseDto((Long) o[0], (String) o[1], (Long) o[2]))
.collect(Collectors.toList());
}
}
코드 작성 TIP:
- @Repository: 레포지토리 빈 자동 등록
-
레포에서 em.createQuery()할때
.getResultList();
로 반환받는게 null처리 용이!! (보통 하나를 받더라도 이걸로 하는 중!) -
Spring Data JPA를 사용해보면 Optional<> 로도 반환하므로
orElse(null)
또는orElseThrow(new ... )
등 알기!- 단, Member 이런식으로 반환타입 명시하면 Optional<>로 반환 안함!
-
DTO: RequestDto, ResponseDto는 JsonIgnore문제에도 자유롭다!(DTO Lazy강제초기화 필수)
- 주로 컨트롤러 단에서 내부 엔티티 보호 목적으로 사용! -> “컨트롤러 파트 보자”
- 따라서 “미리 요청,응답 양식이 있는게 아니라면” 컨트롤러 단 개발할 때 Dto 고려
- 테스트코드 작성엔 DTO 생길수록 코드수정이 늘어나긴 함
-
@Transactional 작성에 주의하자.
- 일반 JPA를 사용할 때는 서비스 계층의 @Transactional 범위 내에서 동작하지만, Spring Data JPA는 각 메서드가 독립적인 트랜잭션으로 실행될 수 있어 차이가 발생!
- 서비스 계층(상위 계층)에 @Transactional이 또 있으면 해당(상위) 범위로 동작
- EntityManager는 트랜잭션 범위의 영속성 컨텍스트를 사용 하므로 당연히 @Transactional 필요
- 참고 TDD 코드 플젝: entertainment 플젝
- 자세한 JPQL 관련 개념은 아래 “JPQL파트”에서 참고
댓글남기기