스프링데이터 + JPA/스프링 데이터 JPA

5. 순수 JPA 리포지토리

sdafdq 2023. 11. 18. 23:18

강의 순서는

순수 JPA 리포지토리 만든다음, 

순수 JPA 리포지토리 -> 스프링 데이터 JPA

저 순수 JPA 리포지토리를 똑같은 기능을 가진 스프링 데이터 JPA로 어떻게 만드냐? 

이거임

 

먼저 멤버 리포지토리

@Repository
@RequiredArgsConstructor
public class MemberJpaRepository {
    private final 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);
    }

    public void delete(Member member){
        em.remove(member);
    }

    public long count(){
        return em.createQuery("select count(m) from Member m", Long.class)
                .getSingleResult();
    }

    public List<Member> findAll(){
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }
}

뭐 보면 앎.

findById는 저렇게 옵셔널로.

저렇게 옵셔널로 한번 감싸는 게 NullException 등 방지할 수 있음.

 

delete는 그냥 em.remove 해주면 됨.

 

count는 저렇게.. JPQL에서 SQL에 있는 메소드들은 거의 기본적으로 제공을 해 줌.

 

@Repository
@RequiredArgsConstructor
public class TeamRepository {
    private final 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();
    }
}

팀인데.

보면 굉장히 비슷함.

그리고 우리가 저것들?

CrudRepository 인터페이스에서 본 적 있음..

다름이 아니고, JpaRepository, 즉 Spring Data JPA가 자동으로 구현해 주는 게 그냥 저런 모양일 거임.

 

@Test
public void basicCRUD(){
    Member member1 = new Member("member1", 10);
    Member member2 = new Member("member2", 11);

    memberJpaRepository.save(member1);
    memberJpaRepository.save(member2);

    Member findMember1 = memberJpaRepository.findById(member1.getId()).get();
    Member findMember2 = memberJpaRepository.findById(member2.getId()).get();

    assertThat(findMember1).isSameAs(member1);
    assertThat(findMember2).isSameAs(member2);

    List<Member> members = memberJpaRepository.findAll();
    assertThat(members).containsOnly(member1, member2);

    long count = memberJpaRepository.count();
    assertThat(count).isEqualTo(2);

    memberJpaRepository.delete(member1);
    Optional<Member> deletedMember = memberJpaRepository.findById(member1.getId());
    assertThat(deletedMember.isEmpty()).isTrue();
}

테스트. 기존에 있던 MemberRepository 테스트(더 정확히는 MemberJpaRepository의 테스트)에 추가함.

보면 저장 한다음,

찾아서,

찾을 때 저렇게 옵셔널을 .get()해서 바로 찾는 건 안좋다고 함.

.orElse()라고 따로 있는데 이런 걸로 null인지 아닌지 확인 하면서 하는 거 인듯.

여튼 그렇게 save()하고, 찾아서 isSameAs해보면,  

맞음. assertThat 해봐도 무사히 넘어감.

 

isSameAs는 말 그대로 같은건지, 주소가 같은건지 물어보는 거고,

isEqualTo는 같은 값인지 물어보는 거.

근데 같은 트랜잭션(지금 테스트 자체를 클래스 레벨에 @Transactional 해 놓음) 내에서는 영속성 컨텍스트가 같은 레퍼런스를 보장함.

그래서 isSameAs 해도 맞아서 저 assertThat도 무사히 넘어감.

 

그 다음 findAll() 해서 다 가져오고,

containsOnly로 member1과 member2를 포함하는지,

그러니가 저 contains 종류가 3개 있는데

contains 그냥 포함여부

containsOnly 포함여부인데, 원소의 개수와 값이 같아야 함.

containsExactly 이거는 원소의 개수, 값, 순서까지 다 같아야 함.

 

이번엔 member1, member2 순서는 그다지 상관 없어서 containsOnly로 함.

 

저것도 무사히 넘어감.

우리가 지금 ddl-auto 옵션을 create로 써놔서, 테이블 존재하면 지우고 다시 생성하면서 시작함.

사실 이렇게 하는 것 보다는,

엔티티에 핵심 비즈니스 로직을 포함시키며 스프링 환경과 상관없이 단위 테스트를 만들어 놓고,

스프링 환경용 통합테스트? 단위테스트 같은 것도 따로 만들어 놓는 것이 좋은 것 일듯.

 

그 test패키지에 application.yml따로 준 다음 설정 주거나 datasource 비워두면 메모리 DB에서 테스트 할 수 있는걸로 앎.

그래서 그거는 그냥 한번 실행하고 끝나면 휘발되는거라..

지금은 test패키지에 따로 application.yml 안 만들어 놔서 그냥 main패키지의 application.yml 따라가서 실제로 DB에 테이블 생성되고 그러는 중.

여튼 거기에 ddl-auto 옵션을 create로 해 놔서 test도 그거 따라가서 게다가 @SpringBootTest라서 지금 내가 @Rollback(false)도 해놔서 DB에 값이 남음.

 

여튼 그렇고, 그렇게 containsOnly 검사했고,

이제 count는 우리가 createQuery로 짜 놓은

public long count(){
    return em.createQuery("select count(m) from Member m", Long.class)
            .getSingleResult();
}

이거 실행되서 반환됨.

 

그럼 우리가 ddl-auto가 create라서 아예 테이블 있으면 삭제됐다가 다시 생성되면서 member1, member2 넣고 시작하면서

저렇게 조회해 보면(애초에 저렇게 JPQL로 짜놓은 쿼리가 나가는 순간은 영속성 컨텍스트를 flush해 놓음. JPQL -> SQL을 하긴 하지만 또 좀 개발자가 어떤 쿼리를 적어 놓을 지 미지수니까 여러가지 면에서 반영시키기 위해 그 전에 미리 flush를 해 놓는 듯.)

 

그래서, 그렇게 member 2개 넣어놨으니까 그냥 매직넘버로 2가 맞는 지 검사.

참임. 저것도 넘어감.

 

그다음 member1을 삭제 해 봄.

public void delete(Member member){
    em.remove(member);
}

그냥 이게 삭제.

 

그 후 조회해보면 이게 remove는 영속성 컨텍스트에서 제거시켜버리는 거라.

영속성 컨텍스트에 없으므로 DB에 쿼리가 날라간다.

그 후 똑같은 id로 조회해본다.

그럼 일단 Optional을 반환타입으로 해놨으니까 그게 오고,

그 옵셔널에 Optional.isEmpty() 해보면 비어있는지 아닌 지 나온다.

그게 .isTrue()인지 확인 해 본다.

이것도 delete해서 지워서, 없는걸로, 즉 null이므로 참으로 넘어간다.

 

영속성 컨텍스트에 관한 건

https://qwefdg3.tistory.com/707

여기 참조.