인터페이스만 정의해도 CRUD 기능을 사용할 수 있다 ?
테스트 코드를 통해서 CRUD 기능이 동작하는지 간단하게 테스트해보자.
테스트 순서
- Entity 생성
- 순수 JPA Repository 생성 후 테스트
- Data JPA Repository 생성 후 테스트
Entity 생성
먼저 Member Entity 를 만들자.
@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "username"})
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
public Member(String username) {
this.username = username;
}
}
순수 JPA Repository
Data-JPA 를 사용하기 전에 순수 JPA 로 CRUD 기능을 만들어보았다.
@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 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();
}
}
잘 동작하는지 테스트 코드로 확인해보자.
@SpringBootTest
@Transactional
@Rollback(value = false)
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);
findMember1.setUsername("member!!!!!");
// 리스트 조회 검증
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);
}
}
정상적으로 성공하는 것을 볼 수 있다.
Data JPA Repository
그렇다면 이제, Data-JPA Repository 를 만들고 Test 해보자.
public interface MemberRepository extends JpaRepository<Member, Long> {
}
끝났다 ..!
JpaRepository
를 상속받고 <대상 Entity, PK 타입>
만 지정하면 CRUD 기능을 그대로 사용할 수 있다 !
위 테스트 코드 그대로 테스트를 진행해보자 !
테스트 코드에서 Repository 만 MemberJpaRepository
가 아닌 MemberRepository
로 변경하고 진행하자.
@SpringBootTest
@Transactional
@Rollback(value = false)
class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
@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);
findMember1.setUsername("member!!!!!");
// 리스트 조회 검증
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);
}
}
동일하게 성공..!
이게 어떻게 가능한걸까 ?
JpaRepository 는 여러 interface 를 상속받고 있는데
그 중에 CrudRepository 라는 곳에 기본적인 기능들이 정의되어 있다.
물론 이 글에선 일부러 Data-JPA 인터페이스에서 제공하는 함수 이름과
동일하게 순수 JPA Repository 의 함수 이름을 동일하게 설정했지만,
어쨌든 이런 기능을 이미 제공하고 있기 때문에 공통 인터페이스의 중복 코드를 줄일 수 있다 !
사실 여기서 끝이 아니다.
메소드 이름으로 쿼리를 생성하는 등의 기능이 더 있지만 일단은 여기까지만 알아보자 ㅎㅎ
인터페이스만 있는데 어떻게 ?
👉 Spring Data JPA 가 구현체를 만들어서 주입해준다 ‼️
실제로 인터페이스인 Repository 클래스를 찍어보자.
위와 같이 가짜 Proxy 객체를 만들어서 주입해준 것을 확인할 수 있다.
Data JPA interface 를 만들면 @Repository 어노테이션을 생략할 수 있다.
컴포넌트 스캔을 Data JPA 가 자동으로 처리하고,
JPA 예외를 스프링 예외로 변환하는 과정도 자동으로 처리한다.