본문 바로가기

Spring/JPA

[JPA] - 나머지 쿼리

728x90

1. 다형성 쿼리 

> treat, item 등의 쿼리문 사용 가능

> ex) select i from Item i where treat(i as Book).author= 'kim'; // treat = 일종의 캐스팅

> 사용한 테이블 전략에 맞춤형으로 SQL이 나가게 됨. 

 

 

2. 엔티티 직접 사용

> 엔티티 칼럼을 직접 사용하면, SQL에서는 당연히 해당 엔티티의 기본 키값을 사용

> 그냥 엔티티를 직접 파라미터로 전달해서 사용할 수 있다는 특징이 있음

 

가령, 

 

em.createQuery("select m from Member m where m = :member").setParameter("member", member)
em.createQuery("select m from Member m where m.id = :memberId").setParameter("memberId", memberId)

 

 두 JPQL 모두 실 SQL 결과는 "select m from Member m where m.id = {}"

 

team 이 teamA 인 녀석의 멤버들을 모두 가져와라

JPQL : select m from Member m where m.team = :teamA or m.teamId = :teamAId

 

 

3.Named Query

> 미리 정의해서 이름을 부여해두고 사용하는 JPQL (Entity 따위 맨 위에 Annotation 으로 Query 를 미리 짜놓고, 그 쿼리에 대한 key 이름을 만들어 놓는다)

> 장점 1 : 앱 로딩시 초기화를 해놓고, 앞으로 동일한 쿼리에 대해서는 재사용을 한다 

> 장점 2: 앱 로딩 시점에 쿼리를 검증해준다

>>>>>>>>>>>>>>> 2가 매우 막강함. 

 

@Entity
@NamedQuery(
    name = "Member.findByUsername",
    query = "select m from Member m where m.username = :username"
) // 관례상 사용할 Entity 에 많이 함
public class Member {
    ...
}

void test(){
    tx.begin()
    try{
    ...
    
    List<Member> members = em.createNamedQuery("Member.findByUsername", Member.class)
        .setParameter("username", "김삿갓")
        .getResultList();
    
    ...
    }catch{
    ...
    }
}

에러 중 제일 좋은 오류 :: 컴파일 시점에 나는 오류. 빨리 잡을 수 있음. 

에러 중 제일 나쁜 오류 :: 유저가 발견하는 오류. 

>> SQL이 잘못되었음을 실행 시점에 알 수 있기 때문에, 네임드 쿼리는 실수할 확률이 줄어든다. 

>> SPring Data JPA 를 써도, @Query 를 통해서 등록해놓을 수 있음. 이것도 네임드 쿼리와 동일

>> 실무에서는 바로 위에서 말한 @Query 를 통해서 네임드 쿼리를 사용하긴 해도, 엔티티에 명명해서 하는건 조금 비추. 너무 지저분해지고, 여기 저기 쿼리가 있는 건 좋지 않음. 

 

 

4. 벌크 연산. 

 

가정 : 재고가 10개 미만인 모든 상품의 가격을 10% 상승시키려면?

현재 

1. 재고가 10개 미만인 상품 모~두 조회 (백만개)

2. 각 상품의 가격을 모~두 10% 상숭 (백만개)

3. 커밋 시점에 변경 감지 작동, SQL 나감.  (백만개) 

 

** 벌크 연산이란, 쿼리 한번으로 여러 테이블의 로우를 변경할 수 있는 것이다. 

 

모든 회원의 나이를 20살로 바꾸자

JPQL : update Membe m set m.age = 20 후 . executeUpdate() 를 수행

em.createQuery({JPQL}).executeUpdate() 는 영향 받은 항목 수를 반환한다. 

 

* 주의 사항 :: 벌크 연산은 영컨을 무시하고 바로 DB에 직접 쿼리를 날린다. 따라서 영컨을 사용하다가 벌크를 하면 DB가 좀 꼬일 수 있음. 

 

두가지 해결책

>>>> 1) 벌크 연산을 먼저 실행

>>>> 2) 벌크 연산 수행 후 영컨 초기화

 

1 설명 )) 영컨을 하다가 벌크가 문제가 되는거지, 벌크 후에 영컨을 하는건 문제가 안되니까 저렇게 하면 ㄱㅊ

2 설명 )) 

회원 조회. 연봉 5천만. 벌크 연산으로 6천만으로 상승. 1차 캐시에는 아직 5천만 >>> 문제 발발 (항상 이놈의 1차캐시가 문제임)

따라서 6천만으로 상승시킨 뒤에 Flush 를 해주고 (플러쉬는 차피 벌킹하며 SQL 나가면 자동으로 됨, 플러쉬가 되는 상황 3가지 기억!!), 영컨을 초기화하면, 인앱에서 다시 가져올 때는 쿼리를 날려야 하고, 6천만이 되어있을 것. 

 

void test(){
    tx.begin();
    try{
        ...
        Team teamA = new Team();
        teamA.setName("teamA");
        em.persist(teamA);
        
        ... 
        //member 1,2,3 를 만들고 teamA 지정하고 영속화한 상태
        ...
        
        int result = em.createQuery("update Member m set m.age = 20") // 20살로 모두 변경
                       .executeUpdate();
                       
        // 이미 FLUSH 됨, 영컨은 가만히 있음
        
        System.out.println("resultCnt = " + resultCnt);
        
        System.out.println("member1.getAge() = " + member1.getAge());
        System.out.println("member2.getAge() = " + member2.getAge());
        System.out.println("member3.getAge() = " + member3.getAge());
        
        tx.commit();
    }catch{
    ...
    }
}

 

나이를 출력해보면 출력값들이 모두 20살이 안나오고 초기에 Setting 한 나이들이 조회되는 것을 알 수 있다. 이 상태로 인 앱에서 로직을 수행하는 것임. (트랜젝션 내) >> 플러쉬는 되었겠지만, 영컨 초기화가 안되어 있기 때문에 1차캐시에 값들이 그대로 있다.  (데이터 정합성 문제) 

 

clear 후에 다시 조회해야함. 

 

...
int resultCount = em.createQuery("update Member m set m.age = 20")
                     .executeUpdate();

em.clear(); // *************** 중요

Member findMember = em.find(Member.class, member1.getId());
System.out.println("member1.getAge() = " + member1.getAge());

 

이렇게 하면 데이터 정합성이 맞음. 이게 위 2번에 대한 설명임. 

 

암튼 벌크 연산시에는 영컨을 어떻게 사용하고 있는지 Repo 와 잘 생각하며 파악해야 한다. 안그러면 ㄹㅇ ㅈ되는거임.

 

 

728x90