일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |
- Docker
- fastcampus
- aws
- java
- Cluster
- 설정
- redash
- SpringBoot
- hive
- 머신러닝
- Kafka
- 레디스
- Mac
- 간단
- Jenkins
- vue
- 예제
- 자바
- 젠킨스
- login
- spring
- 로그인
- ec2
- 클러스터
- gradle
- Zeppelin
- config
- 자동
- Redis
- EMR
- Today
- Total
코알못
[JPA] JPA 정복하기 - 02. 공통 인터페이스 기능 본문
이번에는 스프링 Data JPA를 이용하여 CRUD를 실습 해보도록 하자!
그 전에 스프링 Data JPA 가 주는 이점을 확실하게 알 기 위해 순수 JPA 기반 repository 만들고 스프링 데이터 JPA 공통 인터페이스를 사용하여 만들어 보도록 하자!
순수 JPA 기반 Repository를 아래와 같이 만든다.
TeamRepository.java
package study.datajpa.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import study.datajpa.entity.Team;
import java.util.List;
import java.util.Optional;
@Repository
public class TeamRepository {
@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();
}
}
MemberRepository.java
package study.datajpa.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.stereotype.Repository;
import study.datajpa.entity.Member;
import java.util.List;
import java.util.Optional;
@Repository
public class MemberJpaRepository {
/***
* @PersistenceContext
* - Autowired와 차이 구글링 해보니 동일하게 동작하나 JPA를 위한 어노테이션인 해당 어노테이션을 사용하는것이 좋을것 같다는 내용이 많다.
*/
@PersistenceContext
/***
* EntityManager
* - 영속(=관리=스프링에서 만든) 컨텍스트 공간에 있는것을 불러온다(현재는 영속 공간에 있는 EntityManager을 불러온다)
* - em.persist, em.find시 영속된 공간에 저장(=캐싱)하고 찾기에 결과는 정상적으로 나오나 DB에 실제로 가보면 없다.
* - 그래서 EntoryTransaction의 commit 메소드를 사용하여 영속 공간에서 DB로 저장된다.
* - 쓰기 지연 : 한번에 DB에 접근하기 떄문에 DB 부하를 줄일 수 있다.
* - 지연 로딩 : 쓸 시점에 조회하여 가져온다.
*/
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();
}
public Member find(Long id){
return em.find(Member.class, id);
}
}
그러나 repository를 보면 update가 구현되어 있지 않다..!
이는 트랜젝션 Commit시에 자동으로 변경 감지 되어 업데이트 되는 기능을 JPA 에서 제공하고 있기 때문에 따로 update 는 repository 에 정의하지 않아도 된다.
이제 테스트 검증을 위한 테스트 코드를 작성한다.
MemberJpaRepositoryTest.java
package study.datajpa.repository;
import jakarta.transaction.Transactional;
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.datajpa.entity.Member;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@Rollback(value = false)
@Transactional
@SpringBootTest
class MemberJpaRepositoryTest {
@Autowired
private MemberJpaRepository memberJpaRepository;
@Test
void testCRUD() {
Member member1 = Member.builder()
.username("member1")
.build();
Member member2 = Member.builder()
.username("member2")
.build();
memberJpaRepository.save(member1);
memberJpaRepository.save(member2);
// 단건 검증
Member findMember1 = memberJpaRepository.findById(member1.getId()).get();
Member findMember2 = memberJpaRepository.findById(member2.getId()).get();
assertEquals(findMember1, member1);
assertEquals(findMember2, member2);
// 리스트 조회 검증
List<Member> all = memberJpaRepository.findAll();
assertEquals(all.size(), 2);
// 카운트 검증
long count = memberJpaRepository.count();
assertEquals(count, 2);
// 변경 검증
member1.setUsername("member1!");
Member findMember = memberJpaRepository.findById(member1.getId()).get();
assertEquals(member1.getUsername(), findMember.getUsername());
// 삭제 검증
memberJpaRepository.delete(member1);
memberJpaRepository.delete(member2);
long deleteCount = memberJpaRepository.count();
assertEquals(deleteCount, 0);
}
}
이제 테스트 코드 testCRUD 메소드를 실행해보면 정상적으로 검증이 통과 한것을 볼 수 있다.
그러나 자세히 보면 TeamRepository 에 작성한 코드와 MemberRepository 에 작성한 코드가 사실상 엔티티를 제외하고 동일하다.
이를 해결해준것이 Spring Data JPA 이다.
우리는 Spring Data JPA 관련 Interface만 상속하면 알아서 Scan(@Repository 생략 가능) 하고 Spring Data JPA가 알아서 구현체를 만들어서 주입한다.
이를 실습 해보자!
MemberRepository.java
package study.datajpa.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import study.datajpa.entity.Member;
public interface MemberRepository extends JpaRepository<Member, Long> {
}
이게 끝이다... 해당 interface 만들고 Spring Data JPA 인터페이스인 JpaRepository 상속하고 <엔티티, PK 타입> 만 정의해주면 끝난다!
이제 테스트 코드를 작성해보자!
이전 테스트 코드 가져와서 repository명만 Spring Data JPA 인터페이스로 변경한다.
여기서 @Rollback 은 Test 코드의 경우 자동으로 Transaction 이 끝나면 Rollback 이 되기 때문에 롤백 되지 않도록 false로 정의한다.
MemberRepositoryTest.java
package study.datajpa.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.PersistenceContext;
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 org.springframework.transaction.annotation.Transactional;
import study.datajpa.entity.Member;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
@Transactional
@Rollback(value = false)
@SpringBootTest
class MemberRepositoryTest {
@Autowired
MemberRepository memberRepository;
@Test
void testCRUD() {
Member member1 = Member.builder()
.username("member1")
.build();
Member member2 = Member.builder()
.username("member2")
.build();
memberRepository.save(member1);
memberRepository.save(member2);
// 단건 검증
Member findMember1 = memberRepository.findById(member1.getId()).get();
Member findMember2 = memberRepository.findById(member2.getId()).get();
assertEquals(findMember1, member1);
assertEquals(findMember2, member2);
// 리스트 조회 검증
List<Member> all = memberRepository.findAll();
assertEquals(all.size(), 2);
// 카운트 검증
long count = memberRepository.count();
assertEquals(count, 2);
// 변경 검증
member1.setUsername("member1!");
Member findMember = memberRepository.findById(member1.getId()).get();
assertEquals(member1.getUsername(), findMember.getUsername());
// 삭제 검증
memberRepository.delete(member1);
memberRepository.delete(member2);
long deleteCount = memberRepository.count();
assertEquals(deleteCount, 0);
}
}
테스트 돌려보면 정상적으로 통과 됨을 알 수 있다. 정말 편리하게 할 수 있다...
다음시간에는 쿼리 메소드 기능(메소드로 쿼리 호출 기능)을 배워보도록 하자!
끝~!