Reference. 자바 ORM 표준 JPA 프로그래밍
책 목차 및 이전 글
더보기
들어가기 전 JPA 특징, Q&A
1. JPA 소개
3. 영속성 관리
4. 엔티티 매핑
4.1 - 4.3 @Entity, @Table, 다양한 매핑
4.4 - 4.5 데이터베이스 스키마 자동 생성, DDL 생성 기능
5. 연관관계 매핑 기초
6. 다양한 연관관계 매핑
7. 고급매핑
8. 프록시와 연관관계 관리
9. 값 타입
9.3~5 값 타입과 불변 객체, 값 비교, 값 타입 컬렉션
10. 객체지향 쿼리 언어
10.6 객체지향 쿼리 심화
- 객체지향 쿼리와 관련된 다양한 고급 주제들
- 한 번에 여러 데이터를 수정하는 벌크 연산
- JPQL과 영속성 컨텍스트
- JPQL과 플러시 모드
10.6.1 벌크 연산
- 여러 건을 한번에 수정하거나 삭제하는 경우 벌크 연산을 사용
UPDATE 벌크 연산
String qlString = "update Product p " + "set p.price = p.price * 1.1 " + "where p.stockAmount < :stockAmount"; int resultCount = em.createQuery(qlString) .setParameter("stockAmount", 10) .executeUpdate();
DELETE 벌크 연산
String qlString = "delete from Product p " + "where p.price < :price"; int resultCount = em.createQuery(qlString) .setParamter("price", 100) .executeUpdate();
- JPA 표준은 아니지만 하이버네이트는 INSERT 벌크 연산도 지원
String qlString = "insert into ProductTemp(id, name, price, stockAmount) " + "select p.id, p.name, p.price, p.stockAmount from Product p " + "where p.price < :price"; int resultCount = em.createQuery(qlString) .setParamter("price", 100) .executeUpdate();
벌크 연산의 주의점
- 벌크 연산이 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리한다는 점을 주의
Product productA = em.createQuery("select p from Product p where p.name = :name", Product.class) .setParamter("name", "productA") .getSingleResult(); //출력결과: 1000 System.out.println("productA 수정 전 = " + productA.getPrice()); em.createQuery("update Product p set p.price = p.price * 1.1") .executeUpdate(); //출력결과: 1000 System.out.println("productA 수정 후 = " + productA.getPrice());
- 상품 A를 조회했으므로 가격이 1000원인 상품 A가 영속성 컨텍스트에 관리
- 벌크 연산은 영속성 컨텍스트를 통하지 않고 데이터베이스를 직접 쿼리하므로 가격이 다른 현상이 발생, 따라서 벌크 연산은 주의해서 사용이 필요
벌크 연산과 영속성 컨텍스트 값이 안맞는 현상 처리 방법
- em.fresh() 사용
- 벌크 연산 후 정확한 엔티티를 사용해야한다면 em.refresh()를 사용해서 다시 조회
- 벌크 연산 먼저 실행
- 벌크 연산을 가장 먼저 실행 후 엔티티를 조회, 이 방법은 JPA와 JDBC를 함께 쓸때도 유용
- 벌크 연산 수행 후 영속성 컨텍스트 초기화
- 벌크 연산을 수행한 직후 영속성 컨텍스트를 초기화해서 엔티티를 제거하는 방법 영속성 컨텍스트를 초기화했기 때문에 벌크 연산이 적용된 엔티티를 조회
10.6.2 영속성 컨텍스트와 JPQL
쿼리 후 영속 상태인 것과 아닌 것
- JPQL의 조회 대상은 엔티티, 임베디드 타입, 값 타입 등 다양한 종류가 존재
- JPQL은 엔티티를 조회하면 영속성 컨텍스트에서 관리되지만 그 외는 관리하지 않음
select m from Member m //엔티티 조회 (관리O) select o.address from Order o //임베디드 타입 조회 select m.id, m.username from Member m //단순 필드 조회 (관리X)
JPQL로 조회한 엔티티와 영속성 컨텍스트
- JPQL로 데이터베이스에 조회한 엔티티가 영속성 컨텍스트에 존재하면 데이터베이스에 조회한 결과를 버리고 영속성 컨텍스트에 있던 엔티티를 반환
예제
em.find(Member.class, "member1"); //회원1 조회 //엔티티 쿼리 조회 결과가 회원1, 회원2 List<Member> resultList = em.createQuery("select m from Member m", Member.class) .getResultList();
- JPQL을 사용해서 조회 요청
- JPQL은 SQL로 변환되어 데이터베이스를 조회
- 조회한 결과와 영속성 컨텍스트를 비교
- 식별자 값을 기준으로 member1은 이미 영속성 컨텍스트에 존재하므로 버리고 기존에 있던 member1이 반환 대상
- 식별자 값을 기준으로 member2는 없으므로 영속성 컨텍스트에 추가
- 쿼리 결과인 member1, member2를 반환. 여기서 member1은 쿼리 결과가 아닌 영속성 컨텍스트에 있던 엔티티
JPQL로 조회한 새로운 엔티티를 추가하거나 대체할 때 생기는 문제
엔티티를 추가하거나 대체할 수 있는 3가지 방법
- 새로운 엔티티를 영속성 컨텍스트에 하나 더 추가
- 영속성 컨텍스트는 기본 키 값을 기준으로 하기 때문에 사용 X
- 기존 엔티티를 새로 검색한 엔티티로 대체
- 언뜻 보면 합리적이지만, 영속성 컨텍스트에 수정중인 데이터가 사라지는 위험 발생
- 기존 엔티티는 그대로 두고 새로 검색한 엔티티를 제거
- 영속성 컨텍스트는 엔티티의 동일성을 보장하므로 3번으로 동작
find() vs JPQL
- em.find()
- 1차 캐시를 통해 영속성 컨텍스트에 있으면 메모리에서 찾으므로 성능상 이점이 존재
//최초 조회, 데이터베이스 조회 Member member1 = em.find(Member.class, 1L); //두 번째 조회, 영속성 컨텍스트에 있으므로 데이터베이스 조회X Member member2 = em.find(Member.class, 1L);
- JPQL
- JPQL은 항상 데이터베이스에 SQL을 실행해서 결과를 조회
- 영속성 컨텍스트에 값이 존재해도 쿼리 후 결과를 버리고 영속성 컨텍스트 값을 반환
//첫 번째 호출: 데이터베이스 조회 Member. member1 = em.createQuery("select m from Member m where m.id = :id", Member.class) .setParameter("id", 1L) .getSingleResult(); //두 번째 호출: 데이터베이스 조회 Member. member1 = em.createQuery("select m from Member m where m.id = :id", Member.class) .setParameter("id", 1L) .getSingleResult();
- em.find()은 영속성 컨텍스트에서 엔티티를 먼저 찾고, JPQL은 데이터베이스를 먼저 조회
JPQL의 특징 정리
- JPQL은 항상 데이터베이스를 조회
- JPQL로 조회한 엔티티는 영속 상태
- 영속성 컨텍스트에 이미 존재하는 엔티티가 있으면 기존 엔티티를 반환
10.6.3 JPQL과 플러시 모드
- JPA는 플러시가 일어날 때 영속성 컨텍스트에 등록, 수정, 삭제 엔티티를 찾아 SQL 문을 만들어 데이터베이스에 반영
flush()
직접사용 또는 플러시 모드에 따라 커밋 직전이나 쿼리 실행시 플러시가 호출em.setFlushMode(FlushModeType.AUTO); //커밋 또는 쿼리 실행시 플러시(기본값) em.setFlushMode(FlushModeType.COMMIT); //커밋시에만 플러시
FlushModeType.COMMIT
모드는 성능 최적화를 위해 꼭 필요할 때만 사용
쿼리와 플러시 모드
- JPQL은 영속성 컨텍스트에 데이터를 고려하지 않고 데이터베이스에 데이터를 조회
- JPQL을 실행하기 전에 영속성 컨텍스트의 내용을 데이터베이스에 반영이 필요
//가격을 1000 -> 2000원으로 변경 (변경 감지 사용) product.setPrice(2000); //가격이 2000원인 상품 조회 Product product2 = em.createQuery("select p from Product p where p.price = 2000", Product.class) .getSingleResult();
- 플러시 모드가 AUTO이므로 쿼리 실행 직전에 영속성 컨텍스트가 플러시되므로 2000원으로 수정한 상품으로 조회
- 플러시 모드를 COMMIT으로 설정하면 수정한 데이터 조회가 불가능
em.flush
또는 createQuery에setFlushMode
를 AUTO로 변경하면 조회 가능
em.setFlushMode(FlushModeType.COMMIT); //가격을 1000 -> 2000원으로 변경 (변경 감지 사용) product.setPrice(2000); //1. em.flush() 직접 호출 Product product2 = em.createQuery("select p from Product p where p.price = 2000", Product.class) .setFlushMode(FlushModeType.AUTO) //2. setFlushMode() 설정 .getSingleResult();
플러시 모드와 사용하는 이유 (최적화)
- FlushModeType.COMMIT 모드는 트랜잭션을 커밋할때만 플러시 호출
- 잘못하면 무결성에 심각판 피해를 주지만, 플러시 횟수를 줄여 성능 최적화가 가능
//비즈니스 로직 등록() 쿼리() //플러시 등록() 쿼리() //플러시 등록() 쿼리() //플러시 커밋() //플러시
- FlushModeType.AUTO: 쿼리와 커밋할 때 총 4번 플러시
- FlushModeType.COMMIT: 커밋 시에만 1번 플러시
- JPA를 사용하지 않고 JDBC를 직접 사용할 때도 고민이 필요
- 별도의 JDBC 호출은 실행한 쿼리를 인식할 방법이 없음 JPA에 FlushModeType.AUTO 설정시 플러시가 발생X
- JDBC로 쿼리를 실행하기 직전에 em.flush()를 호출해 DB와 동기화하는 것이 안전
'개발서적 > 자바 ORM 표준 JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 12.4 쿼리 메소드 기능 (0) | 2021.09.21 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 12.1~3 스프링 데이터 JPA 소개, 공통 인터페이스 기능 (0) | 2021.09.21 |
[자바 ORM 표준 JPA 프로그래밍] 10.5 네이티브 SQL (0) | 2021.09.13 |
[자바 ORM 표준 JPA 프로그래밍] 10.4 QueryDSL (0) | 2021.09.13 |
[자바 ORM 표준 JPA 프로그래밍] 10.3 Criteria (0) | 2021.09.13 |