Back-End/JPA

5. 연관관계 매핑 기초

wisdom11 2022. 4. 23. 17:25

1. 단방향 연관관계

다대일 연관관계

위와 같은 다대일 단방향 연관관계가 있을 때는 2가지의 어노테이션을 통해 다음과 같이 구현이 가능하다.

@Entity
public class Member {
	@Id @Column(name = "MEMBER_ID")
	private String id;

	private Stirng username;

	//연관관계 매핑
	@ManyToOne
	@JoinColumn(name = "TEAM_ID")
	private Team team;

	//연관관계 설정
	public void setTeam(Team team){
		this.team = team;
	}
	
	...
}
@Entity
public class Team {
	@Id @Column(name = "TEAM_ID")
	private String id;

	private Stirng name;
	
	...
}

연관관계를 나타내기 위해 사용된 어노테이션을 알아보자.

@ManyToOne

  • N:1 관계
속성 기능 기본값
optional false - 연관된 엔티티가 항상 있어야 한다. true
fetch 글로벌 패치 전략을 설정한다. → 8장 @XXXToOne = FetchType.EAGER
@XXXToMany = FetchType.LAZY
cascade 영속성 전이 기능을 사용한다. → 8장  
targetEntity 거의 사용하지 않는다.
연관된 엔티티의 타입 정보를 설정한다.
 

 

@JoinColumn

  • 외래키를 매핑한다.
  • 생략 가능하다. → 생략 시, ‘필드명_참조하는 테이블의 컬럼명’으로 외래 키를 찾는다.
속성 기능 기본값
name 매핑할 외래키 이름 필드명_참조하는 테이블의 컬럼명
referencedColumnName 외래키가 참조하는 대상 테이블의 컬럼명 참조하는 테이블의 기본키 컬럼명
foreignKey (DDL) 외래키 제약조건을 직접 지정한다.
테이블 생성 시에만 사용한다.
 
unique
nullable
insertable
updatable
columnDefinition
table
@Column 속성과 동일  

 

2. 연관관계 사용

등록, 수정, 삭제, 조회하는 예제를 통해 연관관계를 어떻게 사용하는지 알아보자.

저장

예시 코드)
public void teamSave() {
      //팀1 저장
      Team team1 = new Team("team1", "팀1");
      em.persist(team1);

      //회원1 저장
      Member member1 = new Member("member1", "회원1");
      member1.setTeam(team1); //연관관계 설정 member1 -> team1
      em.persist(member1);

      //회원2 저장
      Member member2 = new Member("member2", "회원2");
      member2.setTeam(team1); //연관관계 설정 member2 -> team1
      em.persist(member2);
  }

JPA는 참조한 팀의 식별자를 외래 키로 사용해서 적절한 등록 쿼리를 생성한다.
예시 코드를 통해 실행된 SQL은 다음과 같다.

INSERT INTO TEAM (TEAM_ID, NAME) VALUES ('team1', '팀1');
INSERT INTO MEMBER (MEMBER_ID, NAME, TEAM_ID) VALUES ('member1', '회원1', 'team1');
INSERT INTO MEMBER (MEMBER_ID, NAME, TEAM_ID) VALUES ('member2', '회원2', 'team1');

 

조회

연관관계가 있는 엔티티를 조회하는 방법 (2가지)
  • 객체 그래프 탐색
  • 객체지향 쿼리 사용 (JPQL)

 

객체 그래프 탐색

Member member = em.find(Member.class, "member1");
Team team = member.getTeam(); //객체 그래프 탐색

 

객체지향 쿼리 사용 (JPQL)

연관된 테이블을 조인해서 검색조건을 사용하면 된다.
String jpql = "select m from Member m join m.team t where " + "t.name=:teamName";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setParameter("teamName", "팀1")
.getResultList();
참고) ‘:’로 시작하는 것은 파라미터를 바인딩받는 문법이다.

 

실행된 SQL은 다음과 같다.
SELECT M.* FROM MEMBER MEMBER
INNER JOIN TEAM TEAM ON MEMBER.TEAM_ID = TEAM1_.ID
WHERE TEAM1_.NAME='팀1'

 

수정

//팀2 저장
Team team2 = new Team("team2", "팀2");
em.persist(team2);

//회원1에 새로운 팀2 설정
Member member = em.find(Member.class, "member1");
member.setTeam(team2);
UPDATE MEMBER
SET TEAM_ID='team2', ...
WHERE ID='member1'

수정은 em.update() 같은 메소드가 없다.
불러온 엔티티의 값을 변경하기만 하면 트랜잭션 커밋 시 플러시가 일어나면서 변경 감지 기능이 작동한다.

 

연관관계 제거

연관관계를 null로 설정하면 된다.
Member member1 = em.find(Member.class, "member1");
member1.setTeam(null);
UPDATE MEMBER
SET TEAM_ID=null, ...
WHERE ID='member1'

 

연관된 엔티티 삭제

기존에 있던 연관관계를 먼저 제거하고 연관된 엔티티를 삭제해야 한다.
그렇지 않으면 외래키 제약조건으로 인해, 데이터베이스에 오류가 발생한다.

member1.setTeam(null);
member2.setTeam(null);
em.remove(team);

 

3. 양방향 연관관계

이번에는 반대 방향인 팀에서 회원으로 접근하는 관계를 추가하여 아래와 같은 연관관계를 만들어주자.

양방향 객체 연관관계

팀 엔티티에 다음과 같이 코드를 추가하여 양방향 매핑을 할 수 있다.

@Entity
public class Team {
    @Id 
    @Column(name = "TEAM_ID")
    private String idl

    private String name;
    
    // 새롭게 추가된 부분
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<Member>();
    
    ...
}

 

4. 연관관계의 주인

양방향 연관관계 매핑 시, 두 연관관계 중 하나를 연관관계의 주인으로 정해야한다.
연관관계의 주인과 주인이 아닌 쪽의 특징은 다음과 같다.

연관관계의 주인 (owner)

  • 외래 키가 있는 곳, @ManyToOne (N)
  • 외래 키 관리자
  • 외래 키를 관리(등록, 수정, 삭제)할 수 있다.
 

주인이 아닌 쪽 (non-owning side)

  • @OneToMany (1)
  • mappedBy 속성을 사용한다. (반대쪽 매핑 필드의 이름을 넣어준다)
  • 외래 키를 읽기만 할 수 있다.

 

5. 양방향 연관관계 저장

양방향 연관관계를 저장하는 코드도 단방향 연관관계에서 살펴본 코드와 완전히 동일하다.

public void teamSave() {
      //팀1 저장
      Team team1 = new Team("team1", "팀1");
      em.persist(team1);

      //회원1 저장
      Member member1 = new Member("member1", "회원1");
      member1.setTeam(team1); //연관관계 설정 member1 -> team1
      em.persist(member1);

      //회원2 저장
      Member member2 = new Member("member2", "회원2");
      member2.setTeam(team1); //연관관계 설정 member2 -> team1
      em.persist(member2);
  }

양방향 연관관계는 연관관계의 주인이 외래 키를 관리한다.
따라서 주인이 아닌 방향은 값을 설정하지 않아도 데이터베이스에 외래 키 값이 정상 입력된다.
그렇기 때문에 Member.team만 설정해주어도 잘 저장이 된다.

 

6. 양방향 연관관계의 주의점

순수한 객체까지 고려한 양방향 연관관계

위에서 연관관계의 주인이 아닌 방향은 값을 설정하지 않아도 정상적으로 데이터베이스에 값이 저장된다고 했다.
그렇지만 객체 관점에서는, 양쪽 방향에 모두 값을 입력해주는 것이 안전하다.
순수한 객체 상태에서도 동작할 수 있도록 객체의 양방향 연관관계는 양쪽 모두 관계를 맺어주자!

 

연관관계 편의 메소드

양방향 연관관계는 결국 양쪽 다 신경써야 한다.
이때 양방향 연관관계를 모두 설정하도록 메소드를 작성하면 실수를 줄일 수 있다.

public class Memeber {
    private Team team;
	
    /* 연관관계 편의 메소드 */
    public void setTeam(Team team) {
    	
        // 기존 팀과 관계를 제거
    	if(this.team != null) {
        	this.team.getMembers().remove(this);
        }
        this.team = team;
        team.getMembers().add(this);
    }
    ...
}

이렇게 한 번에 양방향 연관관계를 설정하는 메소드를 연관관계 편의 메소드라고 한다.

 

 

728x90