JPA - 일대일 연관관계 (1)

이유석·2023년 1월 11일
4

JPA - Entity

목록 보기
7/14
post-thumbnail

일대일 연관관계를 회원(Member)과 해당 회원의 사물함(Locker)을 예시로 설명해보도록 하겠습니다.

  • 회원과 사물함이 있다.
  • 회원은 하나의 사물함을 소유할 수 있다.
  • 사물함은 한명의 회원에 의해서만 사용된다.
  • 즉, 회원과 사물함은 일대일(1:1)의 관계입니다.

일대일 연관관계

  • 일대일 (1:1) 관계는 양쪽이 서로 하나의 관계만 갖습니다.

    • 예) 회원은 하나의 사물함만 갖고, 사물함도 한명의 회원에 의해서만 사용됩니다.
  • 일대일 관계에는 주 테이블대상 테이블이 관계를 맺습니다.

    • 주 테이블 : 해당 관계에서 주체가 될 수 있는 대상 (회원)
    • 대상 테이블 : 해당 관계에서의 주체에 의해 관계가 맺어지는 대상 (사물함)
  • 일대일 관계는 주 테이블이나 대상 테이블 둘 중 어느 곳이나 외래키를 가질 수 있습니다.
    • 주 테이블에 외래키 저장
      - 객체지향 개발자들이 선호합니다.
      - 외래 키에 해당하는 대상 테이블을 객체 참조와 비슷하게 사용할 수 있기 때문입니다.
    • 대상 테이블에 외래키 저장
      - 데이터베이스 개발자들이 선호합니다.
      - 테이블 관계를 일대일 (1:1)에서 일대다 (1:N)으로 변경할 때, 테이블 구조를 그대로 유지할 수 있습니다.

주 테이블에 외래키, 단방향

객체 및 테이블 모델링

  • 회원 객체와 사물함 객체는 단방향 관계입니다.
  • 주 테이블인 회원 객체(Member)는 Member.locker 필드를 통해서 회원이 소유한 사물함 객체(Locker)에 접근할 수 있습니다.
  • 대상 테이블인 사물함 객체(Locker)는 해당 사물함을 소유하고 있는 회원 객체(Member)에 접근할 수 없습니다.

위 관계를 통해서 객체 및 테이블 모델링을 한 결과는 아래와 같습니다.

  • 테이블 연관관계에서 주 테이블에 저장된 외래키에 UNIQUE 제약조건이 추가된 것을 확인할 수 있습니다.

객체 관계 매핑

해당 객체 모델링을 코드로 나타내어 보도록 하겠습니다.

코드 설명

Member 클래스 (일대일 에서 주 테이블에 해당합니다.)

public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
    
    @Column(name = "USERNAME")
    private String username;

	// Getter, Setter, Constructor
}

Locker 클래스 (일대일 에서 대상 테이블에 해당합니다.)

public class Locker {

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

    @Column(name = "NAME")
    private String name;
    
    // Getter, Setter, Constructor
}

주 테이블에 외래키를 저장하는 단방향 관계일 경우, 주 테이블에 해당하는 클래스에 대상 테이블에 해당하는 클래스를 참조 필드로 작성해주시면 됩니다.
이때, 해당 참조 필드위에 OneToOne@JoinColumn(name = "외래키 이름")을 추가하여 줍니다.

@JoinColumn

외래키를 매핑할 때 사용합니다.
자세한 속성은 JPA 단방향 연관관계 에 있는 설명과 동일합니다.

JoinColumn 어노테이션은 생략 가능합니다.
@JoinColumn을 생략하면 외래 키를 찾을 때 기본 전략을 사용합니다.
기본 전략 : 필드명 + _ + 참조하는 테이블의 컬럼명

@OneToOne

일대일 관계에서 사용합니다.

속성기능기본값
optionalfalse로 설정하면 연관된 엔티티가 항상 있어야 한다true
mappedBy연관관계의 주인 필드를 선택한다.
양방향 매핑시 사용됩니다.
fetch글로벌 패치 전략을 설정한다.
(자세한 내용은 추후에)
@OneToOne=FetchType.EAGER (즉시 로딩)
cascade영속성 전이 기능을 사용한다.
(자세한 내용은 추후에)
orphanRemovaltrue 로 설정 시, 고아 객체를 즉시 삭제합니다. false (고아 객체를 삭제하지 않습니다.)
targetEntity연관된 엔티티의 타입 정보를 설정한다.
이 기능은 거의 사용하지 않음

연관관계 사용

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

저장

// 사물함 저장
Locker locker = new Locker(0L, "사물함");
entityManager.persist(locker);

// 회원 저장
Member member = new Member(0L, "회원");
member.setLocker(locker); // 연관관계 설정 
entityManager.persist(member);
  • JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태이어야 합니다.

  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.

INSERT INTO LOCKER (LOCKER_ID, NAME) VALUES (0, "사물함");
INSERT INTO MEMBER (MEMBER_ID, USERNAME, LOCKER_ID) VALUES (0, "회원", 0);

조회

연관관계가 있는 엔티티를 조회하는 방법은 크게 2가지가 있습니다.

객체그래프 탐색
member.getLocker() 을 사용해서 member 와 연관된 locker 엔티티를 조회할 수 있습니다.

Member member = entityManager.find(Member.class, 0L);
Locker locker = member.getLocker();

객체지향 쿼리 (JPQL) 사용

String jpql = "select m.locker from Member m where m.id = :memberId";

Locker locker = entityManager
	.createQuery(jpql, Locker.class)
    .setParameter("memberId", 0L)
    .getSingleResult();
  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
SELECT l.LOCKER_ID, l.NAME
FROM LOCKER l
	JOIN MEMBER m ON l.LOCKER_ID = m.LOCKER_ID
WHERE m.MEMBER_ID = 0;

수정

회원이 소유하고 있던 사물함을 신규 사물함으로 변경해보도록 하겠습니다.

Locker locker = new Locker(1L, "신규 사물함");
entityManager.persist(locker);

Member member = entityManager.find(Member.class, 0L);
member.setLocker(locker);
  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
UPDATE MEMBER
SET LOCKER_ID = 1, ...
WHERE
	MEMBER_ID = 0;

연관관계 제거

회원의 사물함 기간이 종료되어, 소유하고 있던 사물함과의 연관관계를 제거해보도록 하겠습니다.

Member member = entityManager.find(Member.class, 0L);
member.setLocker(null);
  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
UPDATE MEMBER
SET LOCKER_ID = null, ...
WHERE
	MEMBER_ID = 0;

삭제 (orphanRemoval = false)

회원이 서비스를 탈퇴하여 회원 객체를 삭제해보도록 하겠습니다.

Member member = entityManager.find(Member.class, 0L);
entityManager.remove(member);
  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
DELETE
FROM MEMBER
WHERE MEMBER_ID = 0;

orphanRemoval 을 true 로 설정 시, 회원 객체를 삭제할 때 고아 객체가 되는 사물함 객체 역시 삭제가 됩니다.

소스코드

주 테이블 외래키, 양방향

객체 및 테이블 모델링

  • 회원 객체와 사물함 객체는 양방향 관계입니다.
  • 주 테이블인 회원 객체(Member)는 Member.locker 필드를 통해서 회원이 소유한 사물함 객체(Locker)에 접근할 수 있습니다.
  • 대상 테이블인 사물함 객체(Locker)는 해당 사물함을 소유하고 있는 회원 객체(Member)에 Locker.member 필드를 통해서 접근할 수 있습니다. 이때, 해당 member 필드는 읽기 전용입니다.

위 관계를 통해서 객체 및 테이블 모델링을 한 결과는 아래와 같습니다.

  • 테이블 연관관계에서 주 테이블에 저장된 외래키에 UNIQUE 제약조건이 추가된 것을 확인할 수 있습니다.

객체 관계 매핑

해당 객체 모델링을 코드로 나타내어 보도록 하겠습니다.

코드 설명

Member 클래스 (일대일 에서 주 테이블에 해당합니다.)

public class Member {

    @Id
    @Column(name = "MEMBER_ID")
    private Long id;

    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
    
    @Column(name = "USERNAME")
    private String username;
	
    // 순수한 객체를 고려한 연관관계 편의 메서드
    public void setLocker(Locker locker) {
        if (this.locker != null) {
            this.locker.setMember(null);
        }

        this.locker = locker;
        if (locker != null) {
            locker.setMember(this);
        }
    }
    
	// Getter, Setter, Constructor
}

Locker 클래스 (일대일 에서 대상 테이블에 해당합니다.)

public class Locker {

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

    @Column(name = "NAME")
    private String name;
    
    @OneToOne(mappedBy = "locker")
    private Member member;
    
    // Getter, Setter, Constructor
}

주 테이블에 외래키를 저장하는 양방향 관계일 경우, 주 테이블에 해당하는 클래스는 주 테이블에 외래키를 저장하는 단방향 관계와 동일합니다.

대상 테이블에 해당하는 클래스에 주 테이블에 해당하는 클래스를 참조 필드로 작성해주시면 됩니다.
이때 해당 필드위에 @OneToOne(mappedBy = "반대쪽 매핑의 필드 이름값")을 추가하여 줍니다.

  • mappedBy 속성은 양방향 매핑일 때 사용하며, 반대쪽 매핑의 필드 이름값으로 설정하면 됩니다.
    이는 연관관계의 주인을 설정하기 위한 것 입니다.

연관관계 사용

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

저장

// 사물함 저장
Locker locker = new Locker(0L, "사물함");
entityManager.persist(locker);

// 회원 저장
Member member = new Member(0L, "회원");
member.setLocker(locker); // 연관관계 설정 
entityManager.persist(member);
  • JPA에서 엔티티를 저장할 때 연관된 모든 엔티티는 영속 상태이어야 합니다.

  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.

INSERT INTO LOCKER (LOCKER_ID, NAME) VALUES (0, "사물함");
INSERT INTO MEMBER (MEMBER_ID, USERNAME, LOCKER_ID) VALUES (0, "회원", 0);

조회 - 주 테이블에 의한 조회

객체그래프 탐색
member.getLocker() 을 사용해서 member 와 연관된 locker 엔티티를 조회할 수 있습니다.

Member member = entityManager.find(Member.class, 0L);
Locker locker = member.getLocker();

객체지향 쿼리 (JPQL) 사용

String jpql = "select m.locker from Member m where m.id = :memberId";

Locker locker = entityManager
	.createQuery(jpql, Locker.class)
    .setParameter("memberId", 0L)
    .getSingleResult();
  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
SELECT l.LOCKER_ID, l.NAME
FROM LOCKER l
	JOIN MEMBER m ON l.LOCKER_ID = m.LOCKER_ID
WHERE m.MEMBER_ID = 0;

조회 - 대상 테이블에 의한 조회

객체그래프 탐색
locker.getMember() 을 사용해서 locker 를 소유하고 있는 엔티티를 조회할 수 있습니다.

Locker locker = entityManager.find(Locker.class, 0L);
Member member = locker.getMember();

객체지향 쿼리 (JPQL) 사용

String jpql = "select l.member from Locker l where l.id = :lockerId";

Member member = entityManager
	.createQuery(jpql, Member.class)
    .setParameter("lockerId", 0L)
    .getSingleResult();
  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
SELECT m.MEMBER_ID, m.LOCKER_ID, m.USERNAME
FROM Member m
	JOIN Locker l ON m.LOCKER_ID = l.LOCKER_ID
WHERE l.LOCKER_ID = 0;

수정

회원이 소유하고 있던 사물함을 신규 사물함으로 변경해보도록 하겠습니다.

Locker locker = new Locker(1L, "신규 사물함");
entityManager.persist(locker);

Member member = entityManager.find(Member.class, 0L);
member.setLocker(locker);
  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
UPDATE MEMBER
SET LOCKER_ID = 1, ...
WHERE
	MEMBER_ID = 0;

연관관계 제거

회원의 사물함 기간이 종료되어, 소유하고 있던 사물함과의 연관관계를 제거해보도록 하겠습니다.

Member member = entityManager.find(Member.class, 0L);
member.setLocker(null);
  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
UPDATE MEMBER
SET LOCKER_ID = null, ...
WHERE
	MEMBER_ID = 0;

삭제 (orphanRemoval = false)

회원이 서비스를 탈퇴하여 회원 객체를 삭제해보도록 하겠습니다.

Member member = entityManager.find(Member.class, 0L);
entityManager.remove(member);
  • 위 코드를 실행하였을 때, 실행되는 SQL 은 아래와 같습니다.
DELETE
FROM MEMBER
WHERE MEMBER_ID = 0;

소스코드

다음 포스트에서는 일대일 연관관계 시, 대상 테이블에 외래키를 저장하는 형태에 대해서 알아보도록 하겠습니다.

profile
소통을 중요하게 여기며, 정보의 공유를 통해 완전한 학습을 이루어 냅니다.

1개의 댓글

comment-user-thumbnail
2025년 8월 23일

Escorts Lajpat Nagar is all about premium companionship for those who appreciate class and beauty. With an impressive range of companions to choose from, you get the opportunity to find someone who suits your mood and style. Each interaction is designed to make you feel valued, relaxed, and happy. If you’re seeking an elite and confidential experience in Lajpat Nagar, this service will definitely provide exactly what you desire.

답글 달기