☕️자바 𝗝𝗔𝗩𝗔

[JPA] Entity 연관관계 매핑 (연관관계의 주인)

비타민찌 2022. 9. 21. 15:43
728x90

패러다임의 불일치

'객체지향 프로그래밍' 은 추상화, 캡슐화, 정보은닉, 상속, 다형성 등 시스템의 복잡성을 제어할 수 있는 다양한 정치들을 제공한다. 이에 반해 '관계형 데이터베이스'는 추상화, 상속 다형성 같은 개념이 없다. 데이터 중심으로 구조화되어 있고 집합적인 사고가 필요하다. 이러한 객체와 관계형 데이터베이스의 패러다임 불일치 문제를 해결하는데 많은 시간과 코드가 소비된다.

 

 

연관관계

객체는 참조를 사용해서 다른 객체와 연관관계를 가지고 (참조로 연관된 객체를 조회)

테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가진다. (join으로 연관된 테이블 조회)

 

객체를 테이블에 맞춰 모델링?

그럼 객체를, 테이블과 똑같이 모델링하면 어떻게 될까? 예시를 하나 들어본다.

데이터베이스에는 아래 그림과 같이 team과 member가 1대다 연관관계를 가지면서, member의 team_id가 foreign key로 등록된다.

이를 코드로 옮기면 다음과 같다.

@Entity
public class Member {

	@Id @GeneratedValue
	private Long id;

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

    //   Member 모델에 teamId 만들어줌
    @Column(name = "TEAM_ID")
	private Long teamId; 

        ...

}
@}Entity
public class Team {

    @Id @GeneratedValue 
    private Long id;
    
    private String name; 
    ...
    
 }

여기서 member의 team을 조회해보자.

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

// 회원 저장
Member member = new Member();

member.setName("member1");
 
member.setTeamId(team.getId());
 
em.persist(member);


// 멤버의 팀 조회 
Member findMember = em.find(Member.class, member.getId());
Team findTeam = em.find(Team.class, findMember.getTeamId());

findTeam을 할 때 참조를 바로 이용하는 게 아니라, 저장된 teamId를 이용해서 id값에 맞는 팀을 조회해와야 한다.

즉, 관계형 데이터베이스 방식에 맞추면 Member 객체와 연관된 Team 객체를 참조를 통해서 조회할 수 없다.

또한 결국 객체지향의 장점을 잃어버리고 만다.

 

테이블과 똑같이 모델링했음에도 이렇게 달라지는 이유, 이러한 패러다임이 생기는 이유는 테이블은 외래 키(Fk)로 조인을 해서 연관 테이블을 찾아오지만 객체는 참조를 해서 연관 객체를 찾아와야 하는 차이 때문이다.

객체지향적으로 모델링하기 위해서는 아래와 같이 Member안에 team이라는 객체 가 들어있어야 참조를 바로 할 수 있다.

 

JPA와 연관관계

JPA는 연관관계와 관련한 패러다임 불일치 문제를 해결해준다.👍🏻

어떻게 해결하나면..

아래와 같이 Member 클래스 안에 team을 만들고,

@ManyToOne

@JoinColumn(name = "TEAM_ID")

private Team team;

@ManyToOne 어노테이션을 붙여 Member 입장에서 연관관계를 맺어준다.(다대일)

@JoinColumn으로 TEAM_ID를 넣어줘서 조인할 때 FK로 쓸 값을 정해준다.

 

이제 setTeam을 통해 team객체를 넘겨주면 알아서 teamId를 찾아 FK값으로 쓰고 연관관계를 설정해준다.

//팀 저장
Team team = new Team();

team.setName("TeamA");
 
em.persist(team);



//회원 저장
Member member = new Member();
 
member.setName("member1");

member.setTeam(team); //단방향 연관관계 설정, 참조 저장
 
em.persist(member);

 

이제 아래와 같이 team을 getTeam을 이용해 바로 참조로 가져올 수 있다.

Team findTeam = findMember.getTeam();

이렇게 객체에서 member가 소속된 팀을 조회할 때 참조를 이용해 연관된 team을 찾는 것을 객체 그래프 탐색이라 한다.

(또 다른 방법으로 객체지향 쿼리를 사용하는 JPQL도 있다.)

 

SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있을지 정해지는데,

JPA는 연관된 객체를 사용하는 시점에 적절한 SELECT 쿼리를 날릴 수 있다. 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미루는 지연 로딩이다.

// 처음 조회 시점에 SELECT 쿼리
Member member = jpa.find(Member.class, memberId);

Order order = member.getOrder();
order.getOrderDate();   // Order를 사용하는 시점에 SELECT ORDER

 

이번에는 team에서 member를 참조해보자.

이렇게 되면 team도 member를 참조하고, member에서도 team을 참조할 수 있으니 양방향 객체 연관관계가 된다.

Team에 members 리스트를 추가하고, team의 입장에서 @OneToMany 어노테이션을 붙여준 뒤 mappedBy에는 연관될 객체 자체를 넣어준다.

@Entity

public class Team {


	private String name;



	@Id @GeneratedValue
 
    	private Long id;

        
	@OneToMany(mappedBy = "team")

	List<Member> members = new ArrayList<Member>();
    
 }

Team클래스의 team이 연관될 객체이기 때문에 이렇게 넣어주면 된다. 이러면 team.getMembers();으로 역참조가 가능해진다.

 

JPA와 연관관계의 주인

두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야 하는데 이것을 연관관계의 주인이라고 한다.

연관관계의 주인만이 외래 키를 관리할 수 있게 하는 것인데, 다시 말해 연관관계의 주인을 정한다는 말은 외래 키 관리자를 선택한다는 말이다. 주인 쪽만 연관관계를 등록하고 수정할 수 있다. 주인이 아닌 쪽은 읽기만 가능하다. (여기서는 Member가 주인)

그래서 아래와 같이 한다면 연관관계가 생성되지 않는다.

Team team = new Team();
 
team.setName("TeamA");
 
em.persist(team);



Member member = new Member();
 
member.setName("member1");





//역방향(주인이 아닌 방향)만 연관관계 설정
 
team.getMembers().add(member);


em.persist(member);

아래와 같이 주인 쪽에서 연관관계를 생성하는 코드를 꼭 추가해줘야 한다.

member.setTeam(team);

 

연관관계의 주인은 실제 테이블에서 외래 키가 생성되는 객체로 선택하면 된다. 코드로는 주인이 아닌 쪽에 mappedBy를 적어주면 된다.

 

 

 

출처

 

728x90