JPA 게시글은 대부분 인프런의 김영한님의 강의인 '자바 ORM 표준 JPA 프로그래밍' 기반으로 내용을 정리했습니다.
프로젝션(SELECT)
- SELECT 절에 조회할 대상을 지정하는 것
- 프로젝션 대상 : 엔티티, 임베디드 타입, 스칼라 타입(숫자, 문자 등 기본 데이터 타입)
- SELECT m FROM Member m → 엔티티 프로젝션
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
List<Member> result = em.createQuery("select m from Member m", Member.class)
.getResultList();
일반적인 조회 방법은 여태 써왔던 방식 그대로 사용하면 된다. 근데 여기서 한 가지 의문점이 든다. 저렇게 다건을 조회해와도 영속성 컨텍스트에 모두 올라갈까?
em.flush();
em.clear();
List<Member> result = em.createQuery("select m from Member m", Member.class)
.getResultList();
Member findMember = result.get(0);
findMember.setAge(20);
처음 예제에서 추가로 코드를 넣어줬다. 먼저 영속성 컨텍스트를 모두 비우고 Member를 조회한다. 그리고 나서 Member를 하나 가져오고 그 Member의 Age를 바꿔준다. 만약 UPDATE 쿼리가 나간다면 영속성 컨텍스트에서 관리가 되고 있는 것이고, 나가지 않는다면 영속성 컨텍스트에 없다는 말이다.
Hibernate:
/* update
jpql.Member */ update
Member
set
age=?,
TEAM_ID=?,
username=?
where
id=?
실행 후 출력 로그를 확인해보면 UPDATE 쿼리가 잘 나가는 것을 볼 수 있다. JPA에서 1개든 10개든 100개든 조회해오는 모든 데이터는 영속성 컨텍스트로 올라간다.
- SELECT m.team FROM Member m → 엔티티 프로젝션
List<Team> result = em.createQuery("select m.team from Member m", Team.class)
.getResultList();
코드를 위와 같이 수정한다. 조회를 해 오는 데이터가 m.team이므로 Team.class로 변경해줘야한다. 그리고 실행을 해보면 JOIN 쿼리가 나간다.
Hibernate:
/* select
m.team
from
Member m */ select
team1_.id as id1_3_,
team1_.name as name2_3_
from
Member member0_
inner join
Team team1_
on member0_.TEAM_ID=team1_.id
뭔가 나는 JPQL을 심플하게 작성했는데 쿼리는 복잡하게 나간다. 좋은 기능 같지만 JPQL도 최대한 쿼리와 비슷하게 작성해야 함이 맞다. JOIN의 경우엔 성능과 직접적으로 연관되어 있는 기능이기도 하고 튜닝도 할 수 있기 때문에 한 눈에 보여야한다. 그래서 코드를 아래와 같이 수정해주면 된다.
List<Team> result = em.createQuery("select m.team from Member m join m.team t", Team.class)
.getResultList();
- SELECT m.address FROM Meber m → 임베디드 타입 프로젝션
List<Address> result = em.createQuery("select o.address from Order o", Address.class)
.getResultList();
임베디드 타입 프로젝션은 특이한 점은 없지만 한계점이 존재한다.
List<Address> result = em.createQuery("select a from Address a", Address.class)
.getResultList();
위와 같이 Address 엔티티 대상으로 JPQL을 작성할 수 없다. 왜냐하면 Address는 어떤 다른 객체에 소속되어 있기 때문에 단독으로 사용이 불가능하다.
- SELECT m.username, m.age FROM Member m → 스칼라 타입 프로젝션
em.createQuery("select m.username, m.age from Member m")
.getResultList();
이 스칼라 타입 프로젝션이 일반 SQL의 스칼라 프로젝션 타입과 비슷하다. 내용이 많으므로 [여러 값 조회]에서 후술한다.
- DISTINCT로 중복 제거
em.createQuery("select distinct m.username, m.age from Member m")
.getResultList();
SELECT 다음에 DISTINCT 키워드를 넣어주면 된다.
여러 값 조회
em.createQuery("select distinct m.username, m.age from Member m")
.getResultList();
스칼라 타입에는 지금 username과 age 두 개의 필드가 존재한다. 이 데이터를 어떻게 가져와야 할까?
- Query 타입 조회
List resultList = em.createQuery("select distinct m.username, m.age from Member m")
.getResultList();
Query 타입의 반환 값은 List이다. List에는 특정 타입을 넣을 수 없으니 Object로 돌려준다. 그래서 값을 가져올 때는 아래와 같이 형변환을 통해 값을 가져와야 한다.
Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("result = " + result[0]);
System.out.println("result = " + result[1]);
// 출력 로그
Hibernate:
/* select
distinct m.username,
m.age
from
Member m */ select
distinct member0_.username as col_0_0_,
member0_.age as col_1_0_
from
Member member0_
result = member1
result = 10
- Object[] 타입으로 조회
List<Object[]> resultList = em.createQuery("select distinct m.username, m.age from Member m")
.getResultList();
Object[] result = resultList.get(0);
System.out.println("result = " + result[0]);
System.out.println("result = " + result[1]);
List 제네릭에 Object[]를 넣어주면 형변환 작업을 생략할 수 있다.
- new 명령어로 조회
- 이게 제일 깔끔한 방법이다.
- 단순 값을 DTO로 바로 조회
- 패키지명을 포함한 전체 클래스 명 입력
- 순서와 타입이 일치하는 생성자가 필요하다.
먼저 MemberDTO 클래스를 생성한다.
public class MemberDTO {
private String username;
private int age;
public MemberDTO(String username, int age) {
this.username = username;
this.age = age;
}
// getter/setter 생략
}
그리고 실행 클래스에서 코드를 수정해준다.
List<MemberDTO> result = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m", MemberDTO.class)
.getResultList();
MemberDTO memberDTO = result.get(0);
System.out.println("memberDTO = " + memberDTO.getUsername());
System.out.println("memberDTO = " + memberDTO.getAge());
뭔가 되게 원하는 대로 작동이 되지만 뭔가 쿼리에 구구절절 패키지명을 적어야 하는게 맞나 싶기도 하다. 근데 추후 QueryDSL을 사용하면 이런 불편한 부분을 극복할 수 있게 된다.