-
#JPA-22 JPQL- 다형성 쿼리, 엔티티 직접 사용, Named 쿼리JPA 2021. 2. 25. 14:58
[출처] 인프런 김영한 강사님 -자바 ORM 표준 JPA 프로그래밍 기본
<JPQL - 다형성 쿼리>
TYPE
• 조회 대상을 특정 자식으로 한정
• 예) Item 중에 Book, Movie를 조회해라
• [JPQL]
select i from Item i where type(i) IN (Book, Movie)
• [SQL]
select i from i where i.DTYPE in (‘B’, ‘M’)
TREAT(JPA 2.1)
• 자바의 타입 캐스팅과 유사
• 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 때 사용
• FROM, WHERE, SELECT(하이버네이트 지원) 사용
TREAT(JPA 2.1)
• 예) 부모인 Item과 자식 Book이 있다. (다운캐스팅같은것을 할 수 있다.)
• [JPQL]
select i from Item i where treat(i as Book).auther = ‘kim’
• [SQL]
select i.* from Item i where i.DTYPE = ‘B’ and i.auther = ‘kim’
<JPQL - 엔티티 직접 사용>
엔티티 직접 사용 - 기본 키 값
• JPQL에서 엔티티를 직접 사용하면 SQL에서 해당 엔티티의 기 본 키 값을 사용
• [JPQL]
select count(m.id) from Member m //엔티티의 아이디를 사용 select count(m) from Member m //엔티티를 직접 사용
jpql은 entity를 직접 넘길 수 있다.
• [SQL] (JPQL 둘다 같은 다음 SQL 실행)
select count(m.id) as cnt from Member m
엔티티 직접 사용 - 기본 키 값
엔티티를 파라미터로 전달
식별자를 직접 전달
실행된 SQL
엔티티 직접 사용 - 외래 키 값
여기서 m.team mapped된 TEAM_ID이다.
실행된 SQL
<JPQL - Named 쿼리>
Named 쿼리 - 정적 쿼리
@namedQuery해서 엔티티에 미리 선언을 해놓는 것이며 , 쿼리에 이름을 부여할 수 있다.
em.createNaemdQuery로 이름으로 쿼리를 재활용하여 쓸 수 있다.
이게 얼마나 좋은 거냐고 생각할 수도 있지만, 엄청난 매리트가 있다.
• 미리 정의해서 이름을 부여해두고 사용하는 JPQL
• 정적 쿼리(동적 쿼리 X)
• 어노테이션, XML에 정의
• 애플리케이션 로딩 시점에 초기화 후 재사용 (진짜 장점)
- 정적 쿼리이기는 변하지 않을거기 때문에 로딩시점에 JPA나 Hibernate같은 애들이 이것을 parsing을 한다.
SQL로 그래서 캐시한다. JPA는 결국 SQL로 파싱되어 사용되어지는데 거기에대한 COST를 로딩시점에 하여 줄일 수있다.
• 애플리케이션 로딩 시점에 쿼리를 검증 (제일 중요)
Named 쿼리 - 어노테이션
만약 Query에 잘못된 내용이 들어가면 문자라서 실행은되는데 아래와 같은 에러가 발생한다.
Applicaiton loading 시점에 jpa가 파싱을할수 있다. 어노테이션이 등록이 되어있기 때문에
JPA가 쫙 끌어올리다가 쿼리를 미리 SQL로 파싱해서 들고있을려고하는데 문법이 안맞아서 오류가 발생.
개발 할때 가장좋은 오류는 COMPILE시 발생하는 오류 ->즉시 잡을 수 있으니깐
중간 정도 오류 - 런타임이긴한데 applicaiton loading시점에 발생하는 오류
가장 나쁜에러는 사용자가 버튼을 클릭했을때 나는 오류
Named 쿼리 - 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="http://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> <named-query name="Member.count"> <query>select count(m) from Member m</query> </named-query> </entity-mappings>
Named 쿼리 환경에 따른 설정
• XML이 항상 우선권을 가진다.
• 애플리케이션 운영 환경에 따라 다른 XML을 배포할 수 있다
이름없는 Named 쿼리
dao 사용 하면 application loading 시점에 파싱된다.
pubic interface UserRepository extends JpaRepository<User,Long> { @Query("select u from User u where u.emailAddress = ?1") User findByEmailAddress(String emailAddress); }
<JPQL - 벌크 연산>
벌크 연산(PK를찍어서 한 건에 대한 것이 아닌 SQL UPDATE, DELETE문)
• 재고가 10개 미만인 모든 상품의 가격을 10% 상승하려면?
• JPA 변경 감지 기능으로 실행하려면 너무 많은 SQL 실행
• 1. 재고가 10개 미만인 상품을 리스트로 조회한다.
• 2. 상품 엔티티의 가격을 10% 증가한다.
• 3. 트랜잭션 커밋 시점에 변경감지가 동작한다.
(더티채킹은 만약 100만건이면 100만건 다조회하고 다돌리면서 증가시키고
commit시점에 변경감지가 동작하고 변경된 데이터가 100건이라면 100번의 UPDATE SQL 실행 )
• 변경된 데이터가 100건이라면 100번의 UPDATE SQL 실행
벌크 연산 예제
• 쿼리 한 번으로 여러 테이블 로우 변경(엔티티)
• executeUpdate()의 결과는 영향받은 엔티티 수 반환
• UPDATE, DELETE 지원
• INSERT(insert into .. select, 하이버네이트 지원) <JPA 표준스팩에는 없음>
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();
ex) 모든 회원 나이를 20살로 바꿔라
JpaMain class
console
Hibernate: /* update Member m set m.age = 20 */ update Member set age=20 resultCount = 3
DB result
모든 직원의 나이가 20으로 바뀌었다.
벌크 연산 주의
• 벌크 연산은 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리
(DB는 처리가 됬지만 APPLICATION에서는 남아있다)
- 해결책 -
• 1 . 벌크 연산을 먼저 실행
• 2. 벌크 연산 수행 후 영속성 컨텍스트 초기화
JpaMain class
Team teamA = new Team(); teamA.setName("팀A"); em.persist(teamA); Team teamB = new Team(); teamB.setName("팀B"); em.persist(teamB); Member member1 = new Member(); member1.setUsername("회원1"); member1.setAge(0); member1.setTeam(teamA); em.persist(member1); Member member2 = new Member(); member2.setUsername("회원2"); member1.setAge(0); member2.setTeam(teamB); em.persist(member2); Member member3 = new Member(); member3.setUsername("회원3"); member1.setAge(0); member3.setTeam(teamB); em.persist(member3); /* em.flush(); em.clear();*/ //모든 회원의 나이를 20살로 바꿔 //FLUSH(커밋을 하거나,QUERY가나갈때, 강제로 호출할떄) 자동 호출 int resultCount = em.createQuery("update Member m set m.age = 20") .executeUpdate(); System.out.println("resultCount = " + resultCount); //이렇게 하면 영속성 컨텍스트에는 20살인게 반영이 안되있다. tx.commit();
중요부분만 캡처 console
Hibernate: /* update Member m set m.age = 20 */ update Member set age=20 member1.getAge() = 0 member2.getAge() = 0 member3.getAge() = 0
20으로 update했는데 값이 모두 0,0,0이다 왜그렇까?
-영속성 컨텍스트를 flush하는 순간에 모든 age값은 0,0,0 이였다 .
execute하는 시점에 flush가되고 쿼리가나가서 20살로 된다 하지만 영속성컨텍스트에는 0,0,0그대로이다.
이렇게 em.find로 다시가져와도 0이다 왜냐면 영속성컨텍스트가 초기화 되지않아, 1차캐시에서 다시 조회해 오기때문이다.
그럼 어떻게 해결해야할까?
console
Hibernate: select member0_.id as id1_0_0_, member0_.age as age2_0_0_, member0_.TEAM_ID as TEAM_ID5_0_0_, member0_.type as type3_0_0_, member0_.username as username4_0_0_ from Member member0_ where member0_.id=? member1.getAge() = 0
em.clear를 사용해야한다. em.clear를하면 영속성 컨텍스트에 없으니깐 다시 깔끔하게 가져올 수 있다.
'JPA' 카테고리의 다른 글
#JPA-21 JPQL - 페치 조인(fetch join) (0) 2021.02.23 #JPA-20 JPQL- 경로 표현식 (0) 2021.02.23 #JPA-19 페이징 API, JOIN, JPA 기타 (0) 2021.02.23 #JPA-18 JPQL(Java Persistence Query Language) (0) 2021.02.23 #JPA-17 JPA가 지원하는 다양한 쿼리 방법 (0) 2021.02.23