본문 바로가기

Spring/JPA

2) Spring Data JPA 2부 - @OneToMany ,@ManyToOne , Casecade

반응형

앞선 1부에 이어서 2부에서는 cascade 옵션에 대해 알아 보자.

 

#cascade : 영속성전이 Type을 설정 한다고 하는데 .. 뭔가 단어가 어렵다. 정확히 어떤 역활을 하는지 보자.

              type의 종류는  ALL , PERSIST , MERGE ,REMOVE ,REFRESH,DETACH 가있다.

              여기서 ALL을 당연히 모든 Type을 다 넣은것이고 각각에 대해 알아보자.

 

먼저 cascade 옵션을 빼고 Product를 저장해보자.

@OneToMany(mappedBy = "product" , targetEntity = Item.class , fetch = FetchType.EAGER)
private List<Item> items = new ArrayList<Item>();
    @Transactional
    public String saveProduct(String productName ) throws Exception {
        Product product = new Product(productName);
        for(int i = 0 ; i < 2 ; i++) {
            Item item = new Item("Product Item " + i);
            item.setItemNumber("Product Item "+ i +" Number");
            item.setProduct(product);
            product.getItems().add(item);
        }
        productRepo.save(product);
        return product.getProductId();
    }

위와 같은 코드를 실행하면 아래와 같이 Product만 저장을 한다. 당연히 product만 save 했기 때문이다.

2021-03-12 10:21:30.186 DEBUG 10128 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    insert 
    into
        product
        (product_name, product_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        product
        (product_name, product_id) 
    values
        (?, ?)
2021-03-12 10:21:30.195 TRACE 10128 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [ProductTest1]
2021-03-12 10:21:30.196 TRACE 10128 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [03ce6792-09d0-4529-85fd-1448258fee84]

그럼 이제 casecade type을 PERSIST로 하고 다시 저장해 보자.

 @OneToMany(mappedBy = "product" , targetEntity = Item.class , fetch = FetchType.EAGER ,cascade = CascadeType.PERSIST)
 private List<Item> items = new ArrayList<Item>();

그럼 아래와 같이 Product와 두개의 Item이 저장되는 insert가 쿼리가 실행된다.

2021-03-12 10:25:05.392 DEBUG 7972 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    insert 
    into
        product
        (product_name, product_id) 
    values
        (?, ?)
Hibernate: 
    insert 
    into
        product
        (product_name, product_id) 
    values
        (?, ?)
2021-03-12 10:25:05.400 TRACE 7972 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [ProductTest1]
2021-03-12 10:25:05.400 TRACE 7972 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [35b617ad-2dfa-45db-8649-5ec657d36142]
2021-03-12 10:25:05.409 DEBUG 7972 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    insert 
    into
        item
        (item_name, item_number, product_id, item_id) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        item
        (item_name, item_number, product_id, item_id) 
    values
        (?, ?, ?, ?)
2021-03-12 10:25:05.410 TRACE 7972 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Product Item 0]
2021-03-12 10:25:05.410 TRACE 7972 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Product Item 0 Number]
2021-03-12 10:25:05.410 TRACE 7972 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [35b617ad-2dfa-45db-8649-5ec657d36142]
2021-03-12 10:25:05.410 TRACE 7972 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [fef22e6a-422f-40e3-8833-c20d02a1ab36]
2021-03-12 10:25:05.412 DEBUG 7972 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    insert 
    into
        item
        (item_name, item_number, product_id, item_id) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        item
        (item_name, item_number, product_id, item_id) 
    values
        (?, ?, ?, ?)
2021-03-12 10:25:05.413 TRACE 7972 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Product Item 1]
2021-03-12 10:25:05.414 TRACE 7972 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Product Item 1 Number]
2021-03-12 10:25:05.414 TRACE 7972 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [35b617ad-2dfa-45db-8649-5ec657d36142]
2021-03-12 10:25:05.414 TRACE 7972 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [2b5cec4c-63aa-413a-b68e-8374c49a9ab0]

결과에서 알수 있듯이 Save되는 객체와 연관된 객체까지 같이 insert 되는 옵션이다.

 

MERGE 옵션에 대해 알아보자. MERGE는 단순하게 생각해서 update 옵션이다. 이해하기 쉽게 jpa save 소스를 열어보자.

    @Transactional
	@Override
	public <S extends T> S save(S entity) {

		Assert.notNull(entity, "Entity must not be null.");

		if (entityInformation.isNew(entity)) {
			em.persist(entity);
			return entity;
		} else {
			return em.merge(entity);
		}
	}

위 소스에서 보면 Entity가 새로운것이면 persist 하고 아니면 merge 하게 된다. 

여기서 알수 있듯이 cascade 옵션이 PERSIST 이면 새로운 Entity 일때 연관된 Entity 까지 같이 insert 되고 

MERGE 일때 update시 연관된 Entity를 같이 update한다는말이다.

 

테스트를 해보자.

현재 cascade type 은 아래와 같다.

@OneToMany(mappedBy = "product" , targetEntity = Item.class , fetch = FetchType.EAGER , cascade = CascadeType.PERSIST)
private List<Item> items = new ArrayList<Item>();
2021-03-12 13:38:21.726 DEBUG 28332 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    update
        product 
    set
        product_name=? 
    where
        product_id=?
Hibernate: 
    update
        product 
    set
        product_name=? 
    where
        product_id=?
2021-03-12 13:38:21.729 TRACE 28332 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [productChange1]
2021-03-12 13:38:21.729 TRACE 28332 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [f1d55f8e-5c3e-419b-a47f-3196fb4ccc35]
2021-03-12 13:38:21.735 DEBUG 28332 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
Hibernate: 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
2021-03-12 13:38:21.736 TRACE 28332 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [productChange1 Item Change0]
2021-03-12 13:38:21.736 TRACE 28332 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Product 0 Item 0 Number]
2021-03-12 13:38:21.736 TRACE 28332 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [f1d55f8e-5c3e-419b-a47f-3196fb4ccc35]
2021-03-12 13:38:21.736 TRACE 28332 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [33fe52e8-6f88-421d-a3a7-2eb12a71b891]
2021-03-12 13:38:21.737 DEBUG 28332 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
Hibernate: 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
2021-03-12 13:38:21.738 TRACE 28332 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [productChange1 Item Change1]
2021-03-12 13:38:21.738 TRACE 28332 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Product 0 Item 1 Number]
2021-03-12 13:38:21.738 TRACE 28332 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [f1d55f8e-5c3e-419b-a47f-3196fb4ccc35]
2021-03-12 13:38:21.738 TRACE 28332 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [01f48282-e054-4323-bf15-ff99ac3b855b]

 

이상태에서 업데이트를 해보자 소스는 아래와 같다.

@Transactional
    public Product updateProduct(String productId , String productName ) throws Exception {
        Product product = productRepo.findById(productId).get();
        product.setProductName(productName);
        if(product.getItems().size() > 0 ) {
            for(int i = 0 ; i < product.getItems().size() ; i++) {
                Item item = product.getItems().get(i);
                item.setItemName(productName + " Item Change" + i);
            }
            Item additem = new Item("Product Item ADD ");
            additem.setItemNumber("Product Item ADD Number");
            additem.setProduct(product);
            product.getItems().add(additem);
        }else {
            for(int i = 0 ; i < 2 ; i++) {
                Item item = new Item("Product Item " + i);
                item.setItemNumber("Product Item "+ i +" Number");
                item.setProduct(product);
                product.getItems().add(item);
            }
        }
        productRepo.save(product);
        
        return product;
    }
    

위소스는 테스트를 위해 기존 item 기존 아이템에 신규 item을 하나더 추가하고 없으면 신규 아이템 2개를 붙이는 소스이다.

update를 수행하면 당연히 item이 2개 존재하니 item insert 하나와 update 두개의 쿼리가 발생해야한다.

결과를 보자.

2021-03-12 15:48:24.973 DEBUG 9916 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    insert 
    into
        item
        (item_name, item_number, product_id, item_id) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        item
        (item_name, item_number, product_id, item_id) 
    values
        (?, ?, ?, ?)
2021-03-12 15:48:24.975 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [null]
2021-03-12 15:48:24.975 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [null]
2021-03-12 15:48:24.975 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [null]
2021-03-12 15:48:24.975 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [f4249f90-3f64-45ac-ba25-43469c0b9c45]
2021-03-12 15:48:24.977 DEBUG 9916 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    update
        product 
    set
        product_name=? 
    where
        product_id=?
Hibernate: 
    update
        product 
    set
        product_name=? 
    where
        product_id=?
2021-03-12 15:48:24.979 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [productChange2]
2021-03-12 15:48:24.980 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [ed542c60-8e17-4a40-9203-cc459db2c27f]
2021-03-12 15:48:24.985 DEBUG 9916 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
Hibernate: 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
2021-03-12 15:48:24.986 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [productChange2 Item Change0]
2021-03-12 15:48:24.986 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Product Item 0 Number]
2021-03-12 15:48:24.987 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [ed542c60-8e17-4a40-9203-cc459db2c27f]
2021-03-12 15:48:24.987 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [070688e7-d365-4d62-990b-54c56baf616e]
2021-03-12 15:48:24.989 DEBUG 9916 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
Hibernate: 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
2021-03-12 15:48:24.990 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [productChange2 Item Change1]
2021-03-12 15:48:24.991 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Product Item 1 Number]
2021-03-12 15:48:24.991 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [ed542c60-8e17-4a40-9203-cc459db2c27f]
2021-03-12 15:48:24.991 TRACE 9916 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [d3c165d1-088a-49d1-961b-70df2ccead04]

신규로 추가한 item의 insert 쿼리가 발생하는것을 볼 수 있다. 하지만 insert parameter값을 보면 item_id 를 제외한 나머지 필드의 값이 NULL로 들어가는것을 볼 수 있다. 

이상태에서 MERGE 옵션을 추가해서 다시 실행해보자.

@OneToMany(mappedBy = "product" , targetEntity = Item.class , fetch = FetchType.LAZY , cascade = {CascadeType.PERSIST , CascadeType.MERGE})
private List<Item> items = new ArrayList<Item>();
2021-03-12 15:53:56.594 DEBUG 25624 --- [nio-8080-exec-4] org.hibernate.SQL                        : 
    insert 
    into
        item
        (item_name, item_number, product_id, item_id) 
    values
        (?, ?, ?, ?)
Hibernate: 
    insert 
    into
        item
        (item_name, item_number, product_id, item_id) 
    values
        (?, ?, ?, ?)
2021-03-12 15:53:56.595 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [Product Item ADD ]
2021-03-12 15:53:56.595 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Product Item ADD Number]
2021-03-12 15:53:56.595 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [581f3027-9cdf-48d0-8836-48174da3f542]
2021-03-12 15:53:56.595 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [6b1cc926-1e7d-46be-b29c-b185582f34df]
2021-03-12 15:53:56.597 DEBUG 25624 --- [nio-8080-exec-4] org.hibernate.SQL                        : 
    update
        product 
    set
        product_name=? 
    where
        product_id=?
Hibernate: 
    update
        product 
    set
        product_name=? 
    where
        product_id=?
2021-03-12 15:53:56.598 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [productChange3]
2021-03-12 15:53:56.598 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [581f3027-9cdf-48d0-8836-48174da3f542]
2021-03-12 15:53:56.600 DEBUG 25624 --- [nio-8080-exec-4] org.hibernate.SQL                        : 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
Hibernate: 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
2021-03-12 15:53:56.601 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [productChange3 Item Change0]
2021-03-12 15:53:56.601 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Product Item 0 Number]
2021-03-12 15:53:56.601 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [581f3027-9cdf-48d0-8836-48174da3f542]
2021-03-12 15:53:56.601 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [3f67309e-9fa6-4e3c-9361-eb40eca54060]
2021-03-12 15:53:56.602 DEBUG 25624 --- [nio-8080-exec-4] org.hibernate.SQL                        : 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
Hibernate: 
    update
        item 
    set
        item_name=?,
        item_number=?,
        product_id=? 
    where
        item_id=?
2021-03-12 15:53:56.603 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [productChange3 Item Change1]
2021-03-12 15:53:56.603 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [2] as [VARCHAR] - [Product Item 1 Number]
2021-03-12 15:53:56.603 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [3] as [VARCHAR] - [581f3027-9cdf-48d0-8836-48174da3f542]
2021-03-12 15:53:56.603 TRACE 25624 --- [nio-8080-exec-4] o.h.type.descriptor.sql.BasicBinder      : binding parameter [4] as [VARCHAR] - [7dc5052f-0d35-42d7-b9e1-ee1130ac86f7]

MERGE옵션을 추가하니 신규 추가된 item 의 insert 쿼리에 정상적으로 parameter가 들어가는것을 확인!

PERSIST와 MEGER는 거의 한쌍으로 같이 다녀야 되는것 아닐까? 상황에 따라 다르겠지만 어떤 경우에 다른게 써야 할지 당장 생각이 나지 않는다.

 

다음은 REFRESH 옵션에 대해 테스트 해보자 . 먼저 REFRESH 옵션이 없는 경우를 보자.

@Transactional
    public Product updateProductRefresh(String productId , String productName ) throws Exception {
        Product product = productRepo.findById(productId).get();
        product.setProductName(productName);
        String changeItemName = productName + " Item Change";
        if(product.getItems().size() > 0 ) {
            for(int i = 0 ; i < product.getItems().size() ; i++) {
                Item item = product.getItems().get(i);
                item.setItemName(changeItemName + i);
            }
//            엔티티를 Refresh할경우 Entity가 새로 생성된 경우 ID가 없어서 Refresh할때 Null Identifier 라는 에러 발생함.
//            Item additem = new Item("Product Item ADD ");
//            additem.setItemNumber("Product Item ADD Number");
//            additem.setProduct(product);
//            product.getItems().add(additem);
            
        }else {
            for(int i = 0 ; i < 2 ; i++) {
                Item item = new Item("Product Item " + i);
                item.setItemNumber("Product Item "+ i +" Number");
                item.setProduct(product);
                product.getItems().add(item);
            }
        }
        entityManager.refresh(product);
        
        log.info("Change Product Name : " + productName + " Current Producct Name : " + product.getProductName());
        log.info("Change Item Name : " +  changeItemName + " Current Item Name : " + product.getItems().get(0).getItemName());
        
        productRepo.save(product);
        
        return product;
    }

위 소스에서 주목할점은 

entityManager.refresh(product) 

 product 엔티티를 refresh 하고 나면 아래 log에서 확인해 보듯이 Loading된 Entity의 정보에서 변경된 부분을 취소하고 최초 Loading된 상태로 초기화 하는 기능을 한다. 

2021-03-13 23:36:01.654  INFO 18944 --- [nio-8080-exec-1] c.d.jpa.service.SampleServiceImpl        : Change Product Name : Name Change7 Current Producct Name : Name Change4

로그에서 확인해 보면 Parameter로 넘어온 변경할 product Name이 적용되지 않고 refresh 후에 최초 DB값으로 초기화된 것을 확인 할수있다. 

 

그럼 아래 로그에 Item도 item name이 초기화 되었을까? 로그를 확인해보자.

2021-03-13 23:36:01.656  INFO 18944 --- [nio-8080-exec-1] c.d.jpa.service.SampleServiceImpl        : Change Item Name : Name Change7 Item Change Current Item Name : Name Change7 Item Change0

로그에서 보듯이 Item Name은 초기화 되지 않았다. casecade가 영속성전이라는 의미를 이제 이해가 간다.

cascade에 REFRESH 옵션이 없으니 연결된 Item 까지 refresh가 되지 않는 것이다. 

 

그럼 REFRESH 를 넣고 테스트 해보자.

    @OneToMany(mappedBy = "product" , targetEntity = Item.class , fetch = FetchType.LAZY , cascade = {CascadeType.REFRESH , CascadeType.PERSIST , CascadeType.MERGE})
    private List<Item> items = new ArrayList<Item>();

똑같은 방법으로 테스트해보고 로그인 확인해 보자.

2021-03-13 23:47:32.363  INFO 4136 --- [nio-8080-exec-1] c.d.jpa.service.SampleServiceImpl        : Change Product Name : Name Change8 Current Producct Name : Name Change4
2021-03-13 23:47:32.363  INFO 4136 --- [nio-8080-exec-1] c.d.jpa.service.SampleServiceImpl        : Change Item Name : Name Change8 Item Change Current Item Name : Name Change7 Item Change0

결과와 같이 Item까지 Refresh가 적용이 되면서 초기화로 변경사항이 없기 떄문에 update 쿼리가 발생하지 않는것을 

로그상으로 확인이 가능하다.

 

이제 REMOVE에 대해 테스트해보자.

현재 상태에서 REMOVE를 해보자. 

@Transactional
    public void deleteProduct(String productId) throws Exception {
    	Product product = productRepo.findById(productId).get();
        productRepo.delete(product);
    }

REMOVE가 없는 상태에서는 아래와같이 item에 foreign key로 연결되어있어서 에러가 발생한다.

2021-03-14 00:00:08.405 DEBUG 26152 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    delete 
    from
        product 
    where
        product_id=?
Hibernate: 
    delete 
    from
        product 
    where
        product_id=?
2021-03-14 00:00:08.405 TRACE 26152 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [7506a4b0-296d-4040-96c1-f1cfac96cce3]
2021-03-14 00:00:08.413 DEBUG 26152 --- [nio-8080-exec-2] o.h.engine.jdbc.spi.SqlExceptionHelper   : could not execute statement [n/a]

org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Referential integrity constraint violation: "FKD1G72RRHGQ1SF7M4UWFVUHLHE: PUBLIC.ITEM FOREIGN KEY(PRODUCT_ID) REFERENCES PUBLIC.PRODUCT(PRODUCT_ID) ('7506a4b0-296d-4040-96c1-f1cfac96cce3')"; SQL statement:
delete from product where product_id=? [23503-200]
	at org.h2.message.DbException.getJdbcSQLException(DbException.java:459) ~[h2-1.4.200.jar:1.4.200]
	at org.h2.engine.SessionRemote.done(SessionRemote.java:611) ~[h2-1.4.200.jar:1.4.200]
	at org.h2.command.CommandRemote.executeUpdate(CommandRemote.java:237) ~[h2-1.4.200.jar:1.4.200]
	at org.h2.jdbc.JdbcPreparedStatement.executeUpdateInternal(JdbcPreparedStatement.java:191) ~[h2-1.4.200.jar:1.4.200]
	at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:152) ~[h2-1.4.200.jar:1.4.200]

그럼 REMOVE를 넣어보자.

    @OneToMany(mappedBy = "product" , targetEntity = Item.class , fetch = FetchType.LAZY , cascade = {CascadeType.REFRESH , CascadeType.PERSIST , CascadeType.MERGE , CascadeType.REMOVE})
    private List<Item> items = new ArrayList<Item>();
2021-03-14 00:01:51.810 DEBUG 18092 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    delete 
    from
        item 
    where
        item_id=?
Hibernate: 
    delete 
    from
        item 
    where
        item_id=?
2021-03-14 00:01:51.811 TRACE 18092 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [7bbaabc2-726a-4c69-9961-0e3624804668]
2021-03-14 00:01:51.815 DEBUG 18092 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    delete 
    from
        item 
    where
        item_id=?
Hibernate: 
    delete 
    from
        item 
    where
        item_id=?
2021-03-14 00:01:51.815 TRACE 18092 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [9ffb5ff0-b43a-4461-82c2-26947f65890c]
2021-03-14 00:01:51.816 DEBUG 18092 --- [nio-8080-exec-1] org.hibernate.SQL                        : 
    delete 
    from
        product 
    where
        product_id=?
Hibernate: 
    delete 
    from
        product 
    where
        product_id=?
2021-03-14 00:01:51.816 TRACE 18092 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [7506a4b0-296d-4040-96c1-f1cfac96cce3]
2021-03-14 00:01:51.846 DEBUG 18092 --- [nio-8080-exec-1] o.h.e.jdbc.internal.JdbcCoordinatorImpl  : HHH000420: Closing un-released batch

 

정상적으로 Item까지 삭제가 되면서 Product가 삭제되는것을 확인 할 수 있다.

 

#orphanRemoval 옵션에 대해 알아보자.  orphanRemoval 옵션이 없으면 어떤 현상이 생기는지 아래 코드로 테스트 해보자.

    @Transactional
    public void saveProductOrphan(String productId) throws Exception {
    	Product product = productRepo.findById(productId).get();
    	product.getItems().remove(0);
    	
        productRepo.save(product);
    }

소스에서 보듯이 Product와 연결된 Item중 하나를 연결을 끊었다. Product에서 변화가 생기지도 않았고 

그렇다고 연결된 Item list에서 엔티티 내의 데이터가 변경된것이 아니기 때문에 아무런 쿼리를 생성하지 않게 된다.

 

그럼 실제 삭제된 Item 한개가 없는 상태로 관리가 되어 질것이고 여기서 DB와 엔티티의 싱크가 깨지게 되는것이다.

2021-03-14 00:26:27.192 DEBUG 27324 --- [nio-8080-exec-4] o.hibernate.internal.util.EntityPrinter  : com.devracoon.jpa.entity.Product{productId=e2bb7b24-70f6-4ba8-ab9e-55c840a69674, items=[com.devracoon.jpa.entity.Item#405228ed-6217-44d1-a533-99c4d1104ae6], productName=ProductTest3}
2021-03-14 00:26:27.192 DEBUG 27324 --- [nio-8080-exec-4] o.hibernate.internal.util.EntityPrinter  : com.devracoon.jpa.entity.Item{itemId=405228ed-6217-44d1-a533-99c4d1104ae6, itemNumber=Product Item 1 Number, itemName=Product Item 1, product=com.devracoon.jpa.entity.Product#e2bb7b24-70f6-4ba8-ab9e-55c840a69674}
2021-03-14 00:26:27.193 DEBUG 27324 --- [nio-8080-exec-4] o.hibernate.internal.util.EntityPrinter  : com.devracoon.jpa.entity.Item{itemId=04f1a742-e5bf-4579-8847-2b257516920d, itemNumber=Product Item 0 Number, itemName=Product Item 0, product=com.devracoon.jpa.entity.Product#e2bb7b24-70f6-4ba8-ab9e-55c840a69674}

이제 orphanRemoval=true를 넣고 다시 테스트해 보자.

    @OneToMany(mappedBy = "product" , targetEntity = Item.class , fetch = FetchType.LAZY , cascade = {CascadeType.REFRESH , CascadeType.PERSIST , CascadeType.MERGE , CascadeType.REMOVE} , orphanRemoval = true)
    private List<Item> items = new ArrayList<Item>();

 

2021-03-14 00:30:20.650 DEBUG 2848 --- [nio-8080-exec-2] org.hibernate.SQL                        : 
    delete 
    from
        item 
    where
        item_id=?
Hibernate: 
    delete 
    from
        item 
    where
        item_id=?
2021-03-14 00:30:20.650 TRACE 2848 --- [nio-8080-exec-2] o.h.type.descriptor.sql.BasicBinder      : binding parameter [1] as [VARCHAR] - [f4894a22-a6d5-46ab-80f6-e2769e190e62]
2021-03-14 00:30:20.655 DEBUG 2848 --- [nio-8080-exec-2] o.h.e.jdbc.internal.JdbcCoordinatorImpl  : HHH000420: Closing un-released batch

 

로그에서 보다시피 삭제된 Item 하나의 삭제 쿼리가 발생하는것을 확인 할 수 있다. 

 

여기까지 OneToMany에서 사용되는 Property들에 대해 알아 봤다. 

생각보다 많은 테스트와 시간이 필요하고 좀 더 깊이 있게 알아야겠다고 느끼는 과정이였다.