ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] 15장. 고급 주제와 성능 최적화
    book/자바 ORM 표준 JPA 프로그래밍 2020. 11. 28. 18:56

    15.1 예외처리

     

    1.1 JPA 표준 예외 정리

    JPA 표준 예외들은 javax.persistence.PersistenceException의 자식 클래스다.

    그리고 이 예외 클래스는 RuntimeException의 자식이다.

    따라서 JPA 표준 예외들은 언체크 예외다.

    언체크 예외 : 2020/08/21 - [study/java] - [JAVA] Exception

     

    PersistenceException

     

    JPA 표준 예외는 크게 2가지로 나눌 수 있다.

    - 트랜잭션 롤백을 표시하는 예외

    - 트랜잭션 롤백을 표시하지 않는 예외

     

    트랜잭션 롤백을 표시하는 예외는 심각한 예외이므로 복구해서는 안된다.

    이 예외가 발생한 경우 트랜잭션을 강제로 커밋해도 커밋되지 않고 RollbackException이 발생한다.

     

    1.2. 스프링 프레임워크의 JPA 예외 변환.

    서비스 계층에서 데이터 접근 계층의 구현 기술에 의존하는 것은 그리 좋은 설계가 아니다.

    스프링 프레임워크는 데이터 접근 계층에 대한 예외를 추상화해서 개발자에게 제공해준다.

    JPA 예외스프링 변환 예외

    javax.persistence.PersistenceException org.springframework.orm.jpa.JpaSystemException
    javax.persistence.NoResultException org.springframework.dao.EmptyResultDataAccessException
    javax.persistence.EntityNotFoundException org.springframework.orm.jpa.JpaOptimisticLockingException
    javax.persistence.RollbackException org.springframework.transaction.TransactionRequiredException
    javax.persistence.EntityExistsException org.springframework.dao.DataIntegrityViolationException

     

    1.3 스프링 프레임워크에 JPA 예외 변환기

    JPA 예외를 스프링 프레임워크가 제공하는 추상화된 예외로 변경하려면 PersistenceExceptionTranslationPostProcessor를 스프링 빈으로 등록하면 된다.

    @Repository 어노테이션을 사용한 곳에 예외 변환 AOP를 적용해서 JPA 예외를 스프링 프레임워크가 추상화한 예외로 변환해준다.

    @Repository
    public class NoResultExceptionTestRepository {
        @PersistenceContext
        EntityManager em;
    
        public Member findMember(){
            //조회된 데이터가 없는 경우
            return em.createQuery("select m from Member m", Member.class)
                    .getSingleResult();
        }
    }

     

    HibernateJpaDialect 클래스
    EntityManagerFactoryUtils

     

    만약 예외를 변화하지 않고 그대로 반환하고 싶다면 다음처럼 throws 절에 그대로 반환할 JPA 예외나 그의 부모 클래스를 직접 명시하면 된다.

    @Repository
    public class NoResultExceptionTestService {
    
        @PersistenceContext
        EntityManager em;
    
        public Member findMember() throws NoResultException {
            //조회된 데이터가 없는 경우
            return em.createQuery("select m from Member m", Member.class)
                    .getSingleResult();
        }
    }
    

     

     

    1.4 트랜션 롤백시 주의사항

    트랜잭션을 롤백하는 것은 데이터베이스의 반영사항만 롤백하는 것이지 수정한 자바 객체까지 원상태로 복구해주지는 않는다.

    예를 들어 엔티티를 조회해서 수정한다고 가정해보자.

    엔티티를 수정하는 과정에서 문제가 발생하여 트랜잭션을 롤백을 하면 데이터베이스의 데이터는 원래대로 복구가 되지만 객체는 수정된 상태로 영속성 컨텍스트에 남아있다.

    따라서 트랜잭션이 롤백된 영속성 컨텍스트를 그대로 사용하는 것은 위험하다.

    새로운 영속성 컨텍스트를 생성하여 사용하거나 clear()를 호출해서 초기화한 다음에 사용한다.

     

    스프링 프레임워크는 이런 문제를 예방하기 위해 영속성 컨텍스트의 범위에 따라 다른 방법을 사용한다.

    기본 전략인 트랜잭션당 영속성 컨텍스트 전략에서는 트랜잭션이 롤백하면서 영속성 컨텍스트도 종료하므로 문제가 없다.

    문제는 OSIV처럼 영속성 컨텍스트의 범위를 트랜잭션 범위보다 넓게 사용해서 여러 트랜잭션이 하나의 영속성 컨텍스트를 사용할 때 발생한다.

    트랜잭션을 롤백해서 영속성 컨텍스트에 이상이 발생해도 다른 트랜잭션에서 해당 영속성 컨텍스트를 그대로 사용하는 문제가 있다.

     

    스프링 프레임워크는 영속성 컨텍스트의 범위를 트랜잭션의 범위보다 넓게 설정하면 롤백시 영속성 컨텍스트를 초기화해서 잘못된 영속성 컨텍스트를 사용하는 문제를 예방한다.

    JpaTransactionManager

     

    15.2 엔티티 비교

    영속성 컨텍스트 내부에는 엔티티 인스턴스를 보관하기 위한 1차 캐시가 있다.

    1차 캐시는 영속성 컨텍스트와 생명주기를 같이한다.

     

    영속성 컨텍스트를 더 정확히 이해하기 위해서는 1차 캐시의 가장 큰 장점인 애플리케이션 수준의 반복 가능한 읽기를 이해해야 한다.

    같은 영속성 컨텍스트에서 엔티티를 조회하면 다음 코드와 같이 항상 같은 엔티티 인스턴스를 반환한다.

    단순 동등성 비교 수준이 아니라 정말 주소값이 같은 인스턴스를 반환한다.

     

    2.1 영속성 컨텍스트가 같을 때 엔티티 비교

    메서드를 호출하는 쪽에서 Transactional 어노테이션을 선언했기 때문에 위 코드는 항상 같은 트랜잭션과 같은 영속성 컨텍스트에 접근한다.

    저장한 회원과 회원 레포지토리에서 찾아온 엔티티가 완전히 같은 인스턴스라는 점을 알 수 있다.

    영속성 컨텍스트가 같으면 엔티티를 비교할 때 다음 3가지 조건을 모두 만족한다.

    - 동일성 : == 비교가 같다.

    - 동등성 : equals() 비교가 같다.

    - 데이터베이스 동등성 : @Id인 데이터베이스 식별자가 같다.

     

    2.2 영속성 컨텍스트가 다를 때 엔티티 비교

    위의 예제와는 다르게 Transactional 을 사용하지 않는다면

    서비스 클래스에서 트랜잭션이 시작되게 된다.

    서비스와 레포지토리의 클래스의 메서드를 사용할 때 서로 다른 트랜잭션이기 때문에 다른 영속성 컨텍스트를 사용하게 된다.

    서비스 계층이 끝날 때 트랜잭션이 커밋되면서 영속성 컨텍스트는 플러시가 되고 영송석 컨텍스트는 종료된다.

    따라는 member 객체는 준영속 상태가 되는데 레포지토리 계층에서 새로운 트랜잭션이 시작되면서 새로운 영속성 컨텍스트가 생성된다.

     

    앞서 보았듯이 같은 영속성 컨텍스트를 보장하면 동일성 비교만으로 충분하다.

    영속성 컨텍스트가 달라지면 동일성 비교는 실패한다.

    동일성비교 대신 데이터베이스 동등성 비교를 사용해보자.

    member.getId().equals(findMember.getId()) // 데이터베이스 식별자 비교

    데이터베이스 동등성 비교는 엔티티를 영속화해양 식별자를 얻을 수 있다는 문제가 있다.

    엔티티를 영속화하기 전에는 식별자 값이 null이므로 정확한 비교를 할 수 없다.

    따라서 엔티티를 비교할 때는 비즈니스키를 활용한 동등성 비교(equals)를 권장한다.

    비즈니스 키가 되는 필드는 보통 중복되지 않ㄱ고 거의 변하지 않는 데이터베이스 기본 키 후보들이 좋은 대상이다.

     

    15.3 프록시 심화 주제

    프록시는 원본 엔티티를 상속받아서 만들어지므로 엔티티를 사용하는 클라이언트는 엔티티가 프록시인지 원본인지 구분없이 사용할 수 있다.

    하지만 프록시를 사용하는 방식의 기술적한계로 인해 예상치 못한 문제가 발생할 수도 있다.

     

    3.1 영속성 컨텍스트와 프록시

    영속성 컨텍스트는 자신이 관리하는 영속 엔티티의 동일성을 보장한다.

    그럼 프록시로 조회한 엔티티의 동일성도 보장할까?

     

    영속성 컨텍스트는 프록시로 조회된 엔티티에 대해서 같은 엔티티를 찾는 요청이 오면 원본 엔티티가 아닌 처음 조회된 프록시를 반환한다.

    객체를 출력한 결과를 보면 프록시로 조회된 것을 확인할 수 있다.

     

    반대로 원본을 먼저 조회하고 프록시를 조회해보자

    결과는 다음과 같다.

    원본 엔티티를 먼저 조회하면 영속성 컨텍스트는 이미 원본을 데이터베이스에서 조회했으므로 프록시를 반환할 이유가 없다.

    따라서 em.getReference()를 호출해도 프록시가 아닌 원본을 반환한다.

     

    3.2 프록시 타입 비교

    프록시는 원본 엔티티를 상속받아서 만들어지므로 프록시로 조회한 엔티티의 타입을 비교할 때는 == 비교를 하면 안되고 대신 instanceof를 사용해야 한다.

     

    3.3 프록시 동등성 비교

    엔티티 동등서 비교는 비즈니스 키를 사용해서 equals() 메서드를 사용하면 된다.

    그런데 IDE나 외부 라이브러리를 사용해서 구현한 equals 메서드로 엔티티를 비교할 때 비교 대상이 원본이면 문제가 없지만 프록시면 문제가 발생할 수 있다.

        @Override
        public boolean equals(Object obj) {        
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (this.getClass() != obj.getClass())
                return false;
            
            Member member = (Member) obj;
            
            if (name == null ? !name.equals(member.name) : member.name != null)
            	return false;
        }

    프록시 객체를 조회해서 위 equals 메서드를 사용하면 false라 반환이 된다.

    원본 데이터를 비교하면 true를 반환한다.

    왜 그럴까?

    위에서 언급했던 것과 같이 프록시는 instanceof를 사용해야한다.

    따라서 위에서 !=가 아닌 instanceof로 바꿔서 사용해야 한다.

    if(!(obj instanceof Member)) 
    	return false;

     

     if (name == null ? !name.equals(member.name) : member.name != null)
        return false;

    이 부분에서도 예외가 발생한다.

    member는 내부에 있는 name을 통해서 비교하지만 프록시는 실제 데이터를 가지고 있지 않다.

    따라서 프록시의 멤버 변수에 직접 접근하면 아무 값도 조회할 수 없다.

    name 멤버 변수가 private 이므로 일반적으로는 프록시의 멤버 변수에 접근하는데 아무 문제가 없지만 equals() 메서드는 자신을 비교하기 때문에 private여도 접근할 수 있다.

    따라서 getter를 통해서 접근해야한다.

     if (name == null ? !name.equals(member.getName()) : member.getName() != null)
        return false;

     

    3.4 상속관계와 프록시

    프록시를 부모 타입으로 조회하면 문제가 발생한다.

    @Test
    public void 부모타입으로_프록시조회(){
    
        Book saveBook = new Book;
        saveBook.setName("jpaBook");
        saveBook.setAuthor("kim");
        em.persist(saveBook);
    
        em.flush();
        em.clear();
    
        Item proxyItem = em.getRefence(Item.class, saveBook.getId());
    
        if(proxyItem instaceof Book){
            System.out.println("proxyItem instanceof Book");
            Book book = (Book) proxyItem; //java.lang.ClassCastException
            System.out.println("책 저자 =" + book.getAuthor);
        }
    
         Assert.assertFalse(proxyItem.getClass == Book.class);
         Assert.assertFalse(proxyItem instanceof Book); //false
         Assert.assertTrue(proxyItem instanceof Item);
    }

    Item은 부모, Book은 자식이다.

    코드에서 조회하려고 하는 엔티티는 Book 이므로 Book 기반으로 코드를 완성시켰는데 em.getReference() 메서드에서는 Item 엔티티를 대상으로 조회했으므로 프록시인 proxyItem은 Item 타입을 기반으로 만들어진다.

    따라서 위의 조건문은 false가 되어 실행되지 않게 된다.

    true가 되어서 조건문 내의 실행문이 실행된다고 하더라도 캐스팅을 하는 과정에서 ClassCastException이 발생할 수 있다.

    결과적으로 프록시는 하위 타임으로 다운 캐스팅할 수 없다는 것을 알 수 있다.

     

    이를 해결하기 위한 방법이 몇가지 있다.

     

    1) JPQL로 대상 직접 조회

    가장 간단한 방법은 처음부터 자식 타입을 직접 조회해서 필요한 연산을 하면 된다.

     

    2) 프록시 벗기기

    public static <T> unProxy(Object entity){
      if(entity instanceof HibernateProxy) {
          entity = ((HibernateProxy) entity).getHibernateLazyIntializer()
                                          .getImplementation();
      }
    
      return (T) entity;
    }

    하이버네이트가 제공하는 unProxy라는 메서드를 사용하면 원본 엔티티를 가져올 수 있다.

    영속성 컨텍스트는 한 번 프록시로 노출한 엔티티는 계속 프록시로 노출한다.

    그래야 영속성 컨텍스트가 영속 엔티티의 동일성을 보장할 수 있다.

    그런데 이 방법은 프록시에서 원본을 직접 꺼내기 때문에 프록시와 원본 엔티티의 동일성 비교가 실패한다.

    이 방법을 사용하는 경우는 원본 엔티티가 필요한 곳에 잠깐 사용하고 버리는 것이 좋다.

     

    3) 기능을 위한 별도의 인터페이스 제공

    특정 작업을 수행하는 공통 인터페이스를 만들고 자식 클래스들이 인터페이스의 메서드를 구현하여 해결할 수 있다.

    그렇게 하면 구현체에 따라 각각 다른 작업이 수행된다.

     

    4) 비지터 패턴 사용

    비지터 패턴을 사용해서 상속관계와 프록시 문제를 해결할 수 있다.

    비지터 패턴 : dailyheumsi.tistory.com/216

    비지터 패턴을 사용하면 프록시에 대한 걱정없이 안전하게 원본 엔티티에 접근할 수 있고 instanceof나 타입 캐스팅없이 코드를 구현할 수 있는 장점이 있다.

    단 너무 복잡하고 더블 디스패치를 사용하기 때문에 이해하기 어렵다는 단점도 있다.

    더블 디스패치 : wonwoo.ml/index.php/post/1490

     

     

     

    15.4 성능 최적화

     

    4.1 N+1 문제

    jojoldu.tistory.com/165

    회원과 주문이 즉시로딩으로 설정되어 연관관계가 맺여진 상태일 때 회원을 조회하게 되면 그 회원이 조회한 주문의 목록들도 함께 조회가 된다.

    조회를 할 때 SQL을 두 번 실행하는 것이 아니고 조인을 사용해서 한 번의 SQL로 회원과 주문 정보를 조회한다.

    여기까지만 보면 즉시로딩이 좋아보이지만 JPQL을 사용할 때 문제가 발생한다.

    List<Membr> members =
        em.createQuery("select m from Member m". Member.class)
        .getResultList();

    JPQL을 실행하면 JPA는 JPQL을 분석하여 SQL을 생성한다.

    그런데 이때 즉시 로딩, 지연 로딩에 대해서는 전혀 신경쓰지 않고 JPQL만을 이용해서 SQL을 생성한다.

    SELECT * FROM MEMBER;

    SQL의 실행 결과로 회원 엔티티를 애플리케이션에 로딩한다.

    그런데 회원 엔티티와 주문 컬렉션이 즉시 로딩으로 설정되어 있으므로 JPA는 주문 컬렉션을 즉시 로딩하려고 다음 SQL을 추가로 실행한다.

    SELECT * FROM ORDERS WHERE MEMBER_ID=?

    조회한 회원이 하나면 2번의 SQL을 실행하겠지만 조회한 회원이 많아질수록 문제가 커진다.

    이처럼 처음 실행한 SQL의 결과 수만큼 추가로 SQL을 실행하는 것을 N+1 문제라고 한다.

     

    지연 로딩을 사용해도 N+1문제에 완전히 벗어날 수는 없다.

    N+1 문제를 해결할 수 있는 방법에 대해서 알아보자

     

    1) 페치 조인 사용

    가장 일반적인 방법은 페치 조인을 사용하는 것이다.

    페치조인을 사용하면 연관된 엔티티를 함께 조회하므로 N+1문제가 발생하지 않는다.

     

    2) 하이버네이트 @BatchSize

    하이버네이트가 제공하는 BatchSize라는 어노테이션을 사용하면 연관된 엔티티를 조회할 때 지정한 Size 만큼 SQL의 IN절을 사용해서 조회한다.

    만약 조회한 회원이 10명인데 size=5로 지정하면 2번의 SQL만 추가로 실행한다.

    SELECT * FROM ORDERS WHERE MEMBER_ID IN ( ?, ?, ?, ?, ?)

     

    3) 하이버네이트 @Fetch(FetchMode.SUBSELECT)

    Fetch 어노테이션에서 FetchMode를 SUBSELECT를 사용하면 연관된 데이터를 조회할 때 서브 쿼리를 사용해서 해결한다.

     

    4.2 읽기 전용 쿼리의 성능 최적화

    엔티티가 영속성 컨텍스트에 관리되면 1차 캐시부터 변경 감지까지 얻을 수 있는 혜택은 많지만 그만큼 변경 감지를 위해 스냅샷 인스턴스를 보관하므로 더 많은 메모리를 사용하는 단점이 있다.

    엔티티를 딱 한 번만 읽어서 출력하면 되는 경우에는 읽기 전용으로 엔티티를 조회하면 메모리 사용량을 최적화할 수 있다.

    1) 스칼라 타입으로 조회

    엔티티가 아닌 모든 필드의 형태로 조회하는 것이다.

    스칼라 타입은 영속성 컨텍스트가 관리하지 않는다.

    select o.id, o.name from order o

     

    2) 읽기 전용 쿼리 힌트 사용

    하이버네이트 전용 힌트인 readOnly를 사용하면 엔티티를 읽기 전용으로 조회할 수 있다.

    이는 읽기 전용이므로 스냅샷을 보관하지 않아 메모리 사용량을 최적화할 수 있다.

    단, 스냅샷이 없으므로 엔티티를 수정해도 데이터베이스에 반영되지 않는다.

     

    3) 읽기 전용 트랜잭션 사용

    트랜잭션을 읽기 전용 모드로 설정할 수 있다.

    트랜잭션에 readOnly=true 옵션만 주면 스프링 프레임워크가 하이버네이트 세션의 플러시 모드를 MANUAL로 설정하여 강제로 플로시를 호출하지 않는 한 플러시가 일어나지 않아 성능이 향상된다.

     

    4) 트랜잭션 밖에서 읽기

    트랜잭션 밖에서 읽는다는 것은 트랜잭션없이 엔티티를 조회한다는 뜻이다.

    JPA에서 데이터를 변경하려면 트랜잭션은 필수이므로 조회가 목적일때만 사용해야 한다.

    @Transactional(propagation = Propagation.NOT_SUPPORTED)

     

    스프링 프레임워크에서는 읽기 전용 트랜잭션을 사용하는 것이 편리하다.

     

    4.3 배치 처리

    수백만 건의 데이터를 처리해야하는 경우 일반적인 방식으로 엔티티를 계속 조회하면 영속성 컨텍스트에 아주 많은 엔티티가 쌓이면서 메모리 부족으로 오류가 발생한다.

    따라서 이런 배치 처리는 적절한 단위로 영속성 컨텍스트를 초기화해야한다.

    또한 2차 캐시를 사용하고 있다면 2차 캐시에 엔티티를 보관하지 않도록 주의해야 한다.

     

    1) JPA 등록 배치

    수천에서 수만 건 이상의 엔티티를 한 번에 등록할 때 주의할 점은 영속성 컨텍스트에 엔티티가 계속 쌓이지 않도록 일정 단위마다 영속성 컨텍스트의 엔티티를 데이터베이스에 플러시하고 영속성 컨텍스트를 초기화해야 한다.

    배치 처리는 아주 많은 데이터를 조회해서 수정하는 데 수많은 데이터를 한 번에 메모리에 올려둘 수 없어서 2가지 방법을 주로 사용한다.

    - 페이징 처리

    - 커서 : JPA는 JDBC 커서를 지원하지 않는다. 하이버네이트 세션을 사용해야 한다.

    더보기

    데이터베이스 커서(Cursor)는 일련의 데이터에 순차적으로 액세스할 때 검색 및 "현재 위치"를 포함하는 데이터 요소이다.

    하이버네이트는 scroll이라는 이름으로 JDBC 커서를 지원한다.

    Entitymanager em = entityManagerFactory.createEntityManager();
    EntityTransaction tx = em.getTransaction();
    Session session = em.unwrap(Session.class)
    tx.begin();
    ScrollableResults scroll = session.createQuery(“select p from Product p”)
                    .setCachMode(CacheMode.IGNORE)
                    .scroll(ScrollMode.FORWARD_ONLY);
    
    int count = 0;
    
    whild(scroll.next()){
        Product p = (Product) scroll.get(0);
        p.setPrice(p.getPrice() + 100);
    
        count++;
    
        if(count % 100 == 0) {
          session.flush();
          session.clear();
        }
     }
     tx.commit();
     session.close();

    scroll은 하이버네이트 전용 기능이므로 먼저 em.unwrap()메서드를 사용해서 하이버네이트 세션을 구한다

    다음으로 쿼리를 조회하면서 scroll 메서드로 ScrollableResults 객체를 반환받는다.

    이 객체의 next() 메서드를 호출하면 엔티티를 하나씩 조회할 수 있다.

     

    2) 하이버네이트 무상태 세션 사용

    하이버네이트는 무상태 세션이라는 특별한 기능을 제공한다.

    이름 그대로 무상태 세션은 영속성 컨텍스트를 만들지 않고 심지어 2차 캐시도 사용하지 않는다.

    무상태 세션은 영속성 컨텍스트가 없다

    엔티티를 수정하려면 무상태 세션이 제공하는 update()메서드를 직접 호춯해야 한다.

    SessionFactory sessionFactory = entityManagerFactory.unwrap(SessionFactory.class);
    StatelessSession session = sessionFactory.openStatelessSession();
    Transaction tx = session.beginTransaction();
    ScrollableResults scroll = session.createQuery("select p from Product p").scroll();
    
    while(scroll.next()) {
        Product p = (Product) scroll.get(0);
        p.setPrice(p.getPrice() + 100);
        session.update(p) //직접 update를 호출 
    }
    tx.commit();
    session.close();

     

    4.4 SQL 쿼리 힌트 사용

    JPA는 데이터베이스 SQL 힌트 기능을 제공하지 않기 때문에 하이버네이트를 직접 사용해야한다.

    Session session = em.unwrap(Session.class);
    
    List<Member> list = session.createQuery("select m from Member m")
                          .addQueryHint("FULL (MEMBER)")
                          .list();

     

    4.5 트랜잭션을 지원하는 쓰기 지연과 성능 최적화

    1) 트랜잭션을 지원하는 쓰기 지연과 JDBC 배치

    insert(member1);
    insert(member1);
    insert(member1);
    insert(member1);
    insert(member1);
    
    commit();

    네트워크 호출 한 번은 단순한 메서드를 수만 번 호출하는 것보다 더 큰 비용이 든다.

    위 코드는 5번의 삽입과 1번의 커밋으로 총 6번의 데이터베이스와 통신한다.

    이것을 최적화하려면 5번의 삽입 SQL을 모아서 한 번에 데이터베이스로 보내면 된다.

    JDBC가 제공하는 SQL 배치 기능을 사용하면 SQL을 모아서 데이터베이스에 한 번에 보낼 수 있다.

    SQL 배치 최적화 전략은 구현체마다 조금씩 다르다.

    <property name="hibnernate.jdbc.batch_size" value = "50" />

    속성을 50으로 주면 최대 50건씩 모아서 SQL 배치를 실행한다.

    하지만 SQL 배치는 같은 SQL일때만 유효하다.

    중간에 다른 처리가 들어가면 SQL 매치를 다시 시작한다.

    더보기

    엔티티가 영속 상태가 되려면 식별자가 꼭 필요하다.

    그런데 IDENTITY 식별자 생성 전략은 엔티티를 데이터베이스가 저장해야 식별자를 구할 수 있으므로 em.persist()를 호출하는 즉시 Insert SQL이 데이터베이스에 전달된다.

    따라서 쓰기 지연을 활용한 성능 최적화를 할 수 없다.

     

    2) 트랜잭션을 지원하는 쓰기 지연과 애플리케이션 확장성

    트랜잭션을 지원하는 쓰기 지연과 변경 감지 기능 덕에 성능과 개발이 편리했다.

    하지만 진짜 장점은 데이터베이스 테이블 로우에 락이 걸리는 시간을 최소화 한다는 점이다.

    이 기능은 트랜잭션을 커밋해서 영속성 컨텍스트가 플러시하기 전까지는 데이터베이스에 데이터를 등록, 수정, 삭제하지 않는다.

    따라서 커밋 직전까지 데이터베이스 로우에 락을 걸지 않는다.

     

    참고자료 : 자바 ORM 표준 JPA 프로그래밍

Designed by Tistory.