ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • #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를하면 영속성 컨텍스트에 없으니깐 다시 깔끔하게 가져올 수 있다.

     

    댓글

Designed by Tistory.