[JPA] JPQL - 중급 문법(다형성 쿼리, 엔티티 직접 사용, Named 쿼리, 벌크 연산)

·2024년 4월 21일
0

JPA

목록 보기
15/17
post-thumbnail

💡다형성 쿼리

  • Item : 부모객체
  • Album, Movie, Book : 자식 객체

📗TYPE

  • 조회 대상을 특정 자식으로 한정
  • 예) Item 중 Book, Movie를 조회하라
	//JPQL
	select i from Item i where type(i) IN(Book, Movie)

	//SQL
	select i from Item i where i.DTYPE in('B', 'M');

📗TREAT(JPA 2.1)

  • 자바의 타입 캐스팅과 유사(형변환)
  • 상속 구조에서 부모 타입을 특정 자신 타입으로 다룰 때 사용
  • FROM, WHERE, SELECT(하이버네이트 지원) 사용
  • 예) 부모인 Item과 자식 Book이 있다.
	//JPQL
	select from Item i where treat(i as Book).author = 'kim';

	//SQL
	select i.* from Item i where i.DTYPE = 'B' and i.author = 'kim';

💡엔티티 직접 사용

기본 키 값

  • JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기본 키 값을 사용
	//JPQL
	select count(m.id) from Member m //엔티티의 아이디를 사용
	select count(m) from Member m //엔티티를 직접 사용

	//SQL(JPQL 둘 다 같은 다음 SQL 실행)
	select count(m.id) as cnt from Member m
  • 파라미터로 엔티티를 넘겨주거나 식별자를 직접 전달하더라도 실행되는 SQL은 같다.
	//엔티티를 파라미터로 전달
	String jpql = "select m from Member m where m = :member";
	List resultList = em.createQuery(jpql)
						.setParameter("member", member)
						.getResultList();

	//식별자를 직접 전달
	String jpql = "select m from Member m where m.id = 	:memberId";
	List resultList = em.createQuery(jpql)
						.setParameter("memberId", memberId)
						.getResultList();
  • 실행된 SQL은 동일하다.
	select m.* from Member m where m.id = ?

📗외래 키 값

  • 기본 키와 로직은 동일하다.
	Team team = em.find(Team.class, 1L);

	//엔티티를 파라미터로 전달
	String query = "select m from Member m where m.team = :team";
	List resultList = em.createQuery(query)
						.setParameter("team", team)
						.getResultList();
	//식별자를 직접 전달
	String query = "select m from Member m where m.team.id = :teamId";
	List resultList = em.createQuery(query)
						.setParameter("teamId", teamId)
						.getResultList();
  • 실행된 SQL
	select m.* from Member m where m.team_id = ?

💡Named 쿼리

  • 미리 정의해서 이름을 부여해두고 사용하는 JPQL
  • 정적 쿼리
  • 어노테이션, XML에 정의
  • 애플리케이션 로딩 시점에 초기화 후 재사용
  • 애플리케이션 로딩 시점에 쿼리를 검증

📌어노테이션에 정의

@Entity
@NamedQuery(
		name="Member.findByUsername",
		query="select m from Member m where m.username = :username")
public class Member {
		...
}
...

List<Member> resultList = 
					em.createNamedQuery("Member.findByUsername", Member.class)
					.setParameter("username", "회원1")
					.getResultList();

📌XML에 정의

//[META_INF?persistence.xml]
<persistence-unit name="jpabook">
		<mapping-file>META-INF/ormMember.xml</mapping-file>

//[META-INF/ormMember.xml]
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="htt://xmlns.jcp.org/xml/ns/persistence/orm" version="2.1">
		<named-query name="Member.findByUsername">
				<query>
						<![CDATA[ select m from Member m where m.username = :username]]
				</query>
		</named-query>
</entity-mappings>
  • 어노테이션과 XML에 정의된 Named 쿼리는 XML이 항상 우선권을 가진다.
    ➡️애플리케이션 운영환경에 따라 다른 XML을 배포할 수 있다.

💡벌크 연산

  • SQL의 update/delete 문을 생각하자
    • 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
  • JPA의 변경 감지 기능으로 실행하려면 너무 많은 SQL이 실행되어야 한다.
    1. 재고가 10개 미만인 상품을 리스트로 조회
    2. 상품 엔티티의 가격을 10% 증가
    3. 트랜잭션 커밋 시점에 변경 감지 동작
  • 변경된 데이터가 100건이라면 100번의 update SQL 실행

📗벌크연산 예제

  • 쿼리 한번으로 여러 테이블 로우 변경(엔티티)
  • exeuteUpdate()의 결과는 영향받은 엔티티 수 반환
  • UPDATE, DELETE 지원
  • INSERT(insert into .. select, 하이버네이트 지원)
String query = "update Product p "+
				"set p.price = p.price * 1.1 where p.stockAmount < :stockAmount";

int resultCount = em.createQuery(qlString)
					.setParameter("stockAmount", 10)
				.executeUpdate();

📗벌크 연산 주의점

  • 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리
    1. 벌크 연산을 먼저 실행
    2. 벌크 연산 수행 후 영속성 컨텍스트 초기화
	Member member1 = new Member();
    member1.setUsername("회원1");
    member1.setAge(0);
    em.persist(member1);
    
    Member member2 = new Member();
    member2.setUsername("회원2");
    member2.setAge(0);
    em.persist(member2);
    
    Member member2 = new Member();
    member1.setUsername("회원2");
    member2.setAge(0);
    em.persist(member2);
    
    int resultCount = em.createQuery("update Member m = set m.userage=20")
    					.executeUpdate();

	System.out.println("member1 = " + member1.getAge());	//0
	System.out.println("member2 = " + member1.getAge());	//0
	System.out.println("member3 = " + member1.getAge());	//0
  • 벌크 연산이 flush를 하는 시점에는 나이가 다 0임
  • 벌크연산 직전에 자동 fulsh -> DB에 0으로 저장됨
  • 그 후 벌크연산(UPDATE)가 나가서 20살로 업데이트됨
    ➡️영속성 컨텍스트에 저장되지 않고 데이터베이스에 바로 쿼리가 나간다
  • 영속성 컨텍스트에 저장되지 않으므로 get.Age()로 멤버의 나이를 출력하면 0으로 출력된다.
    즉, 벌크 연산 수행 후 영속성 컨텍스트 초기화를 해주는 것이 좋다.
	int resultCount = em.createQuery("update Member m = set m.userage=20")
    					.executeUpdate();
                        
	em.clear();	//영속성 컨텍스트 초기화
    Member findMember - em.find(Member.class, member1.getId());
    
    System.out.println("member1 = " + member1.getAge());	//20
profile
백엔드 개발자를 꿈꿉니다 / 이전 블로그 : https://po-dadak.tistory.com/category

0개의 댓글

관련 채용 정보