Spring

Spring 숙련 18 (지연 로딩) (영속성 전이)

sehunbang 2024. 1. 31. 13:30

 

음식 테이블과 고객 테이블이 N : 1 양방향 관계로 가정 해보겠습니다.

 

.

{ // 난주엥 설명

saveAll  : 

 

.deleteAl :  .....

}

 

 

/////

@Test
@DisplayName("아보카도 피자 조회")
void test1() {
    Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);

    System.out.println("food.getName() = " + food.getName());
    System.out.println("food.getPrice() = " + food.getPrice());

    System.out.println("아보카도 피자를 주문한 회원 정보 조회");
    System.out.println("food.getUser().getName() = " + food.getUser().getName());
}

 

food.getName() = 아보카도 피자
food.getPrice() = 50000.0
아보카도 피자를 주문한 회원 정보 조회
food.getUser().getName() = Robbie

 

이 코드의 문제는 코든 정보 + 유저 정보 까지 가저온다는것.

 

Hibernate: 
    select
        f1_0.id,
        f1_0.name,
        f1_0.price,
        u1_0.id,
        u1_0.name 
    from
        food f1_0 
    left join
        users u1_0 
            on u1_0.id=f1_0.user_id 
    where
        f1_0.id=?

 

 

JPA 에서는 연관 관계가 되어 있는 정보를 

바로 가져 올지 vs 필요 할때만 가져올지 정할수 있다. (Fetch Type)

 

FetchType.LAZY  :  지연로딩    (@OneToMany default  가 Lazy)

FetchType.EAGER :  즉시로딩  (@ ManyToOne default  가 EAGER )

 

 

 

test ( EAGER  )

@DisplayName("Robbie 고객 조회")
void test2() {
    User user = userRepository.findByName("Robbie");
    System.out.println("user.getName() = " + user.getName());

    System.out.println("Robbie가 주문한 음식 이름 조회");
    for (Food food : user.getFoodList()) {
        System.out.println(food.getName());
    }
}

Hibernate: 
    /* <criteria> */ select
        u1_0.id,
        u1_0.name 
    from
        users u1_0 
    where
        u1_0.name=?
user.getName() = Robbie
Robbie가 주문한 음식 이름 조회
Hibernate: 
    select
        ol1_0.user_id,
        ol1_0.id,
        ol1_0.name,
        ol1_0.price 
    from
        food ol1_0 
    where
        ol1_0.user_id=?
고구마 피자
아보카도 피자
후라이드 치킨

 

User 객체 안에 PK 가 있고 Food 테이블에 조회 해서 

퀘리를 만들어서 List 에 가져오는것.

 

 

test ( Lazy )

하면 

에러가 뜹니다....

 

영속성 컨텍스트 랑 지연 로딩 :

1 차 캐시 쓰기 - 지연 저장소 - 변경감지  처럼...

 

지연 로딩 도 영속성 컨텍스트 중 하나 입니다.

 

지연 로딩 이 된 엔티티정보조회 하려면. 반드시 영속성 컨텍스트존재 해야 합니다.

 

@Transection 이 적용 되어 야 한다는 뜻. (원래 조회(Select) 할때는 필수 가 아니었지요)

지연 로딩 조회는 @Transection  적용 이 필수 입니다.

 

 

 

 

 

 

영속성 전이 (CaseCadeTest)

 

유저에 함수 추가

public void addFoodList(Food food) {
    this.orderList.add(food);
    food.setUser(this);// 외래 키(연관 관계) 설정
}

 

테스트

@Test
@DisplayName("Robbie 음식 주문")
void test1() {
    // 고객 Robbie 가 후라이드 치킨과 양념 치킨을 주문합니다.
    User user = new User();
    user.setName("Robbie");

    // 후라이드 치킨 주문
    Food food = new Food();
    food.setName("후라이드 치킨");
    food.setPrice(15000);

    user.addFoodList(food);

    Food food2 = new Food();
    food2.setName("양념 치킨");
    food2.setPrice(20000);

    user.addFoodList(food2);

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

Hibernate: 
    /* insert for
        cohttp://m.sparta.jpaadvance.entity.User */insert 
    into
        users (name) 
    values
        (?)
Hibernate: 
    /* insert for
        cohttp://m.sparta.jpaadvance.entity.Food */insert 
    into
        food (name, price, user_id) 
    values
        (?, ?, ?)
Hibernate: 
    /* insert for
        cohttp://m.sparta.jpaadvance.entity.Food */insert 
    into
        food (name, price, user_id) 
    values
        (?, ?, ?)

 

 

Robbie가 음식을 주문하기 위해서는 위 처럼 user, food, food2

모두 직접 save() 메서드를 호출하면서 영속화해야합니다.

 

유저 안에 이미 food list  가 있데....

 

JPA에서는 이를 간편하게 처리할 수 있는 방법으로 영속성 전이

(CASCADE)의 PERSIST 옵션을 제공합니다.

(FK 있는 SQL delete 했을때 on delete cascade 같은게 있었지요)

 

영속성 전이란 : 영속 상태의 Entity에서 수행되는 작업들이 연관된 Entity까지 전파되는 상황을 뜻합니다.

 

 

CASCADE : PERSIST : 

 

 영속정 전의를 하고 싶음 엔티티 @OneToMany 에 CaseCade 라는 옵션이 있다.

 

User.java

@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)
private List<Food> orderList = new ArrayList<>();

 

테스트

void test2() {
    // 고객 Robbie 가 후라이드 치킨과 양념 치킨을 주문합니다.
    User user = new User();
    user.setName("Robbie");

    // 후라이드 치킨 주문
    Food food = new Food();
    food.setName("후라이드 치킨");
    food.setPrice(15000);

    user.addFoodList(food);

    Food food2 = new Food();
    food2.setName("양념 치킨");
    food2.setPrice(20000);

    user.addFoodList(food2);

    userRepository.save(user);
}

 

food, food2  save() 가 없어도 저장이 됩니다.

Hibernate: 
    create table food (
        id bigint not null auto_increment,
        name varchar(255),
        price float(53) not null,
        user_id bigint,
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    create table users (
        id bigint not null auto_increment,
        name varchar(255),
        primary key (id)
    ) engine=InnoDB
Hibernate: 
    alter table food 
       add constraint FKonuv85jhrw21c6y3c9cg0ep6o 
       foreign key (user_id) 
       references users (id)

 

 

삭제 하는 법 :

엔티티에 CASCADE : REMOVE 추가 해줘야 함

 

영속성 전이 없이는 

// 주문한 음식 데이터 삭제
foodRepository.deleteAll(user.getFoodList());
// Robbie 탈퇴
userRepository.delete(user);

 

두 repo 에서 지워야 했지만.

 

영속성 전이 를 사용 하면

    @Test
    @Transactional
    @Rollback(value = false)
    @DisplayName("영속성 전이 삭제")
    void test4() {
        // 고객 Robbie 를 조회합니다.
        User user = userRepository.findByName("Robbie");
        System.out.println("user.getName() = " + user.getName());
        // Robbie 가 주문한 음식 조회
        for (Food food : user.getFoodList()) {
            System.out.println("food.getName() = " + food.getName());
        }
        // Robbie 탈퇴
        userRepository.delete(user);
    }
}

 

userRepository.delete(user); 로 한방에 없엘수 있다.