본문 바로가기
JPA

[JPA] 일대일 연관관계 (양방향)

by 2nyong 2023. 4. 29.

목차


    테이블 설계

    일대일 연관관계를 고객(Member)고객이 주문한 음식(Food)을 예시로 설명하겠습니다.

    • 고객(Member)은 하나의 음식(Food)을 주문할 수 있다.
    • 고객(Member) : 음식(Food) = 1 : 1

     

    양방향 연관관계를 위한 추가 조건은 다음과 같습니다.

    • 외래 키의 주인은 Food Entity 이다.
    일대일 관계에서의 외래 키
    Entity에서 외래 키의 주인은 일반적으로 다(N)의 관계인 Entity이지만, 일대일 관계에서는 아무나 외래 키의 주인이 될 수 있으므로 직접 지정해줘야 합니다.
    • Food Entity는 Food.member 필드를 통해 음식을 주문한 고객(Member Entity)에 접근할 수 있다.
    • Member Entity는 주문한 음식인 Food Entity에 Member.food 필드를 통해 접근할 수 있으며, Member.food 필드는 읽기 전용입니다.


    Entity 관계 매핑

    - Food Entity

    @Entity
    public class Food {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
        private double price;
        
        @OneToOne
        @JoinColumn(name = "member_id")
        private Member member;
        
        // constructor, getter, setter, ...
    }

     

    - Member Entity

    @Entity
    public class Member {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Long id;
        private String name;
        
        @OneToOne(mappedBy = "member")
        private Food food;
        
        public void setFood(Food food) {
            this.food = food;
        }
            
        // constructor, getter, setter, ...
    }

     

    • 양방향 매핑에서도 단방향 매핑과 같이 지정된 외래 키 주인 Entity(Food Entity)쪽에서 반대 Entity(Member Entity)를 참조 필드로 갖습니다.
    • 외래 키 주인 Entity의 참조 필드 위에 @OneToOne 애노테이션과 @JoinColumn(name = "{foreign key name}") 애노테이션을 추가로 작성합니다.
    • 반대 Entity쪽에서도 외래 키 주인 Entity를 참조 필드로 작성해줍니다.
    • 반대 Entity의 참조 필드 위에 @OneToOne(mappedBy = "{외래 키 주인 Entity가 참조하는 필드 이름}") 애노테이션을 추가로 작성합니다.
    • mappedBy 속성은 양방향 연관관계에서 사용하며, 이 연관관계에 대해 외래 키의 주인이 참조하고 있는 필드 명을 넣어줍니다.
    • 위의 Entity 관계를 적용하여 테이블을 생성하였을 때, 실행되는 SQL은  아래와 같습니다.
    create table food (
        id bigint not null auto_increment,
        name varchar(255),
        price float(53) not null,
        member_id bigint,
        primary key (id)
    );
    
    create table member (
        id bigint not null auto_increment,
        name varchar(255),
        primary key (id)
    );
    
    alter table food 
        add constraint FKnyfi1wxbgo0pqliou7xkfe4lw 
        foreign key (member_id) 
        references member (id)

    Entity 관계 사용법

    - 저장

    @Transactional
    @SpringBootTest
    public class OneToOneTest {
    
        @Autowired FoodRepository foodRepository;
        @Autowired UserRepository userRepository;
    
        @Test
        @Rollback(value = false)
        @DisplayName("1대1 양방향 저장 테스트")
        void OneWaySaveTest() {
            // 음식(Food)
            Food food = new Food("후라이드 치킨", 15000);
            foodRepository.save(food);
            
            // 고객(Member)
            Member member = new Member("2nyong");
            userRepository.save(Member);
            member.setFood(food); // 외래 키의 주인이 아닌 쪽에서 참조
        }
    }

     

    • 위 코드 실행 시, 적용되는 SQL은 다음과 같습니다.
    • 외래 키의 주인이 아닌 쪽에서 외래 키를 참조하여 update 했지만, UPDATE 쿼리가 생기지 않았습니다.
      외래 키의 주인은 연관관계를 관리하고, 외래 키를 INSERT하거나 조회하는 등의 역할을 합니다. 또, Member의 food 필드는 읽기 전용이기 때문입니다.
    • 따라서 현재 Member Entity에 작성되어 있는 setFood 메서드는 외래 키를 등록하는 것이 아니라, 단순히 member 필드를 가지고 있는 Food Entity를 Member Entity에 넣어주는 것 까지만 수행하게 됩니다.
    • DB의 Food 테이블을 살펴봐도 외래키가 null로 저장되어 있습니다.
    INSERT INTO FOOD (MEMBER_ID, NAME, PRICE) VALUES (NULL, "후라이드 치킨", 15000);
    INSERT INTO MEMBER (NAME) VALUES ('Inyong');

     

    • 외래 키를 입력해주기 위해서는 Member Entity의 setFood 메서드를 외래 키의 주인인 Food entity를 통해 업데이트할 수 있도록 다음과 같이 수정해주어야 합니다.
        public void setFood(Food food) {
            this.food = food;
            food.setMember(this); // 외래 키의 주인을 통해 업데이트한다.
        }

     

    • Member.java에 코드 수정을 적용하고 테스트 코드 실행 시, 적용되는 SQL은 다음과 같습니다.
    • 이제 JPA의 변경감지와 Hibernate의 업데이트 기본 전략에 의해 UPDATE 문이 실행됩니다.
    • DB의 Food 테이블에 외래키가 정상적으로 입력되었습니다.
    INSERT INTO FOOD (MEMBER_ID, NAME, PRICE) VALUES (NULL, "후라이드 치킨", 15000);
    INSERT INTO MEMBER (NAME) VALUES ('Inyong');
    UPDATE FOOD SET (MEMBER_ID=2, NAME="후라이드 치킨", PRICE=15000) WHERE FOOD_ID=2;

     

    - 조회

    @Transactional
    @SpringBootTest
    public class OneToOneTest {
    
        @Autowired FoodRepository foodRepository;
        @Autowired MemberRepository memberRepository;
    
        @Test
        @DisplayName("1대1 양방향 조회")
        void TwoWayViewTest() {
            // Food -> Member
            Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);
            System.out.println("food.getName() = " + food.getName());
            System.out.println("food.getMember().getName() = " + food.getMember().getName());
            
            // Member -> Food
            Member member = memberRepository.findById(2L).orElseThrow(NullPointerException::new);
            System.out.println("member.getName() = " + member.getName());
            System.out.println("member.getFood().getName() = " + member.getFood().getName());
        }
    }
    
    // 출력 결과
    // food.getName() = 후라이드 치킨
    // food.getMember().getName() = Inyong
    // member.getName() = Inyong
    // member.getFood().getName() = 후라이드 치킨

     

    • 위 코드 실행 시, 적용되는 SQL은 다음과 같습니다.
    SELECT f.ID, m.ID, m.NAME, f.NAME, f.PRICE FROM FOOD f
        LEFT JOIN MEMBER m ON m.ID = f.MEMBER_ID
    WHERE f.ID = 1

     

    'JPA' 카테고리의 다른 글

    [JPA] 일대다 연관관계 (단방향)  (0) 2023.04.29
    [JPA] 다대일 연관관계 (양방향)  (0) 2023.04.29
    [JPA] 다대일 연관관계 (단방향)  (0) 2023.04.29
    [JPA] 일대일 연관관계 (단방향)  (0) 2023.04.28
    [JPA] Entity 연관 관계  (0) 2023.04.28

    댓글