전체 글 318

SQL 쿼리 힌트 사용

SQL 힌트(데이터베이스 벤더에게 제공하는 힌트)를 사용하려면 하이버네이트를 직접 사용해야 한다. 오라클이 아닌 다른 데이터베이스에서 SQL 힌트를 사용하려면 각 방언에서 org.hibernate.dialect.Dialect.getQueryHintString() 메소드를 오버라이딩해서 기능을 구현해야 한다. Session session = em.unwrap(Session.class); List list = session.createQuery("SELECT m FROM Member m") .addQueryHint("FULL (MEMBER)") .list();

JVM/JPA 2023.07.04

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

영속성 컨텍스트는 변경 감지를 위해 스냅샷 인스턴스를 보관하므로 더 많은 메모리를 사용하는 단점이 있다. 다시 조회할 일도 수정할 일도 없이 딱 한 번만 읽어서 화면에 출력하면 될 때는 읽기 전용으로 엔티티를 조회하면 메모리 사용량을 최적화할 수 있다. 메모리를 최적화하려면 하이버네이트가 제공하는 읽기 전용 쿼리 힌트를 사용하고, 플러시 호출을 막아서 속도를 최적화하려면 읽기 전용 트랜잭션을 사용하는 것이 가장 효과적이다. 스칼라 타입으로 조회 스칼라 타입은 영속성 컨텍스트가 결과를 관리하지 않는다. SELECT o.id, o.name, o.price FROM Order o 읽기 전용 쿼리 힌트 사용 하이버네이트 전용 힌트인 org.hibernate.readOnly를 사용하면 읽기 전용으로 조회할 수 있..

JVM/JPA 2023.07.04

N+1 문제

N+1 문제 연관된 엔티티를 조회할 때 SQL문이 실행되는 것을 N+1 문제라고 한다. 즉시 로딩을 사용할 때도 JPQL은 JPQL만 사용해서 SQL을 생성하기에 N+1 문제가 발생할 수 있다. 모두 지연 로딩으로 설정하고 성능 최적화가 꼭 필요한 곳에는 JPQL 페치 조인을 사용하자. 페치 조인 사용 N+1 문제를 해결하는 가장 일반적인 방법은 페치 조인을 사용하는 것이다. 이때 조심해야 할 것은 일대다 조인일 경우 중복된 결과가 나타날 수 있으므로 JPQL의 DISTINCT를 사용해서 중복을 제거하는 것이다. 하이버네이트 @BatchSize 연관된 엔티티를 조회할 때 지정한 size만큼 SQL의 IN 절을 사용해서 조회한다. 만약 조회한 회원이 10명인데 size=5로 지정하면 2번의 SQL만 추가로..

JVM/JPA 2023.07.04

영속성 컨텍스트와 프록시

영속 엔티티의 동일성 보장 해당 코드에 refMember와 findMember는 모두 프록시 객체로 같은 인스턴스이다. 이는 영속 엔티티의 동일성을 보장하기 위함이다. 반대로 엔티티를 먼저 조회후 프록시를 조회하면 모두 엔티티이다. @Test void 영속성컨텍스트와_프록시() { Member member = new Member("member1", "회원1"); em.persist(member); em.flush(); em.clear(); Member refMember = em.getReference(Member.class, "member1"); Member findMember = em.find(Member.class, "member1"); Assertions.assertSame(refMember, fi..

JVM/JPA 2023.07.03

JPA 예외 처리

JPQ 표준 예외 javax.persistence.PersistenceException의 자식 클래스다. 그리고 이 예외 클래스는 RuntimeException의 자식이다. 트랜잭션 롤백을 표시하는 예외는 심각한 예외이므로 복구해선 안 된다. 이 예외가 발생하면 트랜잭션을 강제로 커밋해도 트랜잭션이 커밋되지 않고 대신에 javax.persistence.RollbackException 예외가 발생한다. 트랜잭션 롤백을 표시하지 않는 예외는 심각한 예외가 아니다. 따라서 개발자가 트랜잭션을 커밋할지 롤백할지를 판단하면 된다. 트랜잭션 롤백을 표시하는 예외 트랜잭션 롤백을 표시하는 예외 설명 EntityExistsException 저장 중에 이미 같은 엔티티가 있으면 발생 EntityNotFoundExcept..

JVM/JPA 2023.07.03

엔티티 그래프

엔티티를 조회할 때 연관된 엔티티를 함께 조회할 필요가 있으면 JPQL의 페치 조인을 사용한다. 그런데 페치 조인을 사용하면 같은 JPQL을 중복해서 작성하는 경우가 많다. 이는 JPQL이 엔티티를 조회할 뿐 아니라 연관된 엔티티를 함께 조회하는 기능도 제공하기 때문이다. JPA 2.1에 추가된 엔티티 그래프 기능을 사용하면 엔티티를 조회하는 시점에 함께 조회할 연관된 엔티티를 선택할 수 있다. 따라서 JPQL은 데이터를 조회하는 기능만 수행하면 되고 연관된 엔티티를 함께 조회하는 기능은 엔티티 그래프를 사용하면 된다. 그러므로 엔티티 그래프 기능을 적용하면 다음 JPQL만 사용하면 된다. SELECT o FROM Order o WHERE o.status = ? Named 엔티티 그래프 @NamedEnt..

JVM/JPA 2023.07.03

리스너

엔티티의 생명주기에 따른 이벤트를 처리하는데 사용된다. 이벤트를 잘 활용하면 대부분의 엔티티에 공통으로 적용하는 등록 일자, 수정 일자 처리와 해당 엔티티를 누가 등록하고 수정했는지에 대한 기록을 리스너 하나로 처리할 수 있다. 이벤트 종류 종류 설명 PostLoad 엔티티가 영속성 컨텍스트에 조회된 직후 또는 refresh를 호출한 후 (2차 캐시에 저장되어 있어도 호출된다.) PrePersist persist 메소드를 호출해서 엔티티를 영속성 컨텍스트에 관리하기 직전에 호출된다. 식별자 생성 전략을 사용한 경우 엔티티에 식별자는 아직 존재하지 않는다. 새로운 인스턴스를 merge할 때도 수행된다. PreUpdate flush나 commit을 호출해서 엔티티를 데이터베이스에 수정하기 직전에 호출된다. ..

JVM/JPA 2023.07.02

@Converter

객체 필드로 Boolean을 사용하고 데이터베이스에서는 Y, N으로 저장하고 싶을 때 사용한다. // 클래스 또는 필드 한곳에 지정 @Entity @Convert(converter=BooleanToYNConverter.class, attributeName = "vip") public class Member { @Id private String id; @Convert(converter=BooleanToYNConverter.class) private boolean vip; } @Converter public class BooleanToYNConverter implements AttributeConverter { @Override public String convertToDatabaseColumn(Boolea..

JVM/JPA 2023.07.02

컬렉션

하이버네이트는 컬렉션을 효율적으로 관리하기 위해 엔티티를 영속 상태로 만들 때 원본 컬렉션을 감싸고 있는 내장 컬렉션을 생성해서 이 내장 컬렉션을 사용하도록 참조를 변경한다. 하이버네이트가 제공하는 내장 컬렉션(org.hibernate.collection.internal.PersistentBag)은 원본 컬렉션을 감싸고 있어서 래퍼 컬렉션으로도 부른다. 하이버네이트는 이런 특징 때문에 컬렉션을 사용할 때 다음처럼 즉시 초기화해서 사용하는 것을 권장한다. Collection members = new ArrayList(); 하이버네이트 내장 컬렉션과 특징 컬렉션 인터페이스 내장 컬렉션 중복 허용 순서 보관 Collection, List PersistentBag O X Set PersistentSet X X ..

JVM/JPA 2023.07.02

스프링 데이터 JPA와 QueryDSL 통합

QueryDslPredicateExecutor QueryDSL을 검색조건으로 사용하면서 스프링 데이터 JPA가 제공하는 페이징과 정렬 기능도 함께 사용할 수 있다. 하지만 join, fetch를 사용할 수 없다. // 상속 public interface ItemRepository extends JpaRepository, QueryDslPredicateExecutor{} // 사용 QMember member = QMember.member; Iterable result = memberRepository.findAll( member.name.contains("김").and(member.age.between(10, 20)) ); public interface QuerydslPredicateExecutor { O..

JVM/JPA 2023.07.02
728x90