Everyday Dev System

@OneToOne 외래키 활용 본문

내배캠 주요 학습/Spring 숙련

@OneToOne 외래키 활용

chaeyoung- 2023. 6. 21. 01:33

Validation, entity, 1:1 / 1:다 / 다:1 관계는 주특기 숙련주차 핵심 키워드인만큼 집중해서 들어주세요!

 

- 외래키의 주인은 테이블에 실제 존재하는 FK의 위치이다.

- 양방향일 때 FK의 주인이 누구인지 알려줘야 한다.
-> 외래키의 주인이 아닌 쪽에서 mappedBy를 통해 주인을 지정한다.

- mappedBy의 속성값은 외래키 주인인, 상대 Entity에 있는 필드명을 의미한다.

- 외래키의 주인만이 외래키를 등록 및 삭제 추가 가능하다.

 

<<요약>>

단방향의 경우엔 외래키의 주인만 상대 Entity 타입의 필드를 가진다.

양방향의 경우엔 외래키의 주인은 상대 Entity 타입의 필드를 가지면서,
상대 Entity는 외래키의 주인의 타입의 필드를 가지면 mappedBy를 통해 주인을 지정한다.

 

 

주인 클래스의 필드 코드 :

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;

 

아래는 주인이 아닌 쪽의 코드 : 

@OneToOne(mappedBy = "user")
private Food food;

mappedBy의 속성값은 외래키 주인인, 상대 Entity에 있는 필드명을 의미한다.

외래키의 주인만이 외래키를 등록 및 삭제 추가 가능.

 

 

 

 

1 : 1 단방향 예제 Code : 

1 : 1 관계이다.
단방향이다.
Food -> User
음식 클래스가 FK 주인이다.
더보기

[entity] - 

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "food")
public class Food {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private double price;

    @OneToOne
    @JoinColumn(name = "user_id")
    private User user;
}
package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
}

 

[repository] - 

package com.sparta.jpaadvance.repository;

import com.sparta.jpaadvance.entity.Food;
import org.springframework.data.jpa.repository.JpaRepository;

public interface FoodRepository extends JpaRepository<Food,Long> {

}
package com.sparta.jpaadvance.repository;

import com.sparta.jpaadvance.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User,Long> {

}

 

 

단방향 Test Code:

package com.sparta.jpaadvance.relation;

import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@SpringBootTest
public class OneToOneTest {

    @Autowired
    FoodRepository foodRepository;
    @Autowired
    UserRepository userRepository;

    @Test
    @Rollback(value = false) // 테스트에서는 @Transactional 에 의해 자동 rollback 됨으로 false 설정해준다.
    @DisplayName("1대1 단방향 테스트")
    void test1() {

        User user = new User();
        user.setName("Robbie");

        // 외래 키의 주인인 Food Entity user 필드에 user 객체를 추가해 줍니다.
        Food food = new Food();
        food.setName("후라이드 치킨");
        food.setPrice(15000);
        food.setUser(user); // 외래 키(연관 관계) 설정

        userRepository.save(user);
        foodRepository.save(food);
    }
}

 

        User user = new User();
        user.setName("Robbie");

        Food food = new Food();
        food.setName("후라이드 치킨");
        food.setPrice(15000);
        food.setUser(user); // 외래 키(연관 관계) 설정

        userRepository.save(user);
        foodRepository.save(food);

 

User 타입의 user 객체를 새로 생성한다.

Food 타입의 food 객체를 새로 생성한다.

setter()를 통해서 user 객체를 food 객체에 외래 키를 설정한다.

 

userRepository.save(user);
foodRepository.save(food);

해당 객체들 DB에 저장하기 위해 JpaRepository를 상속받은
FoodRepository, UserRepository 인터페이스 타입 객체에 save() 메서드를 수행합니다.

결과:

 

 

 

양방향 Code : 

더보기

[entity] - 

package com.sparta.jpaadvance.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;

@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    @OneToOne(mappedBy = "user")
    private Food food;

    public void addFood(Food food) {
        this.food = food;
        food.setUser(this);
    }
}

 

 

 

양방향 Test Code:

package com.sparta.jpaadvance.relation;

import com.sparta.jpaadvance.entity.Food;
import com.sparta.jpaadvance.entity.User;
import com.sparta.jpaadvance.repository.FoodRepository;
import com.sparta.jpaadvance.repository.UserRepository;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.transaction.annotation.Transactional;

@Transactional
@SpringBootTest
public class OneToOneTest {

    @Autowired
    FoodRepository foodRepository;
    
    @Autowired
    UserRepository userRepository;

    @Test
    @Rollback(value = false)
    @DisplayName("1대1 양방향 테스트 : 외래 키 저장 실패")
    void test2() {
        Food food = new Food();
        food.setName("고구마 피자");
        food.setPrice(30000);

        // 외래 키의 주인이 아닌 User 에서 Food 를 저장해보겠습니다.
        User user = new User();
        user.setName("Robbie");
        user.setFood(food);

        userRepository.save(user);
        foodRepository.save(food);

        // 확인해 보시면 user_id 값이 들어가 있지 않은 것을 확인하실 수 있습니다.
    }

    @Test
    @Rollback(value = false)
    @DisplayName("1대1 양방향 테스트 : 외래 키 저장 실패 -> 성공")
    void test3() {
        Food food = new Food();
        food.setName("고구마 피자");
        food.setPrice(30000);

        // 외래 키의 주인이 아닌 User 에서 Food 를 저장하기 위해 addFood() 메서드 추가
        // 외래 키(연관 관계) 설정 food.setUser(this); 추가
        User user = new User();
        user.setName("Robbie");
        user.addFood(food);

        userRepository.save(user);
        foodRepository.save(food);
    }

    @Test
    @Rollback(value = false)
    @DisplayName("1대1 양방향 테스트")
    void test4() {
        User user = new User();
        user.setName("Robbert");

        Food food = new Food();
        food.setName("고구마 피자");
        food.setPrice(30000);
        food.setUser(user); // 외래 키(연관 관계) 설정

        userRepository.save(user);
        foodRepository.save(food);
    }

}

 

 

1. 외래키 주인이 아닌 곳에서 저장

        Food food = new Food();
        food.setName("고구마 피자");
        food.setPrice(30000);

        // 외래 키의 주인이 아닌 User 에서 Food 를 저장해보겠습니다.
        User user = new User();
        user.setName("Robbie");
        user.setFood(food);

        userRepository.save(user);
        foodRepository.save(food);

 

Food 타입의 food 객체를 새로 생성한다.

User 타입의 user 객체를 새로 생성한다.

 

외래키의 주인(Food)가 아닌 User에서 Food를 저장 시도.

 

결과 :

 

외래키의 주인만이 외래키를 조정할 수 있다.

 

이를 해결하기 위한 방법으로, 

외래 키의 주인이 아닌 User 에서 Food 를 저장하기 위해 addFood() 메서드 추가

    public void addFood(Food food) {
        this.food = food;
        food.setUser(this);
    }
        Food food = new Food();
        food.setName("고구마 피자");
        food.setPrice(30000);

        // 외래 키의 주인이 아닌 User 에서 Food 를 저장하기 위해 addFood() 메서드 추가
        // 외래 키(연관 관계) 설정 food.setUser(this); 추가
        User user = new User();
        user.setName("Robbie");
        user.addFood(food);

        userRepository.save(user);
        foodRepository.save(food);

 

this.food = food;
food.setUser(this);

위 food 멤버 변수에 자기 자신 객체를 넣는다.

 

 

 

2. 외래키 주인이 저장

        User user = new User();
        user.setName("Robbert");

        Food food = new Food();
        food.setName("고구마 피자");
        food.setPrice(30000);
        food.setUser(user); // 외래 키(연관 관계) 설정

        userRepository.save(user);
        foodRepository.save(food);

user 객체 생성 -> food 객체 생성 -> user 객체를 food 객체에 설정

DB에 저장.

 

 

 

조회하기 

    @Test
    @DisplayName("1대1 조회 : Food 기준 user 정보 조회")
    void test5() {
        Food food = foodRepository.findById(1L).orElseThrow(NullPointerException::new);
        // 음식 정보 조회
        System.out.println("food.getName() = " + food.getName());

        // 음식을 주문한 고객 정보 조회
        System.out.println("food.getUser().getName() = " + food.getUser().getName());
    }

    @Test
    @DisplayName("1대1 조회 : User 기준 food 정보 조회")
    void test6() {
        User user = userRepository.findById(1L).orElseThrow(NullPointerException::new);
        // 고객 정보 조회
        System.out.println("user.getName() = " + user.getName());

        // 해당 고객이 주문한 음식 정보 조회
        Food food = user.getFood();
        System.out.println("food.getName() = " + food.getName());
        System.out.println("food.getPrice() = " + food.getPrice());
    }