프로젝션은 셀렉트 절에 조회할 대상을 지정하는 것을 말한다.
SELECT m FROM Member mSELECT m.team FROM Member mSELECT m.address FROM Member mSELECT m.username, m.age FROM Member m아래 예시를 보자. 엔티티 프로젝션으로 Member 엔티티들을 가져온다.
여기서 질문이 있다.
쿼리에서 불러온 엔티티들이 영속성 켄텍스트에서 관리가 될까?
public class JpaMain {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
em.flush();
em.clear();
List<Member> result = em.createQuery("select m from Member m ", Member.class)
.getResultList(); // Entity들 반환, 반환된 엔티티들은 영속성 켄텍스트에서 관리가 될까?
Member findMember = result.get(0);
findMember.setAge(20); //Age가 20으로 변경된다면 영속성 켄텍스트에서 관리가 된다는 것.
tx.commit();
} catch (Exception e) {
tx.rollback();
e.printStackTrace();
} finally {
em.close();
}
}
}
쿼리로 Member를 가져온 뒤 setAge(20)으로 Age의 데이터를 변경했다. 이때, DB에서 Age의 값이 변경된다면 영속성 컨텍스트에서 관리된다는 것이다.
업데이트 쿼리
Hibernate:
/* update
jpql.Member */ update
Member
set
age=?,
TEAM_ID=?,
username=?
where
id=?

업데이트 쿼리가 나갔고 DB를 확인해보니 Age의 값이 20으로 변경되었다. 쿼리로 불러온 엔티티가 영속성 컨텍스트에서 관리된다는 뜻이다.
엔티티 프로젝션을 하면 셀렉트 절에 엔티티가 10~20개 나올 수도 있다. 이 모든걸 영속성 컨텍스트에서 다 관리가 된다.
임베디드 타입 Address
@Embeddable
public class Address {
private String city;
private String street;
private String zipcode;
임베디드 타입 프로젝션
em.createQuery("select o.address from Order o", Address.class)
.getResultList();
결과 쿼리
Order o */ select
order0_.city as col_0_0_,
order0_.street as col_0_1_,
order0_.zipcode as col_0_2_ from
ORDERS order0_
Order에서 값 타입인 adress를 셀렉트한 결과이다. 결과를 보니 Adress와 관련된 것만 잘 가져오는 것을 볼 수 있다.
임베디드 타입 프로젝션은 한가지 한계가 있는데
em.createQuery("select address from Order o", Address.class)
.getResultList();
위처럼 address로 바로 받아오지는 못한다는 것이다. Adress는 값 타입으로 어딘가에 소속되어있기 때문에 어디 소속인지 엔티티를 정해줘야한다.
em.createQuery("select distinct m.username, m.age from Member m")
.getResultList();
스칼라 타입 프로젝션은 일반 SQL의 셀렉트 프로젝션과 거의 똑같다.
이때 고민이 하나 생긴다. 응답 타입이 2개인데 반환 타입을 어떻게 가져와야할까?
총 3가지 방법이 있다. 아래에서 하나하나 소개하겠다.
List resultList =
em.createQuery("select m.username, m.age from Member m")
.getResultList();
Object o = resultList.get(0);
Object[] result = (Object[]) o;
System.out.println("username = " + result[0])
System.out.println("age = " + result[1])
//결과
username = member1
age = 10
List<Object[]> resultList =em.createQuery("select m.username, m.age from Member m")
.getResultList();
Object[] objects = resultList.get(0);
System.out.println("username = " + objects[0]);
System.out.println("age = " + objects[1]);
//결과
username = member1
age = 20
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> resultList = em.createQuery("select new jpql.MemberDTO(m.username, m.age) from Member m", MemberDTO.class)
.getResultList();
MemberDTO memberDTO = resultList.get(0);
System.out.println("memberDTO.getUsername() = " + memberDTO.getUsername());
System.out.println("memberDTO.getAge() = " + memberDTO.getAge());
//결과
memberDTO.getUsername() = member1
memberDTO.getAge() = 20
SELECT new jpabook.jpql.UserDTO(m.username, m.age) FROM Member mnew 명령어로 조회하는 방법이 가장 깔끔하다. 근데 패키지가 길어진다면 다 적어주는게 너무 길고 문자이기 때문에 한계가 있다.
이런 부분들도 QueryDSL을 사용하면 다 극복이 된다.
JPA는 복잡한 페이징을 다음 두 API로 추상화했다.
페이징 API 예시
String jpql = "select m from Member m order by m.name desc";
List<Member> resultList = em.createQuery(jpql, Member.class)
.setFirstResult(10)
.setMaxResult(20)
.getResultList();
위 쿼리의 MySQL 방언
SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM
MEMBER M
ORDER BY
M.name DESC LIMIT ?, ?
위 쿼리의 Oracle 방언
SELECT * FROM
( SELECT ROW_.*, ROWNUM ROWNUM_
FROM
( SELECT
M.ID AS ID,
M.AGE AS AGE,
M.TEAM_ID AS TEAM_ID,
M.NAME AS NAME
FROM MEMBER M
ORDER BY M.NAME
) ROW_
WHERE ROWNUM <= ?
)
WHERE ROWNUM_ > ?
출처
자바 ORM 표준 JPA 프로그래밍 강의
게시글 속 자료는 모두 위 강의 속 자료를 사용했습니다.