이 글은 김영한 님의 저서 「자바 ORM 표준 JPA 프로그래밍」을 학습한 내용을 정리한 글입니다. 모든 출처는 해당 저서에 있습니다.
여러 건을 한 번에 수정하거나 삭제하는 연산
executeUpdate()
: 벌크 연산으로 영향을 받은 엔티티 건수 반환/*재고가 10개 미만인 모든 상품 가격 10% 상승*/
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();
/*가격이 100원 미만인 상품 삭제*/
String qlString =
"delete from Product p " +
"where p.price < :price";
int resultCount = em.createQuery(qlString)
.setParameter("price", 100)
.executeUpdate();
📖 참고
하이버네이트에서 INSERT 벌크 연산 지원함(JPA 표준 x)
//상품A 조회(상품A의 가격: 1000원)
//조회된 상품A → 영속성 컨텍스트에서 관리
Product productA =
em.createQuery("select p from Product p where p.name = :name", Product.class);
.setParameter("name", "productA")
.getSingleResult();
//출력 결과: 1000
System.out.println("productA 수정 전 = " + productA.getPrice();
//벌크 연산 수행으로 모든 상품 가격 10% 상승
//예상 결과값 : 1100원
em.createQuery("update Product p set p.price = p.price * 1.1")
.executeQuery();
//출력 결과: 1000
//예상 결과값인 1100원과 다름
System.out.println("productA 수정 후 = " + productA.getPrice());
em.refresh()
사용em.refresh(productA); //데이터베이스에서 상품A 다시 조회
벌크 연산 먼저 실행
벌크 연산 수행 후 영속성 컨텍스트 초기화
select m from Member m //엔티티 조회(관리O)
select o.address from Order o //임베디드 타입 조회(관리 x)
select m.id, m.username from Member m //단순 필드 조회(관리 x)
em.find(Member.class, "member1"); //회원1 조회
//엔티티 쿼리 조회 결과가 회원1, 회원2
List<Member> resultList =
em.createQuery("select m from Member m", Member.class)
.getResultList();
member1
은 영속성 컨텍스트에 존재하므로 버리고, 기존에 존재하는 member1
이 반환 대상이 됨member2
는 영속성 컨텍스트에 존재하지 않으므로 영속성 컨텍스트에 추가member1
, member2
반환영속성 컨텍스트는 영속 상태인 엔티티의 동일성 보장
find()
vs JPQLfind()
//최초 조회, 데이터베이스에서 조회
Member member1 = em.find(Member.class, 1L);
//두 번째 조회, 영속성 컨텍스트에 존재하므로 데이터베이스 조회 x
Member member2 = em.find(Member.class, 1L);
//member1 == member2 → 주소 값이 같은 인스턴스
/*첫 번째 호출: 데이터베이스에서 조회*/
//데이터베이스에서 회원 엔티티 조회 후 영속성 컨텍스트에 등록
Member member 1 =
em.createQuery("select m from Member m where m.id = :id", Member.class)
.setParameter("id", 1L)
.getSingleResult();
/*두 번째 호출: 데이터베이스에서 조회*/
//데이터베이스에서 같은 회원 엔티티 조회, 이미 조회한 동일한 엔티티 존재
//→ 새로 검색한 엔티티 버리고 영속성 컨텍스트에 존재하는 기존 엔티티 반환
Member member2 =
em.createQuery("select m from Member m where m.id = :id", Member.class)
.setParameter("id", 1L)
.getSingleResult();
//member1 == member2 → 주소 값이 같은 인스턴스
📖 JPQL의 특징
- JPQL은 항상 데이터베이스 조회
- JPQL로 조회한 엔티티는 영속 상태
- 영속성 컨텍스트에 엔티티가 이미 존재시 기존 엔티티 반환
//가격을 1000→2000원으로 변경
//데이터베이스에는 1000원인 상태
product.setPrice(2000);
//가격이 2000원인 상품 조회
Product product2 =
em.createQuery("select p from Product p where p.price = 2000")
.getSingleResult();
AUTO
가 기본em.setFlushMode(FlushModeType.COMMIT); //커밋 시에만 플러시
//가격을 1000→2000원으로 변경
product.setPrice(2000);
//1. em.flush() 직접 호출
//가격이 2000원인 상품 조회
Product product2 =
em.createQuery("select p from Product p where p.price = 2000", Product.class)
.setFlushMode(FlushModeType.AUTO) //2. setFlushMode() 설정
.getSingleResult();
COMMIT
em.flush()
를 통해 수동으로 플러시setFlushMode()
로 해당 쿼리에서만 사용할 플러시 모드를 AUTO
로 변경쿼리에 설정하는 플러시 모드는 엔티티 매니저에 설정하는 플러시 모드보다 우선권을 가짐
em.setFlushMode(FlushModeType.COMMIT)
FlushModeType.COMMIT
트랜잭션을 커밋할 때만 플러시
→ JPA 쿼리 사용 시 영속성 컨텍스트에는 있지만 데이터베이스에 반영하지 않은 데이터 조회 불가능
∴ 데이터 무결성에 심각한 피해를 줄 수 있음
플러시가 자주 발생할 때 사용하면 쿼리시 발생하는 플러시 횟수를 줄여 성능 최적화할 수 있음
FlushModeType.AUTO
: 쿼리+커밋 → 총 4번 플러시FlushModeType.COMMIT
: 커밋 시에만 1번 플러시📖 참고
JDBC를 직접 사용하여 SQL을 실행할 때 플러시 모드 고민 필요
- JDBC로 쿼리 직접 실행 시 JPA는 JDBC가 실행한 쿼리를 인식할 방법 x
→ 별도의 JDBC 호출은 플러시 모드를AUTO
로 설정해도 플러시 발생 x- JDBC로 쿼리를 실행하기 직전에
em.flush()
를 호출하여 영속성 컨텍스트의 내용을 데이터베이스에 동기화하는 것이 안전