인프런/실전! 스프링 데이터 JPA

1) 예제 도메인 모델, 공통 인터페이스 기능 소개

backend dev 2024. 6. 11.

멤버에서 팀은 다대일

팀에서 멤버스[컬렉션]은 일대다

양방향 연관관계

 

 

@Entity
@Getter @Setter
// JPA가 프록시 기술같은걸 사용할때 기본생성자가 필요하다, private로 해두면 JPA가 사용할수없으므로 Protected로 해둔다.
// [개발자가 실수로 사용하는것을 막기위해 protected로 제한 ]
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(of ={"id","username","age"} ) //  id, username, age 필드만 포함하여 toString() 메서드를 생성하라는 것, team이 포함되면 무한참조순환이 되므로 뺴준것
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 = 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);
    }
}

 

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

}

 

@Setter: 실무에서 가급적 Setter는 사용하지 않기 [ 메소드생성해서 사용해야함.]

 

@NoArgsConstructor AccessLevel.PROTECTED: 기본 생성자 막고 싶은데, JPA 스팩상 PROTECTED로 열어두어야 함

 

@ToString은 가급적 내부 필드만(연관관계 없는 필드만)

 

changeTeam() 으로 양방향 연관관계 한번에 처리(연관관계 편의 메소드)

 

 

Member와 Team은 양방향 연관관계,

Member.team 이 연관관계의 주인,

Team.members 는 연관관계의 주인이 아님,

따라서 Member.team이 데이터베이스 외래키 값을 변경, 반대편은 읽기만 가능


스프링 데이터 jpa 공통 인터페이스 소개 및 활용 전에 순수 JPA기반 리포지토리를 만들어보고

데이터 jpa 인터페이스 해보는 순서를 가진다.

순수 JPA 기반 리포지토리 만들기

기본 CRUD

저장

변경 -> 변경감지 사용

삭제

전체 조회

단건 조회

카운트

 

 

JPA에서 수정은 변경감지 기능을 사용하면 된다.

트랜잭션 안에서 엔티티를 조회한 다음에 데이터를 변경하면, 트랜잭션 종료 시점에 변경감지 기능이 작동해서

변경된 엔티티를 감지하고 UPDATE SQL을 실행한다.

 

@Repository
@RequiredArgsConstructor
public class MemberJpaRepository {

    private final EntityManager em;

    public Member save(Member member) { // 커맨드와 쿼리를 분리해야하기에 반환은 id가 맞다. 하지만 여기는 테스트용이므로 이렇게
        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); // null 이면 비어있는 optional, 아니면 객체가 담겨있는 optional 반환
    }

    public Long count() {
        return em.createQuery("select count(m) from Member m",Long.class).getSingleResult();
    }
}
@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(*) from Team",Long.class).getSingleResult();
    }


}

 


공통 인터페이스 설정

 

스프링 데이터 JPA가 구현 클래스 대신 생성

 

org.springframework.data.repository.Repository 를 구현한 클래스는 스캔 대상

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

그걸 구현한 클래스는 구현체를 구현해서 넣어준다.

실제주입된것을 출력해본결과

@Repository 애노테이션 생략 가능

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

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

 

 

공통 인터페이스 분석

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

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

현재는 바뀌었다. 아래 기준

 

스프링 데이터 JPA

package를 보면 data.jpa.repository

jpa에 특화된 기능 제공

package org.springframework.data.jpa.repository;

@NoRepositoryBean
public interface JpaRepository<T, ID> extends ListCrudRepository<T, ID>, ListPagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

 

package org.springframework.data.repository;

@NoRepositoryBean
public interface ListPagingAndSortingRepository<T, ID> extends PagingAndSortingRepository<T, ID> {

 

 

스프링 데이터

package를 보면 data.repository

common이라는 공통부분, 어떤 db에서도 가능한 공통기능 제공

package org.springframework.data.repository;

import java.util.List;

@NoRepositoryBean
public interface ListCrudRepository<T, ID> extends CrudRepository<T, ID> {

 

package org.springframework.data.repository;

import java.util.Optional;

@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {

 

 

 

 

 

제네릭 타입

JpaRepository<T, ID>의 일부분을 보면

void flush();

<S extends T> S saveAndFlush(S entity);

<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);

/** @deprecated */
@Deprecated
default void deleteInBatch(Iterable<T> entities) {
    this.deleteAllInBatch(entities);
}

void deleteAllInBatch(Iterable<T> entities);

void deleteAllByIdInBatch(Iterable<ID> ids);

제네릭 타입이 많다.

T : 엔티티

ID : 엔티티의 식별자 타입

S : 엔티티와 그 자식 타입

 

주요 메서드

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

 

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

 

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

 

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

 

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

 

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

댓글