10장 객체지향 쿼리 언어
- 객체지향 쿼리 소개
- JPQL
- Criteria
- QueryDSL
- 네이티브 SQL
- 객체지향 쿼리 심화
객체지향 쿼리
EntityManager.find() 메소드를 사용하면 식별자로 엔티티 하나를 조회할 수 있습니다.
이렇게 조회한 엔티티에 객체 그래프 탐색을 사용하면 연관된 엔티티들을 찾을 수 있습니다.
- 식별자로 조회
- 객체 그래프 탐색
간단하게 검색하는 방법은 위 두 가지 방법이 있지만 이 기능만으로 애플리케이션을 개발하기는 힘듭니다.
현실적으로 메모리에 모든 회원 엔티티를 올려두고 애플리케이션에서 필터링하는 것은 비효율적이며, 데이터는 데이터베이스에 있으므로 SQL로 필요한 내용을 최대한 걸러서 조회해야 합니다.
하지만 ORM을 사용하면 데이터베이스 테이블이 아닌 엔티티 객체를 대상으로 개발하므로 검색도 테이블이 아닌 객체를 대상으로 해야 합니다.
JPQL은 이런 문제를 해결하기 위해 만들어졌으며, 다음과 같은 특징이 있습니다.
- 테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리이다.
- SQL을 추상화해서 특정 데이터베이스 SQL에 의존하지 않습니다.
SQL이 DB테이블을 대상으로 하는 쿼리라면 JPQL은 엔티티 객체를 대상으로 하는 객체지향 쿼리입니다.
- JPQL
- Criteria 쿼리: JPQL을 편하게 작성하도록 도와주는 API, 빌더 클래스 모음
- 네이티브 SQL: JPA에서 JPQL대신 직접 SQL을 사용할 수 있음
- QueryDSL: Criteria 쿼리처럼 JPQL을 편하게 작성하도록 도와주는 빌더 클래스 모음, 비표준 오픈소스 프레임워크
- JDBC 직접 사용, MyBatis 같은 SQL매퍼 프레임워크 사용: 필요하면 JDBC를 직접 사용
JPQL
- JPQL은 엔티티 객체를 조회하는 객체지향 쿼리입니다.
- JPQL은 SQL을 추상화해서 특정 데이터베이스에 의존하지 않습니다.
- 같은 SQL함수라도 DB마다 문법이 다를 수 있는데, JPQL이 제공하는 표준화된 함수를 이용하면 선택한 방언에 따라 DB에 맞춘 적절한 SQL함수가 실행됩니다.
- SQL보다 간결합니다.(엔티티 직접 조회, 묵시적 조인, 다형성 지원)
String jpql = "select m from Member as m where m.username = 'Kim'";
List<Member> resultList = em.createQuery(jpql, Member.class).getResultList();
Criteria
- JPQL을 생성하는 빌더 클래스입니다.
- 장점은 문자가 아닌 query.select(m).where(...)처럼 프로그래밍 코드로 JPQL을 작성할 수 있다는 것 입니다.
만약 JPQL에 오타가 있어도 컴파일은 성공하고 배포될 수 있습니다. 하지만 Criteria를 사용하면
- 컴파일 시점에 오류를 발견할 수 있고,
- IDE사용 시 코드 자동완성을 지원하며,
- 동적 쿼리를 작성하기 편해집니다.
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("username"), "kim"));
List<Member> resultList = em.createQuery(cq).getResultList();
위 코드로 인해 JPQL에서 오타를 내서 런타임 에러가 뜰 확률이 적어졌지만, m.get("username")에서 "username" 부분도 문자가 아닌 코드로 변경할 수 있다.
메타 모델 사용
m.get(Member_.username)
Criteria가 가진 장점은 명확하고 많지만 모든 장점을 상쇄할 정도로 복잡하고 장황합니다. 따라서 사용하기 불편하며, Criteria를 이용해 작성한 코드는 한 눈에 들어오지 않는다는 단점이 있다.
QueryDSL
마찬가지로 JPQL 빌더 역할을 합니다.
장점은 코드 기반이면서 단순하고 사용하기 쉽다는 점이 있습니다.
또한 작성한 코드가 JPQL과 비슷해서 한눈에 들어옵니다. (Criteria와 비교시 훨씬 단순하다.)
-> QueryDSL은 JPA 표준은 아니고 오픈소스 프로젝트 입니다.
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member;
List<Member> members =
query.from(member)
.where(member.username.eq("Kim"))
.list(member);
네이티브 SQL
JPA는 SQL을 직접 사용할 수 있는 기능을 지원합니다.
-> 이를 네이티브 SQL이라고 합니다.
JPQL을 사용해도 가끔 특정 DB에 의존하는 기능을 사용해야 할 때가 있는데 (ex: 오라클에만 있는 CONNECT BY기능이나 특정 DB에서만 동작하는 SQL) 이는 JPQL에서 사용할 수 없어서 네이티브 SQL을 사용합니다.
네이티브 SQL의 단점은 특정 데이터베이스에 의존하는 SQL을 작성해야 한다는 것입니다.
-> 따라서 DB를 수정하면 네이티브 SQL도 수정해야 합니다.
String sql = "SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = 'Kim'";
List<Member> resultList = em.createNativeQuery(sql, Member.class).getResultList();
JDBC 직접 사용, 마이바티스 같은 SQL 매퍼 프레임워크 사용
JDBC 커넥터에 직접 접근하고 싶으면 JPA는 JDBC 커넥션을 획득하는 API를 제공하지 않으므로 JPA 구현체가 제공하는 방법을 사용해야 합니다. 하이버네이트에서 직접 JDBC Connection을 획득하는 방법은 아래와 같습니다.
Session session = em.unwrap(Session.class);
session.doWork(new Work()) {
@Override
public void execute(Connection connection) throws SQLException {
//work...
}
});
먼저 JPA EntityManager에서 하이버네이트 Session을 구합니다. 이후 Session의 doWork()메소드를 호출하면 됩니다. JDBC나 마이바티스를 JPA와 함께 사용하면 영속성 컨텍스트를 적절한 시점에 강제로 플러시 해야 합니다. (영속성 컨텍스트와 데이터베이스를 불일치 상태로 만들어서 데이터 무결성을 훼손할 수 있음) 같은 트랜잭션에서 10000원 상품의 가격을 9000원으로 바꾸고 플러시하지 않고 JPA를 우회해서 데이터베이스에 직접 접근하면 데이터베이스의 상품 가격은 아직 10000원 이므로 10000원이 조회됩니다.
이런 이슈를 해결하는 방법은 JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트를 수동으로 플러시해서 데이터베이스와 영속성 컨텍스트를 동기화하면 됩니다.
즉, 데이터베이스에 접근하는 메소드 호출 시 마다 영속성 컨텍스트를 플러시 하면 해결할 수 있습니다.
JPQL
TypeQuery, Query
작성한 JPQL을 실행하려면 쿼리 객체를 만들어야 합니다. 이때 반환할 타입을 명확하게 지정할 수 있으면 TypeQuery를 사용하고, 명확하게 지정할 수 없으면 Query 객체를 사용합니다.
TypedQueryMember> query = em. createQuery ("SELECT m FROM Member m", Member.class);
Query query = em. createQuery ("SELECT m. username, m.age from Member m");
Query를 쓴 부분을 보면 username과 age를 조회하기 떄문에 (String타입, Integer타입) 조회 대상이 명확하지 않기 떄문에 Query객체를 사용했습니다.
결과 조회
query.getResultList(): 결과가 여러개일 때 사용
query.getSingleResult(): 결과가 정확히 하나일 떄 사용
파라미터 바인딩
이름 기준 파라미터: 앞에 :을 사용
위치 기준 파라미터: ? 다음에 위치 값을 줌
파라미터 바인딩 방식을 이용하지 않고 직접 JPQL을 만들면 SQL인젝션 공격을 당할 수 있습니다.
또한 성능 측면에서도 애플리케이션과 데이터베이스 모두 해당 쿼리의 파싱 결과를 재사용할 수 있기 때문에 전체 성능이 향상됩니다.
따라서 파라미터 바인딩 방식은 필수로 적용해야 합니다.
'Backend > Spring Boot' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 14장 (0) | 2023.07.11 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 13장 (0) | 2023.07.04 |
[자바 ORM 표준 JPA 프로그래밍] 9장 (0) | 2023.05.23 |
[자바 ORM 표준 JPA 프로그래밍] 8장 (1) | 2023.05.23 |
[자바 ORM 표준 JPA 프로그래밍] 7장 (0) | 2023.05.23 |