Member
@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "name"})
public class Team {
@Id @GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
public Team(String name) {
this.name = name;
}
}
Team
@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "name"})
public class Team {
@Id @GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
public Team(String name) {
this.name = name;
}
}
Member Repository
@Repository
public class MemberJpaRepository {
@PersistenceContext // EntityManger 를 injection 해주는 annotation
private EntityManager em;
public Member save(Member member){
em.persist(member);
return member;
}
public long count(){
return em.createQuery("select count(m) from Member m", Long.class).getSingleResult();
}
public Optional<Member> findById(Long id){
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
public Member find(Long id){
return em.find(Member.class, id);
}
public void delete(Member member){
em.remove(member);
}
public List<Member> findAll(){
return em.createQuery("select m from Member m").getResultList();
}
}
TeamRepository
@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 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();
}
}
@SpringBootTest
@Transactional
public class MemberJpaRepositoryTest {
@Autowired
MemberJpaRepository memberJpaRepository;
@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);
}
}
참고. UPDATE는 JPA의 변경 감지(Dirty Checking)을 사용하므로 필요 없다.
- 위에 Member와 Team의 Repository는 서로 관리하는 Entity만 다르지 기능은 똑같다.
- 개발자가 CRUD같은 기본 로직을 계속 짜줘야한다. → 매우번거롭다
하지만 Spring Data JPA는 이 귀찮은 과정을 스킵할 수 있게한다.
@Configuration
@EnableJpaRepositories(basePackages = "jpabook.jpashop.repository")
public class AppConfig {}
- 스프링부트사용시
@SpringBootApplication
이 위치를지정(해당 패키지와 하위 패키지 인식) - 만약 패키지의 위치가 달라지면
@EnableJpaRepositories
가 필요하다
org.springframework.data.repository.Repository
를 구현한 클래스는 스캔 대상이다.- 그리하여 실제로 MemberRepository 인터페이스를 가져와 출력하면
memberRepo.getClass() → class com.sun.proxy.$ProxyXXX 와 같이 프록시 객체가 만들어진다는것을 알 수 있다.
- 그리하여 실제로 MemberRepository 인터페이스를 가져와 출력하면
@Repository
생략 가능하다
Member Repository
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {}
Team Repository
@Repository
public interface TeamRepository extends JpaRepository<Team, Long> {}
기존 순수 JPA 기반 테스트에서 사용했던 코드를 그대로 스프링 데이터 JPA 리포지토리 기반 테스트로 변경해도 동일한 방식으로 동작한다
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberRepositoryTest {
@Autowired MemberRepository memberRepo;
@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);
}
}
- 위와 같이 간단하게 만들 수 있다.
- 우리는 그냥 간단한 CRUD를 저기 만들어 놓은 Repository를 이용하여 만들면 된다.
- Spring Data JPA한테 구현해야 할 코드를 맡기자
- JpaRespotiroy 인터페이스: 공통 CRUD 제공
- 제네릭은 <Entity Type, 식별자 타입>으로 설정한다
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T>{
//etc....
}
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
는 대부분의 공통 메서드를 제공한다.