티스토리 뷰

JPA

TIL JPA

perseverance 2023. 3. 28. 01:31

JPA 인프런 강의를 들으면서 궁금했던 점들을 정리하고자 한다.

1. @Transactional없이 JPA를 실행 할 수 있을까?

JPA는 데이터베이스 작업을 수행하기 전에 트랜잭션이 활성화되어 있는지 확인한다. 만약 트랜잭션이 활성화되어 있지 않으면 JPA는 자동으로 트랜잭션을 시작한다. 따라서 @Transactional 어노테이션이 없어도 JPA를 사용하여 데이터베이스 작업을 수행할 수 있다. 하지만 이 경우 JPA가 자동으로 시작한 트랜잭션은 해당 데이터베이스 작업이 완료된 후 자동으로 커밋된다.

2. 영속성 컨텍스트는 트랜잭션이 종료되면 같이 종료될까?

@GetMapping("/api/v1/simple-orders")
public List<Order> ordersV1() {
        List<Order> all = orderRepository.findAllByCriteria(new OrderSearch());
        System.out.println(all.get(0).getMember().getName());
        return all;
}

이렇게하면 orderRepository.findAllByCriteria로 쿼리한번 나가고
all.get(0).getMember().getName()에서도 쿼리가 나가게 된다.
orderRepository.findAllByCriteria에서 이미 트랜잭션이 종료되서 영속성 컨텍스트도 종료되서 지연 로딩 쿼리가 나갈 일이 없는데 왜 쿼리가 나가게 되는걸까 ?
이유는 OSIV 때문
OSIV(Open Session In View)는 하이버네이트의 세션을 HTTP 요청의 시작부터 끝까지 열어두는 기능이다. 이 기능을 사용하면 트랜잭션이 끝나도 영속성 컨텍스트가 종료되지 않고 유지된다. 따라서 지연 로딩이 발생할 수 있다.

OSIV가 꺼진 상태에서는 트랜잭션이 시작될 때 영속성 컨텍스트가 생성되고, 트랜잭션이 끝나면 영속성 컨텍스트도 함께 종료된다. 하지만 이 경우 JPA가 자동으로 시작한 트랜잭션은 해당 데이터베이스 작업이 완료된 후 자동으로 커밋된다. 따라서 조회된 엔티티는 준영속 상태가 돤다.
준영속 상태의 엔티티를 참조하면서 지연 로딩을 시도하면 _LazyInitializationException_이 발생한다. 이는 준영속 상태의 엔티티가 영속성 컨텍스트와 더 이상 관련이 없기 때문이다. 따라서 지연 로딩을 사용하려면 영속성 컨텍스트가 유지되어야 한다.

영속상태와 준영속 상태 차이

영속 상태는 엔티티가 영속성 컨텍스트에 의해 관리되는 상태를 말한다. 영속 상태의 엔티티는 데이터베이스와 동기화되며, 변경 감지(dirty checking)와 지연 로딩(lazy loading) 등의 기능을 사용할 수 있다.

준영속 상태는 엔티티가 영속성 컨텍스트에 의해 관리되지 않는 상태를 말한다. 준영속 상태의 엔티티는 데이터베이스와 동기화되지 않으며, 변경 감지와 지연 로딩 등의 기능을 사용할 수 없다.

3. Spring에서 Order Entity를 Jackson을 통해 JSON으로 변경할때 에러가 발생하는 이유

무한루프 에러

Jackson은 객체를 JSON으로 변환할 때 객체의 getter 메서드를 사용한다. Order와 Member가 양방향 관계이므로 Order의 getter 메서드에서 Member를 반환하고, Member의 getter 메서드에서 다시 Order를 반환하게 되어 무한 루프가 발생한다. 이 문제를 해결하기 위해서는 한 쪽의 getter 메서드에서 @JsonIgnore 어노테이션을 사용하여 JSON 변환 시 해당 필드를 무시하도록 설정하면 된다.

com.fasterxml.jackson.databind.exc.InvalidDefinitionException 에러

이 에러는 일반적으로 Jackson이 특정 타입의 객체를 직렬화하거나 역직렬화할 수 없을 때 발생한다.
일반적으로 이 에러가 발생하는 원인으로는 다음과 같은 것들이 있다.

  1. 객체의 생성자가 없거나 접근 제한자가 private인 경우
  2. 객체의 필드에 대한 getter/setter 메서드가 없는 경우
  3. 객체의 타입이 직렬화/역직렬화를 지원하지 않는 경우

이러한 문제를 해결하려면 객체에 기본 생성자를 추가하거나, getter/setter 메서드를 추가하거나, 직렬화/역직렬화를 지원하는 타입으로 변경해야 한다

직렬화와 역직렬화

직렬화(Serialization)는 객체의 상태를 바이트 스트림으로 변환하는 과정을 말한다. 직렬화된 객체는 파일이나 네트워크를 통해 전송하거나 저장할 수 있다. 직렬화된 객체는 역직렬화(Deserialization)를 통해 다시 원래의 객체로 복원할 수 있다.
역직렬화(Deserialization)는 직렬화된 바이트 스트림을 다시 원래의 객체로 복원하는 과정을 말한다. 역직렬화를 통해 저장되었던 객체의 상태를 복원하거나, 전송된 객체를 수신하여 사용할 수 있다.

4. CASCADE

CascadeType.PERSIST

Parnet 엔티티와 Child 엔티티가 서로 일대다 관계일때
Parent class

@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private List<Child> childList = new ArrayList<>();

public void addChild(Child child) {  
    childList.add(child);  
    child.setParent(this);  
}

Parent 객체를 만들어서 addChild를 이용해 child 객체를 넣어준 후 Parent를 em.persist를 이용해 저장해주면 Parent.childList의 child역시 insert된다.
주의사항
이때 addChild에 연관관계주인인 child에 parent를 안넣어주면 child는 db에 저장은 되지만 parent외래키가 지정이 안되고 null값이 들어가게 된다.
-> CascadeType.PERSIST는 영속성만 관리하지 연관관계를 매핑하는 것과 아무 관련이 없다.

CascadeType.REMOVE

Parent class

@OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
private List<Child> childList = new ArrayList<>();

public void addChild(Child child) {
    childList.add(child);
    child.setParent(this);
}

Parent를 em.remove로 삭제시 parent_id를 외래키로 들고있는 child 역시 같이 삭제된다. CascadeType.REMOVE 없이 parent를 삭제한다면 해당 parent_id를 외래키로 들고있는 child가 있다면 오류가 발생한다.

orphanRemoval = true

Parent class

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Child> childList = new ArrayList<>();
public void addChild(Child child) {  
    childList.add(child);  
    child.setParent(this);  
}

parent의 childList에서 child를 삭제하면 해당 child는 db에서 삭제된다.
CascadeType.REMOVE와 다른점은 CascadeType.REMOVE 은 parent 삭제시 parent와 연관되어져 있는 child도 다 같이 삭제되고
orphanRemoval는 parent의 삭제가 아니라 parent.childList에서 제거된 child를 db에 삭제시킨다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
글 보관함