Back-end

[JPA] 양방향 매핑

yeobi 2024. 5. 13. 13:47

 

 

 

 

 

JpaMain

package hellojpa;

import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import jakarta.persistence.Persistence;

import java.util.List;

public class JpaMain {

    public static void main(String[] args) {

        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
        EntityManager em = emf.createEntityManager();

        EntityTransaction tx = em.getTransaction();
        tx.begin();	

        try {
            // 저장
            Team team = new Team();
            team.setName("TeamA");
            em.persist(team);


            Member member = new Member();   // member 객체를 생성한 뒤
            member.setUsername("memberA");  // 멤버 이름을 설정하고
//            member.changeTeam(team);  // 연관관계 편의 메서드(멤버의 팀을 지정)
            em.persist(member); // 영속성 컨텍스트에 집어넣기

            team.addMember(member); // 연관관계 편의 메서드(팀의 멤버를 지정)

//            team.getMembers().add(member);  // 순수 자바 객체를 고려해서 항상 양쪽에 값을 설정해야함



//            em.flush(); // 쓰기 지연 SQL 저장소에 있는 쿼리를 DB로 보내고
//            em.clear(); // 영속성 컨텍스트를 지우면 밑의 find query에서 select 문이 날라간다.



            tx.commit();    // 커밋을 하는 시점에 DB에 쿼리가 날라감
        }catch (Exception e){
            tx.rollback();
        }finally {
            em.close(); // 데이터베이스 커넥션을 물고 시작해서 닫아줘야 함
        }

        emf.close();
    }
}

 

 

 

Member Entity

package hellojpa;

import jakarta.persistence.*;

@Entity
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

//    @Column(name = "TEAM_ID")
//    private Long teamId;

    @ManyToOne
    @JoinColumn(name = "TEAM_ID")   // 외래키의 이름을 줌
    private Team team;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Team getTeam() {
        return team;
    }

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

    public void setTeam(Team team) {
        this.team = team;
    }
}

 

 

 

Team Entity

package hellojpa;

import jakarta.persistence.*;

import java.util.ArrayList;
import java.util.List;

@Entity
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 void addMember(Member member) {
        member.setTeam(this);   // 매개변수로 받은 멤버에 현재 객체의 팀으로 설정하고
        members.add(member);    // 멤버 리스트에 추가
    }


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Member> getMembers() {
        return members;
    }

    public void setMembers(List<Member> members) {
        this.members = members;
    }
}

 

 

JPA 사용시 주의점

toString의 Lombok을 Entity 양쪽에 쓰면 무한 로딩이 걸린다.

Controller에 Entity를 반환하면 JSON 형식으로 반환되지만 무한루프가 생길 위험이 있으며,

Entity를 API로 반환할 때 Entity는 변경가능하기 때문에 API 스펙 자체가 바뀔 수 있는 위험이 있다.

-> Entity는 RequestDTO나 ResponseDTO 형식으로 변경하여 반환해야 한다.

 

양방향 매핑 규칙

- 객체의 두 관계중 하나를 연관관계의 주인으로 지정

- 연관관계의 주인만이 외래 키를 관리(등록, 수정)

- 주인이 아닌 쪽은 읽기만 가능

- 주인은 mappedBy 속성 사용X

- 주인이 아니면 mappedBy 속성으로 주인 지정

 

양방향 매핑 정리

- 단방향 매핑만으로도 연관관계 매핑은 완료

- 양방향 매핑은 반대 방향으로 조회(객체 그래프 탐색) 기능이 추가된 것 뿐

- JPQL에서 역방향으로 탐색할 일이 많음

- 단방향 매핑을 잘 하고 양방향은 필요할 때 추가해도 됨(테이블에 영향을 주지 않음)

 

연관관계 주인을 정하는 기준

- 비즈니스 로직을 기준으로 연관관계의 주인을 선택하면 안 됨

- 연관관계의 주인은 외래 키의 위치를 기준으로 정해야함(외래키 있으면 주인)