Spring Data JPA
를 사용하여 기본 CRUD를 사용하기전에 Spring Data JPA
가 제공해주는 기능들을 직접 구현해보고자 한다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED) //private X -> 프록시가 객체를 만들때 오류가 발생
@ToString(of = {"id", "username", "age"}) //출력할때 찍은 컬렴들 지정 가능
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
private Integer age;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
//생성자
public Member(String username) {
this.username = username;
}
public Member(String username, int age, Team team) {
this.username = username;
this.age = age;
if(team != null) //단순하게 null 처리 -> 실제로는 예외 발생시키는 로직으로 설계
changeTeam(team);
}
//양방향 연관관계 매핑
public void changeTeam(Team team){
this.team = team;
team.getMembers().add(this);
}
}
/**
* 순수 JPA 기반 레포지토리
*/
@Repository
public class MemberJpaRepository {
@PersistenceContext
private EntityManager em;
//저장
public Member save(Member member){
em.persist(member);
return member;
}
//단건 조회
public Member find(Long id){
return em.find(Member.class, id);
}
public Optional<Member> findById(Long id){
Member member = em.find(Member.class, id);
return Optional.ofNullable(member);
}
//복수건 조회
//JPQL 사용
public List<Member> findAll() {
return em.createQuery("select m from Member m",Member.class)
.getResultList();
}
//개수 조회
//JPQL 사용, count 는 Long 타입 반환!
public Long count() {
return em.createQuery("select count(m) from Member m", Long.class)
.getSingleResult();
}
//삭제
public void delete(Member member){
em.remove(member);
}
}
테스트를 위해 Memeber Entity와 Spring Data JPA
가 제공해주는 공통 인터페이스의 기능을 레포지토리로 만들어보았다.
/**
* spring Data JPA 테스트
*/
@SpringBootTest
@Transactional
public class MemberRepositoryTest {
@Autowired
MemberJpaRepository memberJpaRepository;
@Test
public void basicCRUD() throws Exception {
Member member1 = new Member("memberA");
Member member2 = new Member("memberB");
memberRepository.save(member1);
memberRepository.save(member2);
//단건 조회 검증
Member findMember1 = memberRepository.findById(member1.getId()).get();
Member findMember2 = memberRepository.findById(member2.getId()).get();
Assertions.assertThat(findMember1).isEqualTo(member1);
Assertions.assertThat(findMember2).isEqualTo(member2);
//복수 조회 검증
List<Member> all = memberRepository.findAll();
Assertions.assertThat(all.size()).isEqualTo(2);
//카운트 검증
Long count = memberRepository.count();
Assertions.assertThat(count).isEqualTo(2);
//삭제 검증
memberRepository.delete(member1);
memberRepository.delete(member2);
Long deleteCount = memberRepository.count();
Assertions.assertThat(deleteCount).isEqualTo(0);
}
}
테스트를 실행해보면 정상적으로 동작하게 된다.
이제 Spring Data JPA
가 제공해주는 공통 인터페이스로 바꾸어보겠다.
/**
* 스프링 데이터 JPA 기반 레포지토리
*/
public interface MemberRepository extends JpaRepository<Member, Long> {
}
스프링 데이터 JPA
가 제공해주는 공통 인터페이스인 JpaReository<T,ID>
를 상속받게 하였다.
(T:엔티티 타입, ID:식별자,PK 타입)
레포지토리만 변경하여(@Autowired) 위에서 했던 테스트 코드를 그대로 실행시켜보면 정상적으로
동작되는것을 알 수 있다.
그 이유를 알기 위해 간단히 공통 인터페이스를 분석해보겠다.
JpaReository<T,ID>
가 상속한 인터페이스들의 메서드들을 살펴보게 되면
우리가 앞서 순수 JPA로 구현했던 메서드들이 인터페이스로 모두 있는것을 알수 있다.
아래와 같이 공통 인터페이스의 구성을 살펴보게 되면 많은 인터페이스가 있는것을 알수있다.
스프링 데이터
의 부분에는 관계형 데이터베이스든 비관계형 데이터베이스든 공통으로 제공할 수 있는 기술들의 인터페이스를 모아놓았다.
스프링 데이터 JPA
에는 JPA
에 특화된, 관계형 데이터베이스에 특화된 기능들의 인터페이스를 모아논 곳이다.
주요 메서드들로는 save, delete, findByID 등...
공통적으로 속한 메서드들을 제공해주게 된다.
하지만 공통 메서드가 아닌 도메인에 특화된(member.name, member.age 등..) 기능들일 경우일 경우에는 spring data jpa
가 쿼리 메소드 기능을 제공하여 이를 쉽게 구현할수 있도록 해준다.