JPA find()
메소드 만으로는 애플리케이션 개발을 하기 어렵다. 더 현실적이고 복잡한 검색 방법이 필요한다. 그렇다고 모든 엔티티를 메모리에 올려두고 애필리케이션에서 검색하는 것은 현실성이 없다.
결국 데이터베이스 쿼리를 통하여 원하는 검색 조건의 결과를 애플리케이션에 가져와야한다.
하지만 ORM을 사용하면 데이터베이스 테이블이 아닌 엔티티 객체를 대상으로 개발하므로 검색도 테이블이 아닌 엔티티 객체를 대상으로 하는 방법이 필요하다.
SQL이 데이터베이스 테이블을 대상으로 하는 데이터 중심의 쿼리라면 JPQL은 엔티티 객체를 대상으로 하는 객체지향 쿼리다.
JPQL을 사용하면 JPA는 이 JPQL을 분석한 다음 적절한 SQL을 만들어 데이터베이스를 조회한다. 그리고 조회한 결과로 엔티티 객체를 생성하여 반환한다.
JPA는 JPQL뿐만 아니라 다양한 검색 방법을 제공한다. 다음은 JPA가 공식 지원하는 기능이다.
- JPQL
- Criteria
- 네이티브 SQL
아래는 공식 지원하는 기능은 아니지만 알아둘 가치가 있는 기능이다.
- QueryDSL
- JDBC 직접 사용, MyBatis와 같은 SQL 매퍼 프레임워크 사용
JPQL(Java Persistence Query Language)은 엔티티 객체를 조회하는 객체지향 쿼리다. 문법은 SQL과 비슷하고 ANSI 표준 SQL이 제공하는 기능을 유사하게 지원한다.
Criteria는 JPQL을 생성하는 빌더 클래스이다. Criteria의 장점은 문자가 아니니 query.select().where()
처럼 프로그래밍 코드로 JPQL을 작성할 수 있다는 점이다.
하이버네이트를 포함한 몇몇 ORM 프레임워크들은 이미 오래 전부터 자신만의 Criteria를 지원했다. JPA는 2.0부터 Criteira를 지원한다.
메타 모델 API를 사용하면 Criteria에 사용되는 필드 명까지 코드로 작성할 수 있어 온전히 코드만 사용하여 쿼리를 작성할 수 있다.
Criteria는 다음과 같은 장점이 있다.
다만 Criteria가 가진 장점이 많지만 모든 장점을 상쇄할 정도로 복잡하고 장황하다. 따라서 사용하기 불편한건 물론이고 Criteria로 작성한 코드도 한눈에 들어오지 않는다는 단점이 있다.
QueryDSL도 Criteria처럼 JPQL 빌더 역할을 한다. QueryDSL의 장점은 코드 기반이면서 단순하고 사용하기 쉽다는 점이다. 작성한 코드도 JPQL과 비슷해서 한눈에 들어온다. Criteria는 QueryDSL에 비하여 너무 복잡하다.
QueryDSL은 JPA 표준이 아닌 오픈소스 프로젝트이다. JPA뿐만 아니라 JDO, 몽고DB, Java Collection, Lucene, Hibernate Search 도 거의 같은 문법으로 지원한다.
JPA는 SQL을 직접 사용할 수 있는 기능을 지원한다. 이것을 네이티브 SQL이라 한다.
JPQL을 사용해도 가끔은 특정 데이터베이스에 의존하는 기능을 사용해야할 때가 있다. 이런 기능들은 전혀 표준화되어 있지 않으므로 JPQL에서 사용할 수 없다.
그리고 SQL은 지원하지만 JPQL이 지원하지 않는 기능도 있다. 이때는 네이티브 SQL을 사용하면 된다.
네이티브 SQL의 단점은 특정 데이터베이스에 의존하는 SQL을 작성해야 한다는 것이다. 따라서 데이터베이스를 변경하면 네이티브 SQL도 수정해야한다.
JDBC나 MyBatis 등 JPA외 다른 방식으로 데이터베이스를 조작할 수 있다. 이것들을 JPA와 함께 사용하면 영속성 컨텍스트를 적절한 시점에 강제로 플러시해야한다. JDBC나 MyBatis 등으로 데이터베이스에 쿼리를 날릴 경우, JPA는 해당 조작을 전혀 인식하지 못한다. 최악의 시나리오는 영속성 컨텍스트와 데이터베이스를 불일치 상태로 만들어 데이터 무결성이 훼손되는 것이다.
이런 이슈를 해결하는 방법은 JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트를 수동으로 플러시해서 데이터베이스와 영속성 컨텍스트를 동기화하면 된다.
자바 ORM 표준 JPA 프로그래밍 10장