본문 바로가기
Framework and Tool/JPA

JPA - 다양한 연관관계 - 1 : 1

by ocwokocw 2021. 7. 6.

- 참조: 자바 ORM 표준 JPA 프로그래밍

- 1 : 1 연관관계

1 : 1 관계는 양쪽이 서로 하나씩만 가지는 관계로 아주 심플한 다중성을 갖는다. 회원과 사물함이 있다고 할 때, 회원은 하나의 사물함만 소유할 수 있고 사물함도 회원 한명에 의해서만 소유될 수 있다고 한다면 1 : 1 관계이다.

 

다중성에서 항상 N 쪽이 외래키를 가진다고 하였는데, 1 : 1 에서는 어느쪽에 외래키를 두어도 되기 때문에 양방향일 경우 연관관계의 주인을 선택해야 한다. 주 테이블 또는 대상 테이블에 외래키를 둘 수 있다.

  • 주 테이블: 주 테이블에 대상 테이블을 참조하는 사상을 그대로 따라가기 때문에 어플리케이션 코드에 더 직관적이다.
  • 대상 테이블: DB 개발자들이 선호하는 방법이며 1 : 1 에서 1 : N 으로 변경시에도 구조를 그대로 유지할 수 있다.

아래 내용을 읽기전에 용어를 잘 생각하면서 읽어야 하는데 외래키가 있는 테이블이 주 테이블이라고 생각하면서 읽으면 혼돈이 온다. 여기서 주 테이블과 대상 테이블은 비즈니스 중요도측면에서 회원을 주 테이블로 사물함을 대상 테이블로 일컫는다.


- 주 테이블 외래키: 단방향

1 : 1 관계의 단방향에 대해 알아보자. 회원 엔티티는 User 이고 사물함 엔티티는 Locker 이며, 회원이 사물함을 단방향으로 참조한다고 가정한다.

위의 다이어그램에서 User 는 locker 속성으로 사물함인 Locker 를 참조하며 다중성은 1 : 1 이다. 이때 주 테이블 USER 에 외래키가 있다고 한다면 ER Diagram 은 아래와 같다.

USER 테이블에 외래키인 LOCKER_ID 가 있으며 LOCKER 테이블의 PK 를 참조한다.

위의 클래스다이어그램과 DB 테이블의 관계를 참조하여 JPA 맵핑을 하면 아래와 같다.

 

@Entity
public class Locker {

	@Id @GeneratedValue
	@Column(name = "LOCKER_ID")
	private Long id;
	
	private String name;

.............

@Entity
public class User {

	@Id @GeneratedValue
	@Column(name = "USER_ID")
	private Long id;
	
	private String userName;
	
	@OneToOne
	@JoinColumn(name = "LOCKER_ID")
	private Locker locker;

 

Locker 는 id 와 name 을 속성을 갖는다. User 에는 Locker 를 참조하는 locker 속성이 있다. 우선 다중성부터 생각해보면 1 : 1 이므로 @OneToOne 어노테이션을 사용하였다. User 에서 Locker 를 단방향으로 참조하고 User 에 외래키가 있으므로 @JoinColumn 어노테이션을 사용한다.


- 주 테이블 외래키: 양방향

이제 Locker 에도 User 참조를 추가하여 양방향을 만들어 보도록하자. 다이어그램에도 Locker 에서 User 를 참조하는 user 속성을 추가하고 방향성이 없앤다.

 

@Entity
public class Locker {

	@Id @GeneratedValue
	@Column(name = "LOCKER_ID")
	private Long id;
	
	private String name;

	@OneToOne(mappedBy = "locker")
	private User user;

 

Locker 코드가 User를 참조할 때 다중성은 1 : 1 이므로 @OneToOne 을 사용한다. 그리고 양방향이므로 User 와 Locker 중 연관관계의 주인을 설정해야 한다. User 에 외래키가 있으므로(연관관계 주인) Locker 엔티티의 User 참조 필드에는 mappedBy 속성을 이용해 User 엔티티의 Locker 필드명인 locker 문자열을 할당한다.

 

public void setLocker(Locker locker) {
	
	if(this.locker != null) {
		this.locker.setUser(null);
	}
	
	this.locker = locker;
	
	if(locker != null) {
		locker.setUser(this);
	}
}

 

양방향에서의 연관관계 편의 메소드도 잊지말고 작성해주도록 하자.


- 대상 테이블 외래키: 단방향

1 : 1 관계중 대상 테이블에 외래키가 있는 단방향 맵핑은 허용하지 않는다. 대상 테이블에 외래키가 있는 DR Diagram 을 먼저 그려보자.

주 테이블에서 USER 테이블에서 LOCKER_ID 를 참조하는 대신 대상 테이블인 LOCKER 에서 USER_ID 를 참조하도록 변경하였다.

이때 1 : 1 단방향으로 User 에서 Locker 를 참조하는 클래스 다이어그램은 위와 같다. 만약 코드를 작성한다면 아래와 같은 코드가 된다. 다중성은 1 : 1 이므로 @OneToOne 을 사용하여 User 에서 Locker 엔티티를 참조한다. 외래키는 Locker 에 있으므로 @JoinColumn 을 사용하지 않는다. 또한 양방향이 아니라서 연관관계의 주인은 없으므로 mappedBy 속성도 할당값은 없다.

 

@Entity
public class User {

	@Id @GeneratedValue
	@Column(name = "USER_ID")
	private Long id;
	
	private String userName;
	
	@OneToOne
	private Locker locker;

.......................

@Entity
public class Locker {

	@Id @GeneratedValue
	@Column(name = "LOCKER_ID")
	private Long id;
	
	private String name;

 

위와 같이 설정하고 실행하면 어떻게 될까? 책에서 대상 테이블에 단방향 맵핑을 허용하지 않는다고 했으니 실행이 되지 않을까? 어플리케이션을 실행하면 빌드도 이상없이 잘 된다.

 

public static void main(String[] args) {
	SpringApplication.run(JpaApplication.class, args);
	
	EntityManager em = emf.createEntityManager();

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

	Locker locker1 = new Locker();
	locker1.setName("Name#1");
	
	em.persist(locker1);
	
	User user1 = new User();
	user1.setUserName("User#1");
	user1.setLocker(locker1);
	
	em.persist(user1);
	
	tx.commit();
	em.close();
	
	emf.close();
}

 

위의 코드대로 Locker 를 생성하고 User 에게 locker 를 할당하여 두 엔티티 모두 영속시키고 H2 콘솔로 조회를 해보면 아래와 같이 나타난다.

USER 테이블에 LOCKER_LOCKER_ID 컬럼이 생겼다. 한번 생각해보자. @OneToOne 으로 User 엔티티에서 Locker 를 참조하였다. 그리고 외래키는 Locker 에 있다고 생각했으므로 @JoinColumn 을 생략하였다. 연관관계 맵핑 - 단방향 연관글의 내용을 다시한번 아래와 같은 문구가 있다.

 

  • @ManyToOne: 다중성을 알려준다. 예제에서는 N : 1 임을 알려주고 있다.
  • @JoinColumn(name = "TEAM_ID"): 외래 키를 맵핑할 때 사용한다. name 속성에는 맵핑할 컬럼을 적으면 된다. 생략하면 [필드명]_[참조하는 테이블의 컬럼명] 을 사용한다. 예제같은 경우 team_TEAM_ID 가 된다.

 

대상 테이블에 외래키를 두는 단방향 맵핑을 허용하지 않는다고 했지 실행시 오류가 난다고는 하지 않았다. 오류는 나진 않지만 LOCKER 에 외래키를 두면서 @OneToOne 으로 User 에서 Locker 를 단방향 참조할수는 없다. 만약 그렇게 하고 싶다면 단방향에서 양방향으로 변경해야 한다.


- 대상테이블 외래키: 양방향

대상 엔티티인 Locker 를 연관관계의 주인으로 설정하여 외래키를 두고 User 에서 Locker 를 참조하고 싶다면 양방향 맵피을 해야 한다. Locker 엔티티내의 User 참조 필드에 @JoinColumn 을 사용해준다. 그리고 주 엔티티인 User 에는 mappedBy를 이용해 연관관계의 주인인 Locker 에서 User를 참조하는 필드명 user 를 할당한다.

 

@Entity
public class Locker {

	@Id @GeneratedValue
	@Column(name = "LOCKER_ID")
	private Long id;
	
	private String name;
	
	@OneToOne
	@JoinColumn(name = "USER_ID")
	private User user;

...............

@Entity
public class User {

	@Id @GeneratedValue
	@Column(name = "USER_ID")
	private Long id;
	
	private String userName;
	
	@OneToOne(mappedBy = "user")
	private Locker locker;

 

실행하면 H2 Console 에서 아래와 같은 테이블 구조가 생성된다.

댓글