Programming/Spring

Spring Data JPA - 공통 인터페이스 기능

잇(IT) 2023. 7. 24. 20:04

- Member (Entity)

@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
//매개변수가 없는 생성자 생성과 동시에 접근 지정자를 PROTECTED로 설정한다.

@ToString(of = {"id", "username", "age"})
//ToString에 범위를 지정해주지 않으면 양방향 연관관계에 의해 
//무한 루프가 발생할 수 있기 때문에 필요한 정보만 입력한다.

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) {
        this(username, 0);
    }
    
    public Member(String username, int age) {
        this(username, age, null);
    }
    
    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);
    }
}

 

- Team (Entity)

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

 

- JPA에서 Entity를 사용할 때는 기본적으로 기본생성자가 필요하고 접근 지정자를 protected 이상으로 설정하면 안된다.


- 공통 인터페이스 기능

1. 순수 JPA 기반 리포지토리

2. 스프링 데이터 JPA 공통 인터페이스


- 순수 JPA 기반 리포지토리

- MemberJpaRepository

@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 List<Member> findAll() {
        //JPQL
        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);
    }

- JPA 기본적인 메서드를 제공하지만 기본적으로 제공하지 않는 메서드에 대해서 JPQL을 사용할 수 있다.

- findAll의 경우 JPQL을 이용하여 DB로부터 Member 엔티티들을 List로 반환한다.

 

    public Optional<Member> findById(Long id) {
        Member member = em.find(Member.class, id);
        return Optional.ofNullable(member);
    }

- 위 코드의 경우 해당 id에 해당하는 member 엔티티가 없을 경우 Null로 반환하기 위해 작성하는 메서드이다.

- Optional은 Null 체크가 필요한 경우 사용한다.

 

- TeamRepository

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

- JPA는 기본적으로 트랜잭션을 통해 동작한다. 또한 트랜잭션 내에서 엔티티의 동일성을 보장한다.


- @EnableJpaRepositories는 Spring에서 JpaRepository 인터페이스를 스캔하고, 해당 인터페이스를 구현한 Bean을 자동으로 생성하도록 도와준다. 이렇게 생성된 JpaRepository 구현체는 Spring Data JPA에 의해 자동으로 관리되고, 데이터베이스와의 상호 작용을 지원한다.

- 위와 같은 방법을 통해 Spring Data JPA는 인터페이스만 구현되어 있어도 스프링이 자동으로 구현체를 등록해주기 때문에 메서드를 사용할 수 있는 것이다.

 

- @Repository 애노테이션 생략 가능

1. 컴포넌트 스캔을 스프링 데이터 JPA가 자동으로 처리한다.

2. JPA 예외를 스프링 예외로 변환하는 과정도 자동으로 처리한다.

 

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

1. 첫번째 제네릭 타입은 JpaRepository가 다루는 엔티티 타입을 나타낸다. 위 코드에서 Member는 JpaRepository가 제공하는 기본적인 CRUD 메서드들을 Member 엔티티 클래스에 적용하도록 해준다.

2. 두번째 제네릭 타입은 (Long) 엔티티의 식별자 타입을 나타낸다. Member 엔티티의 식별자 (@Id) 타입이 Long이라는 의미다.

 

- MemberRepository Test 코드

@SpringBootTest
@Transactional
@Rollback(value = false)
//Test 코드의 경우 트랜잭션이 끝나게 되면 자동 롤백이 되어 과정을 볼수 없기 때문에
//Rollback false를 통해 로그를 통해 확인 할 수 있도록 변경한 것이다.
class MemberRepositoryTest {

    @Autowired
    MemberRepository memberRepository;
    @Autowired
    TeamRepository teamRepository;

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

    }

    @Test
    public void basicCRUD() {
        Member member1 = new Member("member1");
        Member member2 = new Member("member2");
        memberRepository.save(member1);
        memberRepository.save(member2);

        //단건 조회 검증
        Member findMember1 = memberRepository.findById(member1.getId()).get();
        Member findMember2 = memberRepository.findById(member2.getId()).get();
        assertThat(findMember1).isEqualTo(member1);
        assertThat(findMember2).isEqualTo(member2);

        //리스트 조회 검증
        List<Member> all = memberRepository.findAll();
        assertThat(all.size()).isEqualTo(2);

        //카운트 검증
        Long count = memberRepository.count();
        assertThat(count).isEqualTo(2);

        //삭제 검증
        memberRepository.delete(member1);
        memberRepository.delete(member2);

        Long deletedCount = memberRepository.count();
        assertThat(deletedCount).isEqualTo(0);
    }

- 구현체를 별도로 작성하지 않아도 Spring이 자동으로 설정해주는 구현체에 의해 메서드들이 동작하고 쿼리도 JpaRepository와 동일하게 동작하는 것을 확인 할 수 있다.

 

1. JpaRepository 인터페이스: 공통 CRUD 제공

2. 제네릭은 <엔티티 타입, 식별자 타입> 설정


- 주의

1. T findOne(ID) Optional findById(ID) 변경

2. boolean exists(ID) boolean existsById(ID) 변경

 

- 제네릭 타입

1. T : 엔티티

2. ID : 엔티티의 식별자 타입

3. S : 엔티티와 그 자식 타입

 

- 주요 메서드

1. save(S) : 새로운 엔티티는 저장하고 이미 있는 엔티티는 병합한다.

2. delete(T) : 엔티티 하나를 삭제한다. 내부에서 EntityManager.remove() 호출

3. findById(ID) : 엔티티 하나를 조회한다. 내부에서 EntityManager.find() 호출

4. getOne(ID) : 엔티티를 프록시로 조회한다. 내부에서 EntityManager.getReference() 호출

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

 

- JpaRepository 는 대부분의 공통 메서드를 제공한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 : 인프런 - 김영한(실전! 스프링 데이터 JPA)

 

728x90