JPA는 복잡한 검색 조건을 사용해서 엔티티 객체를 조회할 수 있는 다양한 쿼리기술을 지원한다.
JPQL은 가장 중요한 객체지향 쿼리 언어이다. 다른 Criteria나 QueryDSL은 JPQL을 편리하게 사용하도록 도와주는 기술이므로 JPA를 다루는 개발자라면 JPQL을 필수로 학습해야 된다.
EntityManager.find()
메소드를 사용하면 식별자로 엔티티 하나를 조회할 수 있다.
검색 방법은 두가지다.
JPQL을 사용하게 되면 JPA는 이 JPQL을 분석한 다음 적절한 SQL을 만들어 DB를 조회한다. 그리고 그 결과로 엔티티 객체를 생성하여 반환해준다.
JPQL은 객체지향 SQL이라고 정의할 수 있다.
@Test
void jpqlTest() {
Member member = new Member("kim");
em.persist(member);
em.flush();
String jpql = "select m from Member as m where m.name = 'kim'";
List<Member> resultList = em.createQuery(jpql, Member.class).getResultList();
assertThat(resultList.get(0).getName()).isEqualTo("kim");
}
실행결과는 다음과 같다.
다른 값들은 생성자에서 설정 안해주었기 때문에 넣지 않았다.
근데 조금 다른 것이 있다.
그것은 원래 우리가 사용하던 방식인 em.find()
를 사용했을 때에는 영속성 컨텍스트인 member가 지금 존재하기 때문에 SELECT쿼리를 날리지 않고 1차캐시에서 조회했었다.
그렇지만 JPQL은 직접 SQL을 추상화해서 사용하기 때문에 쿼리를 날려서 조회하게 된다. 그래서 항상 DB에서 찾는데 이 찾은 엔티티가 영속성 컨텍스트에 존재한다면? DB에서 찾는 데이터를 버리고 영속성 컨텍스트에 있는 반환값을 넘긴다
Criteria의 장점은 문자가 아닌 query.select(m).where(...)
처럼 메소드 체이닝으로 JPQL을 작성할 수 있다.
장점이 많지만 모든 장점을 상쇄할 정도로 복잡하고 장황하다. 따라서 사용하기 불편한건 물론이고 Criteria로 작성한 코드도 한눈에 들어오지 않는 단점이 있음.
@Test
void criteriaTest() {
//Criteria 사용 준비
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Member> query = cb.createQuery(Member.class);
//루트 클래스(조회를 시작할 클래스)
Root<Member> m = query.from(Member.class);
//쿼리 생성
CriteriaQuery<Member> cq = query.select(m).where(cb.equal(m.get("name"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList();
}
QueryDSL도 Criteria처럼 JPQL 빌더 역할을 한다. QueryDSL의 장점은 코드 기반이면서 단순하고 사용하기 쉽다. 작성한 코드도 JPQL과 비슷해서 한눈에 들어온다.
QueryDSL은 JPA 표준은 아니고 오픈소스 프로젝트이다.
코드를 보자
//세팅
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member;
//쿼리, 결과조회
List<Member> members = query.from(member)
.where(member.name.eq("kim"))
.list(member);
여기서 QMember 클래스는 Member 엔티티 클래스를 기반으로 생성한 QueryDSL 쿼리 전용 클래스이다.
여기까지 보면 이제는 JPA와 QueryDSL로 전반적인 코드를 작성할 수 있을것 같다.
그리고 추가적으로 정말 복잡한 것이라면 네이티브 쿼리를 사용하는것도 방법이겠다.
SQL을 직접 사용하는 기능이 바로 네이티브 쿼리이다. SI회사에 근무했을 당시 MyBatis, iBatis 많이 사용했는데 바꾸려는 시도가 최근에 이루어졌었기 때문에 점점 JPA가 편해져 가는중이지만 그래도 익숙해보인다.
내가 MySQL이나 MariaDB를 배웠는데 회사에서 Oracle, PostgreSQL 사용한다면 바꿔야 한다는 얘기이다...
String sql = "SELECT id, name, age FROM Member WHERE name='kim'";
List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();
JDBC를 사용하는것과 흡사하다. 다른점은 다음에서 설명하겠다.
Hibernate에서 JDBC 커넥션을 획득하는 방법
Session session = em.unwrap(Session.class);
session.doWork(new Work() {
@Override
public void execute(Connection connection) throws SQLException {
//작업..
}
});
위에서 다른점이라고 하는것이 이부분인데,
JDBC나 MyBatis를 JPA와 함께 사용하면 영속성 컨텍스트 관리가 안되는 애들이기 때문에 컨텍스트를 적절한 시점에 강제로 플러시 해줘야 한다. 영속성 컨텍스트와 DB 불일치로 데이터 무결성을 훼손해선 절대적으로 안된다.