본문 바로가기
JPA/스프링 DATA JPA

Data JPA 02 - 예제 도메인 확인하기, 공통 인터페이스

by 킹차니 2022. 1. 4.

 

엔티티로 만들어보자.

 

import lombok.*;
import javax.persistence.*;

@ToString
@NoArgsConstructor(access= AccessLevel.PROTECTED)/*프록시 객체를 위해 protect로 기본 생성자*/
@Getter @Setter
@Entity
public class Member {

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

    @ToString.Exclude//연관관계 필드는 toString을 안하는 것이 좋다.
    @JoinColumn(name="team_id")
    @ManyToOne(fetch=FetchType.LAZY)
    private Team team;

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

    //연관관계 메서드
    public void changeTeam(Team team) {
        this.team = team;
        team.getMembers().add(this);
    }

    public Member(String username) {
        this.username = username;
    }
}
import lombok.*;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@ToString
@NoArgsConstructor(access= AccessLevel.PROTECTED)/*프록시 객체를 위해 protect로 기본 생성자*/
@Getter @Setter
@Entity
public class Team {
    @Id @GeneratedValue
    @Column(name="team_id")
    private Long id;
    private String name;

    @ToString.Exclude
    @OneToMany(mappedBy="team", cascade=CascadeType.ALL)
    private List<Member> members = new ArrayList<>();

    public Team(String name) {
        this.name = name;
    }
}

 

 

Member, Team에 대해서 JPA를 사용한 기본적인 CRUD가 가능한 리퍼지토리를 만들어보자.

 

MemberJPARepository

import org.springframework.stereotype.Repository;
import study.datajpa.entity.Member;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.Optional;

@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 void delete(Member member){
        em.remove(member);
    }

    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 List<Member> findAll(){
        return em.createQuery("select m from Member m", Member.class).getResultList();
    }
}

 

TeamJPARepository

import org.springframework.stereotype.Repository;
import study.datajpa.entity.Team;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import java.util.Optional;

@Repository
public class TeamJPARepository {
    @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) {
        return Optional.ofNullable(em.find(Team.class, id));
    }

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

 

그런데 유심히 위의 CRUD메서드들을 보면 MemberRepository, TeamRepository 모두 Member타입이냐 Team타입이냐만 다르지 거의 같은 로직들을 가지고 있는 것을 볼 수 있다. 즉 많은 양의 코드들이 중복되고 있는 것이다. 
하여 과거에 이를 제네릭을 도입하여 해결을 시도하는 등 수많은 도전이 있었다고 한다. 하지만 어떤 개발자가 인터페이스로 해결하였는데, 그것이 바로 Data JPA가 되었다.

 


 

 

MemberRepository, TeamRepository를 Data JPA를 사용하여 만들기 위해서는 아래와 같이 하면 된다.

import org.springframework.data.jpa.repository.JpaRepository;
import study.datajpa.entity.Member;

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

 

import org.springframework.data.jpa.repository.JpaRepository;
import study.datajpa.entity.Team;

public interface TeamRepository extends JpaRepository<Team, Long> {
}

각각의 Repository는 누군가가 만들어놓은 JpaRepository<T, ID>를 extends하기만 하면 된다.

 

그런데 어떻게 이러한 텅텅빈 Repository를 빈객체로 생성하여 의존성을 주입받는지가 의문점이다. 
이는 아래 그림과 같이 스프링이 직접 만들어 주기 때문이다. 즉 개발자가 직접 Repository를 구현하고 만들 필요없이, 스프링이 타입을 보고 그에 대한 Repository객체를 만들어서 의존성 주입을 해주는 것이다.

 

JPARepository는 아래와 같이 다양한 메서드들을 제공한다.

 

주요 메서드:

• save(S) : 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.
delete(T) : 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove() 호출
findById(ID) : 엔티티 하나를 조회한다. 내부에서 EntityManager.find() 호출
getOne(ID) : 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference() 호출

findAll(...) : 모든 엔티티를 조회한다. (정렬( Sort )이나 페이징( Pageable ) 조건을 파라미터로 제공할 수 있다. )

 

 

그런데.. 만약 username으로 Member를 find하는 메서드를 제공하고 싶다면?? 이러한 메서드는 Data JPA 인터페이스가 제공하지 않으므로 이를 직접 구현해야할까?

 

그럴필요 없다. 아래처럼 추상 메서드를 정의해주면 알아서 만들어준다....

import org.springframework.data.jpa.repository.JpaRepository;
import study.datajpa.entity.Member;

public interface MemberRepository extends JpaRepository<Member, Long> {
    Member findByUsername(String username);
}

도대체 이게 어떻게 된 일인지는 다음장에서 알아보자

 

 

 

 

김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.