프로젝션은 셀렉트 절에 조회할 대상을 지정하는 것을 말한다.
SELECT m FROM Member m
SELECT m.team FROM Member m
SELECT m.address FROM Member m
SELECT 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 m
new 명령어로 조회하는 방법이 가장 깔끔하다. 근데 패키지가 길어진다면 다 적어주는게 너무 길고 문자이기 때문에 한계가 있다.
이런 부분들도 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 프로그래밍 강의
게시글 속 자료는 모두 위 강의 속 자료를 사용했습니다.