순수 JPA 기반 리포지토리 만들기
순수한 JPA 기반 레파지토리를 만들기
기본 CRUD
- 저장
- 변경 -> 변경감지 사용
- 삭제
- 전체 조회
- 단건 조회
- 카운트
참고 : JPA 에서 수정은 변경감지 기능을 사용하면 된다.
트랜잭션 안에서 엔티티를 조회한 다음에 데이터를 변경하면 , 트랜잭션 종료 시점에 변경 감지 기능이 작동해서 변경된 엔티티를 감지하고 update sql 을 실행한다.
순수 JPA 기반 리포지토리 - 회원
package study.data_jpa.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import org.springframework.stereotype.Repository;
import study.data_jpa.entity.Member;
import java.util.List;
import java.util.Optional;
@Repository
public class MemberJpaRepository {
@PersistenceContext
private EntityManager em;
public Member save(Member member){
em.persist(member);
return member;
}
public void delete(Member member) {
em.remove(member);
}
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 List<Member> findAll() {
// JPQL 테이블이 아닌 객체를 대상으로 하는 쿼리
return em.createQuery("select m from Member m", Member.class)
.getResultList();
}
public Member find(Long id) {
return em.find(Member.class, id);
}
}
순수 JPA 기반 리포지토리 - 팀
package study.data_jpa.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import study.data_jpa.entity.Team;
import java.util.List;
import java.util.Optional;
@Repository
public class TeamJPARepository {
@PersistenceContext
private EntityManager em;
public Team save(Team team){
em.persist(team);
return team;
}
public void delete(Team team) {
em.remove(team);
}
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();
}
public List<Team> findAll(){
return em.createQuery("select t from Team t", Team.class)
.getResultList();
}
public Team find(Long id){
return em.find(Team.class, id);
}
}
회원 레포지토리와 거의 동일하다.
순수 JPA 기반 리포지토리 테스트
package study.data_jpa;
import jakarta.transaction.Transactional;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import study.data_jpa.entity.Member;
import study.data_jpa.repository.MemberJpaRepository;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberJpaRepositoryTest {
@Autowired
MemberJpaRepository memberJpaRepository;
@Test
public void testMember(){
Member member = new Member("memberA");
Member savedMember = memberJpaRepository.save(member);
Member findMember = memberJpaRepository.find(savedMember.getId());
assertThat(findMember.getId()).isEqualTo(member.getId());
assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
assertThat(findMember).isEqualTo(savedMember); // //JPA 엔티티 동일성 보장
}
@Test
public void basicCRUD() {
Member member1 = new Member("member11");
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();
findMember1.setUsername("member !!!!!!!!");
assertThat(findMember1).isEqualTo(member1);
assertThat(findMember2).isEqualTo(findMember2);
// 리스트 조회 검증
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 deleteCount = memberJpaRepository.count();
assertThat(deleteCount).isEqualTo(0);
}
}
기본 crud 를 검증한다.
공통 인터페이스 설정
JavaConfig 설정- 스프링 부트 사용시 생략 가능
@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig {}
- 스프링 부트 사용시 @SpringBootApplication 위치를 지정(해당 패키지와 하위 패키지 인식)
- 만약 위치가 달라지면 @EnableJpaRepositories 필요
MemberRepository 는 인터페이스일 뿐인데 어떻게 구현체가 없는데 @Autowired 시
@Autowired
MemberJpaRepository memberJpaRepository;
객체가 들어오고 코드가 실행될까?
객체를 확인해보자!
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberRepositoryTest {
@Autowired MemberRepository memberRepository;
@Test
public void testMember() {
System.out.println("memberRepository = " + memberRepository.getClass());
}
}
스프링 데이터 JPA 가 해당 인터페이스를 보고 구현 클래스를 만들어 주입 시킨다.
스프링 데이터 JPA 가 구현 클래스 대신 생성
org.springframework.data.jpa.repository 를 구현한 클래스는 스캔 대상
MemberRepository 인터페이스가 동작한 이유
공통 인터페이스 적용
순수 JPA로 구현한 MemberJpaRepository 대신에 스프링 데이터 JPA 가 제공하는 공통 인터페이스 적용
package study.data_jpa;
import org.springframework.data.jpa.repository.JpaRepository;
import study.data_jpa.entity.Member;
public interface MemberRepository extends JpaRepository<Member, Long> {
}
MemberRepository 테스트
package study.data_jpa;
import jakarta.transaction.Transactional;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import study.data_jpa.entity.Member;
import java.util.List;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberRepositoryTest {
@Autowired MemberRepository memberRepository;
@Test
public void testMember() {
// System.out.println("memberRepository = " + memberRepository.getClass());
Member member = new Member("memberA");
Member savedMember = memberRepository.save(member);
Member findMember = memberRepository.findById(savedMember.getId()).get();
assertThat(findMember.getId()).isEqualTo(member.getId());
assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
assertThat(findMember).isEqualTo(savedMember);
}
@Test
public void basicCRUD() {
Member member1 = new Member("member11");
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();
findMember1.setUsername("member !!!!!!!!");
assertThat(findMember1).isEqualTo(member1);
assertThat(findMember2).isEqualTo(findMember2);
// 리스트 조회 검증
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 deleteCount = memberRepository.count();
assertThat(deleteCount).isEqualTo(0);
}
}
기존 순수 JPA 기반 테스트에서 사용해썬 코드 그대로 스프링 데이터 JPA 레파지토리 기반 테스트로 변경해도 동일하게 동작
TeamRepository 생성
package study.data_jpa;
import org.springframework.data.jpa.repository.JpaRepository;
import study.data_jpa.entity.Team;
public interface TeamRepository extends JpaRepository<Team, Long> {
}
TeamRepository는 테스트 생략
- Generic
- T: 엔티티 타입
- ID: 식별자 타입(PK)
공통 인터페이스 분석
- JpaRepository 인터페이스 : 공통 CRUD 제공
- 제네릭은 <엔티티 타입, 식별자 타입> 설정
JpaRepository 공통 기능 인터페이스
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>,
ListPagingAndSortingRepository<T, ID>,
QueryByExampleExecutor<T> {
...
}
JpaRepository 를 사용하는 인터페이스
public interface MemberRepository extends JpaRepository<Member, Long> {
}
제네릭 타입
- T : 엔티티
- ID : 엔티티의 식별자 타입
- S : 엔티티와 그 자식 타입
주요 메서드
- save(S) : 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.
- delete(T) : 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove() 호출
- findById(ID) : 엔티티 하나를 조회한다. 내부에서 EntityManager.find() 호출
- getOne(ID) : 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference() 호출
- findAll(…) : 모든 엔티티를 조회한다. 정렬( Sort )이나 페이징( Pageable ) 조건을 파라미터로 제공할 수 있다.
참고: JpaRepository 는 대부분의 공통 메서드를 제공한다.
참고 자료 : 실전! 스프링 데이터 JPA
'JPA' 카테고리의 다른 글
양방향 연관관계와 연관관계 주인 (0) | 2024.12.05 |
---|---|
[JPA] 영속성 컨텍스트 (0) | 2024.11.16 |