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 와 잘 생각하며 파악해야 한다. 안그러면 ㄹㅇ ㅈ되는거임.
'Spring > JPA' 카테고리의 다른 글
[JPA] 양방향 매핑 관계 로딩 방법 별로 살펴보기 (N+1 문제는 도대체 언제?) (0) | 2024.03.01 |
---|---|
[JPA] 연관 객체 불러오기.. Fetch Join 정말 괜찮을까? (0) | 2023.12.03 |
[JPA] JPQL 쿼리 2 (FETCH JOIN의 등장) (0) | 2023.01.03 |
[JPA] JPQL 쿼리 (0) | 2022.12.29 |
[JPA] - 값 타입 (0) | 2022.12.29 |