Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
Tags
- 로그인
- 임베디드 타입
- 프로젝트 환경설정
- 스프링
- jpa 활용
- 불변 객체
- 트위터
- 스프링 mvc
- 예제 도메인 모델
- 타임리프 문법
- JPA
- 벌크 연산
- 일론머스크
- JPA 활용 2
- 김영한
- Spring Data JPA
- 실무활용
- 타임리프
- QueryDSL
- 스프링 데이터 JPA
- 컬렉션 조회 최적화
- Bean Validation
- 값 타입 컬렉션
- 기본문법
- 페이징
- 스프링MVC
- 검증 애노테이션
- API 개발 고급
- JPA 활용2
- JPQL
Archives
- Today
- Total
RE-Heat 개발자 일지
[Spring Data JPA] [3] 공통 인터페이스 기능 본문
인프런 김영한 님의 강의를 듣고 작성한 글입니다.
[1] 순수 JPA 기반 리포지토리 만들기
■ 기본 CRUD
- 저장
- 변경 - 변경감지사용
- 삭제
- 전체조회
- 단건조회
- 카운트
수정(update)은 JPA가 제공하는 변경 감지 기능(dirty checking)을 쓰면 된다. 트랜잭션 안에서 엔티티를 조회한 다음 데이터를 변경하면 트랜잭션 종료 시점에 변경 감지 기능이 작동해 update SQL을 실행한다.
MemberJpaRepository
@Repository
@RequiredArgsConstructor
public class MemberJpaRepository {
private final EntityManager em;
//Create(저장)
public Member save(Member member){
em.persist(member);
return member;
}
//Delete(삭제)
public void delete(Member member){
em.remove(member);
}
//Read(조회)
public List<Member> findAll(){
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
public Optional<Member> findById(Long id){
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
public long count(){
return em.createQuery("select count(m) from Member m", Long.class)
.getSingleResult();
}
public Member find(Long id){
return em.find(Member.class, id);
}
}
- @PersistenceContext를 쓰는 대신 생성자로 주입함
- 저장/삭제/전체조회/단건조회/카운트 기능을 추가
- JPQL은 count() 같은 SQL도 지원한다.
- Optional.ofNullable은 null인지 아닌지 확신할 수 없는 객체를 담고 있는 Optional 객체를 생성한다.
- findById를 했을 때 null값이 올 수도 있으므로 스프링 데이터 JPA도 Optional<> 객체로 받는다.
- 참고로 Optional이 담고 있는 객체에 접근하는 방법은 get()이다.
TeamJpaRepository
@Repository
@RequiredArgsConstructor
public class TeamJpaRepository {
private EntityManager em;
public Team save(Team team){
em.persist(team);
return team;
}
public void delete(Team team){
em.remove(team);
}
public List<Team> findAll(){
return em.createQuery("select t from Team t", Team.class)
.getResultList();
}
public Optional<Team> findById(Long id){
Team team = em.find(Team.class, id);
return Optional.ofNullable(team);
}
public long count(){
return em.createQuery("select count(t) from Team t", Long.class)
.getSingleResult();
}
}
- 회원 리포지토리와 거의 동일하다.
MemberJpaRepositoryTest
@Test
public void basicCRUD(){
Member member1 = new Member("member1");
Member member2 = new Member("member2");
memberJpaRepository.save(member1);
memberJpaRepository.save(member2);
Member findMember1 = memberJpaRepository.findById(member1.getId()).get();
Member findMember2 = memberJpaRepository.findById(member2.getId()).get();
//단건 조회 검증
assertThat(findMember1).isEqualTo(member1);
assertThat(findMember2).isEqualTo(member2);
//리스트 조회 검증
List<Member> all = memberJpaRepository.findAll();
assertThat(all.size()).isEqualTo(2);
//카운트 검증
long count = memberJpaRepository.count();
assertThat(count).isEqualTo(2);
//삭제 검증
memberJpaRepository.delete(member1);
memberJpaRepository.delete(member2);
long deletedCount = memberJpaRepository.count();
assertThat(deletedCount).isEqualTo(0);
}
- Optional로 꺼내면 귀찮으니 있다고 가정하고 편의상 get()으로 꺼냄
참고] 변경감지로 update SQL 날아가는지 확인
실행결과
[2] 공통 인터페이스 설정
스프링 데이터 JPA가 제공하는 공통 인터페이스를 사용해 CRUD 코드를 간단히 해결!
■ 스프링 데이터 JPA가 구현 클래스 대신 생성
- 개발자가 interface만 선언해 주면 스프링 데이터 JPA가 자동으로 구현 클래스를 만들어 준다.
- org.springframework.data.repository.Repository를 구현한 클래스는 스캔대상
- MemberRepository 인터페이스가 동작한 이유
- 실제 출력해 보기(Proxy)
- memberRepository.getClass() class co m.sun.proxy.$ProxyXXX
[3] 공통 인터페이스 적용
MemberRepository - interface
public interface MemberRepository extends JpaRepository<Member, Long> {
}
- JpaRepository 상속함
- @Repository 애노테이션을 생략해도 된다.
- 컴포넌트 스캔을 스프링 데이터 JPA가 자동으로 처리
- JPA 예외를 스프링 예외로 변환하는 과정도 자동으로 처리
MemberRepositoryTest
@Test
public void basicCRUD(){
Member member1 = new Member("member1");
Member member2 = new Member("member2");
memberRepository.save(member1);
memberRepository.save(member2);
Member findMember1 = memberRepository.findById(member1.getId()).get();
Member findMember2 = memberRepository.findById(member2.getId()).get();
//단건 조회 검증
assertThat(findMember1).isEqualTo(member1);
assertThat(findMember2).isEqualTo(member2);
//리스트 조회 검증
List<Member> all = memberRepository.findAll();
assertThat(all.size()).isEqualTo(2);
//카운트 검증
long count = memberRepository.count();
assertThat(count).isEqualTo(2);
//삭제 검증
memberRepository.delete(member1);
memberRepository.delete(member2);
long deletedCount = memberRepository.count();
assertThat(deletedCount).isEqualTo(0);
}
- 순수 JPA로 만든 Test와 거의 유사하다. memberJpaRepository -> memberRepository로 바뀐 게 차이점
- 이유는 스프링 데이터 JPA의 메소드 이름과 맞춰 놓았기 때문
- 이제 순수 JPA를 만들 때처럼 일일이 구현하지 않아도 간편하게 쓸 수 있다.
[4] 공통 인터페이스 분석
- JpaRepository 인터페이스: 공통 CRUD 제공
- 제네릭은 <엔티티타입, 식별자타입> 설정
■ 공통 인터페이스 구성
- JpaRepository는 JPA 특화
- common 부분은 몽고 DB로 바꿔도 사용할 수 있다고는 하지만, 실무에선 적용하기 어렵다.
■ 제네릭 타입
- T: 엔티티
- ID: 엔티티의 식별자 타입
- S: 엔티티와 그 자식 타입
■ 주요 메서드
- save(S): 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.
- delete(T): 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove()를 호출한다.
- findById(ID): 엔티티 하나를 조회한다. 내부에서 EntityManager.find()를 호출한다.
- getOne(ID): 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference()를 호출한다.
- findAll(...): 모든 엔티티를 조회한다. 정렬(Sort)이나 페이징(Pageable) 조건을 파라미터로 제공 가능하다.
'백엔드 > 스프링 데이터 JPA' 카테고리의 다른 글
[Spring Data JPA] [5] 확장 기능 (0) | 2023.09.10 |
---|---|
[Spring Data JPA] [4] 쿼리 메소드 기능(하편) - 페이징·벌크연산·EntityGraph (0) | 2023.09.09 |
[Spring Data JPA] [4] 쿼리 메소드 기능(상편) (0) | 2023.09.08 |
[Spring Data JPA] [2] 예제 도메인 모델 (0) | 2023.09.08 |
[Spring Data JPA] [1] 프로젝트 환경 설정 (0) | 2023.09.07 |