이전에 작성한 글의 엔티티 코드를 그대로 사용해서 다형성 쿼리 테스트 코드를 작성했다.
참고: @Inheritance(strategy = InheritanceType.JOINED) 를 사용 중이다.
테스트 코드
String queryString = "select e from Employee e where type(e) "
+ "in (HourlyEmployee, SalariedEmployee )";
List<Employee> list1 = em.createQuery(queryString, Employee.class).getResultList();
for (Employee employee : list1) {
System.out.println("employee = " + employee.getClass().getSimpleName());
}
String queryString = "select e from Employee e "
+"where treat(e as SalariedEmployee).annualSalary = '1023'";
List<Employee> list2 = em.createQuery(queryString, Employee.class).getResultList();
for (Employee employee : list2) {
System.out.println("employee = " + employee.getClass().getSimpleName());
}
테스트 코드
Developer devRef = em.getReference(Developer.class, 3L);
TypedQuery<Developer> query
= em.createQuery("select d from Developer d where d = :dev", Developer.class);
Developer dev = query.setParameter("dev", devRef).getSingleResult();
System.out.println("dev = " + dev);
/*
// 이 방법도 가능!
TypedQuery<Developer> query
= em.createQuery("select d from Developer d where d.id = :dev", Developer.class);
Developer dev = query.setParameter("dev", 3L).getSingleResult();
System.out.println("dev = " + dev);
*/
콘솔 출력
Hibernate:
select
developer0_.developer_id as develope1_1_,
developer0_.age as age2_1_,
developer0_.company_id as company_4_1_,
developer0_.name as name3_1_
from
developer developer0_
where
developer0_.developer_id=?
dev = Developer(id=3, name=naverDev1, age=26)
테스트 코드
String sql = "select d from Developer d where d.company = :comp";
Company ref = em.getReference(Company.class, 1L);
TypedQuery<Developer> query = em.createQuery(sql, Developer.class);
List<Developer> devList = query.setParameter("comp", ref).getResultList();
devList.forEach(System.out::println);
콘솔 출력
Hibernate:
select
developer0_.developer_id as develope1_1_,
developer0_.age as age2_1_,
developer0_.company_id as company_4_1_,
developer0_.name as name3_1_
from
developer developer0_
where
developer0_.company_id=?
Developer(id=3, name=naverDev1, age=26)
Developer(id=4, name=naverDev2, age=22)
하나의 쿼리에 이름을 지정하는 것이다.
특징은 아래와 같다.
테스트를 해보자.
- 엔티티 코드 수정
@NamedQuery(
name = "Developer.findByName",
query = "select d from Developer d where d.name = :name"
)
public class Developer {...}
- 테스트 코드
List<Developer> developers
= em.createNamedQuery("Developer.findByName", Developer.class)
.setParameter("name", "naverDev1")
.getResultList();
for (Developer developer : developers) {
System.out.println("developer = " + developer);
}
- 콘솔 출력
Hibernate:
select
developer0_.developer_id as develope1_1_,
developer0_.age as age2_1_,
developer0_.company_id as company_4_1_,
developer0_.name as name3_1_
from
developer developer0_
where
developer0_.name=?
developer = Developer(id=3, name=naverDev1, age=26)
그런데 만약에 내가 고의적으로 쿼리를 망가뜨리고 실행하면? 애플리케이션이 뜨면서 부터
에러를 잡아낸다. 덕분에 배포하기 전에 빠르게 버그를 발견하고 수정할 수 있다.
- @NamedQuery : query 속성 변경
query = "select d from Developer_WHAT d where d.name = :name"
- 에러!
참고: 스피링 JPA 의
@Query
도 같은 기능을 제공한다,
단지 name을 지정 안하는 차이만 있다. 그래서 이름없는 NamedQuery라고도 한다.
우리는 쿼리를 직접 사용해봤다면 update(또는 delete)문을 사용할 때,
where 절로 필터링하여 여러 건을 한번에 변경시키는 경우가 많다.
그런데 JPA 의 변경 감지 기능만 사용하면 많은 SQL 을 실행하게 된다.
이런 이유로 JPQL은 벌크 연산을 제공한다.
테스트 코드를 작성해보자.
좀 이상한 테스트지만, 모든 회사원의 나이를 20살로 바꾸겠다.
- 테스트 코드
int cnt = em.createQuery("update Developer d set d.age = 20")
.executeUpdate();
System.out.println("cnt = " + cnt);
- 콘솔 출력
Hibernate:
update
developer
set
age=20
cnt = 4
- 테이블 데이터 확인
executeUpdate()
의 결과는 변형이 일어난 엔티티의 수를 반환시킨다.
참고
JPA 에서는UPDATE
,DELETE
만 지원
Hibernate 에서는INSERT(insert into ..select)
도 가능하다!
벌크 연산은 영속성 컨텍스트의 상태와 상관없이 무족건 데이터베이스에 바로 쿼리를 날린다.
물론 벌크 연산도 JPQL이기 때문에 실행되기 전에 flush가 수행되서 이전까지의
SQL 저장소에 있던걸 먼저 날린 후에, 벌크 연산을 하기는 한다.
하지만 여전히 문제가 있다.
아래 코드를 보자.
테스트 코드
Developer developer = new Developer();
developer.setAge(25);
developer.setName("dev1");
developer.setCompany(null);
em.persist(developer);
int cnt = em.createQuery("update Developer d set d.age = 20")
.executeUpdate();
System.out.println("age = " + developer.getAge());
System.out.println("cnt = " + cnt);
콘솔 출력
Hibernate:
insert
into
developer
(age, company_id, name, developer_id)
values
(?, ?, ?, ?)
Hibernate:
update
developer
set
age=20
age = 25 // 영속성 컨텍스트의 엔티티는 여전히 그대로다..!
cnt = 5
이러면 이후에 코드를 짤 때 많은 혼동을 일으킬 수 있다.
이에 대한 대처법은 두 가지로 나뉜다.
다음과 같은 상황에서 좀 궁금한 게 생긴다.
테스트 코드
Developer findDev = em.find(Developer.class, 3L);
System.out.println("ref " + findDev.hashCode());
Developer jpqlDev = em.createQuery("select d from Developer d where d.id = 3L", Developer.class)
.getSingleResult();
System.out.println("ref " + jpqlDev.hashCode());
System.out.println("is Same ? " + (findDev == jpqlDev));
em.find 를 통해서 얻어온 엔티티는 영속성 컨텍스트의 관리를 받게 된다.
이런 상태에서 JPQL 사용하면 일단은 쿼리가 무조건 날라간다.
그렇다면 em.find 로 가져온 엔티티는 없어지고 JPQL로 얻어온 엔티티를 새로이 사용할까?
콘솔 출력을 통해서 답을 알아보자.
콘솔 출력
Hibernate:
select
developer0_.developer_id as develope1_1_0_,
developer0_.age as age2_1_0_,
developer0_.company_id as company_4_1_0_,
developer0_.name as name3_1_0_
from
developer developer0_
where
developer0_.developer_id=?
ref 1779378259
Hibernate:
select
developer0_.developer_id as develope1_1_,
developer0_.age as age2_1_,
developer0_.company_id as company_4_1_,
developer0_.name as name3_1_
from
developer developer0_
where
developer0_.developer_id=3
ref 1779378259
is Same ? true
놀랍게도 em.find 로 찾아온 엔티티를 그대로 사용한다.
결론: JPQL로 쿼리를 날려서 가져온 엔티티가 이미 영속성 컨텍스트 내에 있다면,
JPQL로 조회한 결과를 버리고 이미 있는 것을 계속 쓴다.
이러는 이유는 영속성 컨텍스트의 엔티티 동일성 보장 때문이다.