JPA

Spring Data JPA와 기존 JPA의 차이점 및 사용법

빈코 2022. 10. 13. 15:35

JPA를 사용하면 무수히 많은 CRUD의 반복, 객체와 데이터베이스 매핑 관계의 반복 등 지루한 코드들에서 탈출할 수 있고 또 다른 장점들도 많이 존재합니다. 혹여나 기존 JPA를 모르신다면 해당 링크를 참고해주세요. 이 포스팅은 JPA를 좀 더 쉽게 사용할 수 있는 Spring Data JPA의 사용법을 초점으로 작성하였습니다.

 

로고
Spring Data JPA

개요

사실 Spring Data JPA와 순수 JPA의 차이점이라는 제목이 어울리는지는 잘 모르겠네요. Spring Data JPAJPA를 좀 더 쉽게 사용할 수 있게 도와주는 라이브러리라고 생각하시면 될 것 같아요. 하지만, 순수 JPA를 정확히 모르는 상황에서 Spring Data JPA를 사용하시는 것은 좋지 않습니다. 

 

Spring Data JPA에서는 많은 함수를 제공하는데 만약 JPA의 동작 방식을 모른다면 실무에서 많이 막힐 수 있어요. 그렇기 때문에, 서두에서 언급한 것과 같이 JPA를 정확히 알고 사용하시기를 추천드려요.

 

그러면 순수 JPA와 Spring Data JPA의 사용법을 간단한 예시를 보면서 살펴볼까요?

 

예제 도메인 모델📚

엔티티
Entity
ERD
ERD

위 사진과 같이 Member EntityTeam Entity를 구현할 예정이고, 맴버와 팀은 다대일 관계로 구성돼요.

 

@Entity
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of = {"id", "username", "age"})
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;

    public Member(String username, int age) {
        this.username = username;
        this.age = age;
    }

    public Member(String username) {
        this.username = username;
    }

    public Member(String username, int age, Team team) {
        this.username = username;
        this.age = age;
        if (team != null) {
            changeTeam(team);
        }
    }

    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }
}

📌 Spring Data JPA 사용법이 포스팅의 초점이기 때문에 코드 설명은 넘어가셔도 좋습니다😀

 

  • @Entity : JPA가 관리하는 클래스로 해당 클래스를 엔티티라 한다. 실제 DB와 연관되어 있는 클래스
  • @Getter : getter를 대신 생성해주는 롬복 라이브러리
  • @Setter : setter를 대신 생성해주는 롬복 라이브러리, 실무에서는 Entity에서 사용하지 않는 것이 좋아요. 프로그램 상에서 set을 많이 사용하다 보면 어디서 데이터가 변하는지 추적하기 어려워집니다
  • @NoArgsConstructor : 기본 생성자를 대신 생성해주는 롬복 라이브러리, 기본 생성자를 다른 곳에서 쓰는 걸 막기 위해서는 PRIVATED를 사용해야 하지만, JPA 스펙상 PROTECTED로 열어두어야 해요.
  • @ToString : toString() 메소드를 대신 생성해주는 롬복 라이브러리(가급적 내부 필드만 사용)

 

 

도메인 모델을 참고해서 Member Entity에는 식별자인 Id, 이름, 나이로 구성했어요. @Id와 같은 엔티티 매핑은 해당 포스팅을 참고해주세요😄

 

@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도 Member Entity와 동일하게 도메인 모델을 참조하면서 작성했습니다.

 

순수 JPA📒

기존의 JPA는 Repository를 만들고 해당 Repository에 사용할 함수들을 정의하는 형식으로 진행됩니다. 가장 기본적인 CRUD부터 Id를 이용해서 찾기 등의 함수들을 구현해볼게요.

 

@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 Member find(Long id) {
        return em.find(Member.class, id);
    }

    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();
    }
 }

JPA를 아신다면 위에 코드들이 어렵게 느껴지진 않을 것 같아요. 또한, update(수정) 함수가 없는 이유도 아실텐데요. 혹여나 JPA가 제공하는 변경 감지(Dirty Checking)를 모르시면 링크를 클릭하셔서 선수 학습하시는 것을 추천드려요😄

 

📌 JPA는 수정 함수가 아닌 변경 감지를 사용한다.

 

@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);
}

간단하게 기본적인 CRUD 함수들의 Test 코드를 작성했을 때 문제없이 작동되는 것을 확인하실 수 있을 거예요. Team Entity에 대한 코드들도 작성할 수 있지만, Member Entity와 크게 다른 점이 없어서 생략했어요.

 

이제 Spring Data JPA에서는 위에 코드들을 어떻게 간편화하는지 알아볼까요?

 

Spring Data JPA📘

앞서 언급한 것 처럼, Spring Data JPA는 JPA를 보다 쉽게 사용할 수 있게 도와주는 역할을 합니다. 조금 더 자세히 설명하면 Repository 개발 시 인터페이스만 작성하면 실행 시점에 Spring Data JPA가 구현 객체를 동적으로 생성해서 주입해줍니다.

 

즉, 데이터 접근 계층을 개발할 때 구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있도록 지원합니다! 그럼 어떤 코드가 필요할까요?

 

public interface MemberRepository extends JpaRepository<Member, Long> {}

 

믿기 힘들겠지만 끝입니다! JpaRepository를 extend 해서 인터페이스만 구현해도 이미 구현된 많은 기능들을 사용할 수 있습니다. JpaRepository의 제네릭 타입은 엔티티와 해당 엔티티의 식별자 타입을 넣어주시면 됩니다.

 

저는 Member Entity를 예시로 들었기 때문에, 엔티티인 Member와 멤버의 식별자(Id) 타입인 Long 타입을 넣어주었어요.

 

JpaRepository
JpaRepository

JpaRepository를 살펴보면 위에서 작성했던 코드들을 전부 포함하고 있습니다. 또한, 테스트를 Repository만 바꿔서 실행해도 같은 결과를 도출한다는 것을 아실 수 있으실 겁니다. Spring Data JPA가 제공하는 주요 메서드로는 

 

  • save(S) : 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.(Merge)
  • delete(T) : 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove() 호출
  • findById(ID) : 엔티티 하나를 조회한다. 내부에서 EntityManager.find() 호출
  • getOne(ID) : 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference() 호출
  • findAll() : 모든 엔티티를 조회한다. 정렬(Sort)이나 페이징(Pageable) 조건을 파라미터로 제공할 수 있다.

 

 

도식표
도식표

 

간단하게 그림으로 본다면 위의 그림처럼 될 것 같아요. 한 가지 더 알아야 할 사실은 순수 JPA의 Repository를 개발할 때는 @Repository 애노테이션을 붙였지만, Spring Data JPA의 인터페이스에는 따로 애노테이션을 작성할 필요가 없어요.

 

📌 컴포넌트 스캔을 자동으로 처리하고, JPA 예외를 스프링 예외로 변환하는 과정도 자동으로 처리해주기 때문입니다!

 

@Test
public void testMember() {
    Member member = new Member("memberA");
    Member savedMember = memberRepository.save(member);

    Member findMember = memberRepository.findById(savedMember.getId()).get();

    assertThat(findMember.getId()).isEqualTo(member.getId());
    assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
    assertThat(findMember).isEqualTo(member);
}

 

간단한 테스트 코드를 작성했어요. 저는 JpaRepository를 확장한 memberRepository 인터페이스에서 어떠한 함수도 작성하지 않았지만, save(), findById()의 메서드를 사용할 수 있습니다.

 

 


👨‍👩‍👦‍👦 오픈채팅방 운영

취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁

 

참여코드 : 456456

https://open.kakao.com/o/gVHZP8dg

 

비전공 개발자 취업 준비방(질문&답변)

#비전공 #개발자 #취업 #멘토링 #부트캠프 #국비지원 #백엔드 #프론트엔드 #중소기업 #중견기업 #자바 #Java #sql

open.kakao.com

 


👨‍💻 전자책 출간

아울러 제가  🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁

https://kmong.com/gig/480954

 

비전공개발자 2년만에 중견기업 들어간 방법 | 14000원부터 시작 가능한 총 평점 0점의 전자책, 취

0개 총 작업 개수 완료한 총 평점 0점인 Binco의 전자책, 취업·이직 전자책 서비스를 0개의 리뷰와 함께 확인해 보세요. 전자책, 취업·이직 전자책 제공 등 14000원부터 시작 가능한 서비스

kmong.com


 

마치며

지금까지 Spring Data JPA의 가장 간단한 기능들을 살펴보고 사용법에 대해 알아봤습니다. 하지만 실무에서는 기본적인 CRUD기능만 있지는 않기 때문에, 필요한 함수들을 추가적으로 정의를 해야 합니다. 정의하는 방법은 다음 포스팅인 쿼리 메소드 기능에서 살펴볼게요😄

 

Spring Data JPA Series

1. Spring Data JPA와 기존 JPA의 차이점 및 사용법

2. Spring Data JPA 쿼리 메서드

3. Spring Data JPA 페이징 처리

4. Spring Data JPA Bulk Update & Entity Graph

 

Reference

실전! 스프링 데이터 JPA - 김영한

반응형