
JPA를 엔티티 중심으로 사용하면서, 복잡한 조회·집계·조인을 DB 벤더에 독립적으로 작성하게 해 주는 객체지향 쿼리 언어. EntityManager가 JPQL → SQL로 변환해 DB에 항상 질의(find()와 달리 1차 캐시 우선 조회 X).
키워드: SELECT / UPDATE / DELETE (INSERT는 persist() 사용)
별칭 필수: SELECT m FROM Menu m
대소문자: 키워드 무관, 엔티티/필드명은 구분
실행 절차
createQuery(String jpql, Class<T> type) → TypedQuery<T>
getSingleResult() 혹은 getResultList()로 실행
getSingleResult()는 없거나 여러 건이면 예외NoResultException, NonUniqueResultException)getResultList()는 결과 없으면 빈 컬렉션// 단일 컬럼 (TypedQuery)
String jpql = "SELECT m.menuName FROM Section01Menu m WHERE m.menuCode = 8";
String name = em.createQuery(jpql, String.class).getSingleResult();
// 단일 행 엔티티
Menu menu = em.createQuery("SELECT m FROM Section01Menu m WHERE m.menuCode=8", Menu.class)
.getSingleResult();
문자열 더하기 대신 바인딩으로 안전하게.
:name?1 (1부터 시작)em.createQuery("SELECT m FROM Section02Menu m WHERE m.menuName = :name", Menu.class)
.setParameter("name", "한우딸기국밥")
.getResultList();
em.createQuery("SELECT m FROM Section02Menu m WHERE m.menuName = ?1", Menu.class)
.setParameter(1, "한우딸기국밥")
.getResultList();
// 엔티티
List<Menu> menus = em.createQuery("SELECT m FROM Section03Menu m", Menu.class).getResultList();
// 임베디드
List<MenuInfo> infos = em.createQuery("SELECT m.menuInfo FROM EmbeddedMenu m", MenuInfo.class)
.getResultList();
// 스칼라
List<String> names = em.createQuery("SELECT c.categoryName FROM Section03Category c", String.class)
.getResultList();
// DTO
List<CategoryInfo> dto = em.createQuery(
"SELECT new com.example.CategoryInfo(c.categoryCode, c.categoryName) " +
"FROM Section03Category c", CategoryInfo.class
).getResultList();
DBMS별 페이징을 JPA가 추상화. 정렬 필수로 일관된 결과 보장.
List<Menu> page = em.createQuery(
"SELECT m FROM Section04Menu m ORDER BY m.menuCode DESC", Menu.class)
.setFirstResult(10) // offset (0부터)
.setMaxResults(5) // limit
.getResultList();
COUNT / SUM / AVG / MAX / MINLong cnt = em.createQuery(
"SELECT COUNT(m.menuPrice) FROM Section05Menu m WHERE m.categoryCode=:c", Long.class)
.setParameter("c", 4)
.getSingleResult();
List<Object[]> rows = em.createQuery(
"SELECT m.categoryCode, SUM(m.menuPrice) " +
"FROM Section05Menu m GROUP BY m.categoryCode HAVING SUM(m.menuPrice) >= :min")
.setParameter("min", 50_000L)
.getResultList();
JOINLEFT JOIN / RIGHT JOINc.menuList 처럼 컬렉션에 조인FROM A a, B b (비권장, 조건 누락 주의)// 내부조인
List<Menu> list = em.createQuery(
"SELECT m FROM Section06Menu m JOIN m.category c", Menu.class).getResultList();
// 외부조인 (스칼라)
List<Object[]> rows = em.createQuery(
"SELECT m.menuName, c.categoryName FROM Section06Menu m RIGHT JOIN m.category c " +
"ORDER BY m.category.categoryCode").getResultList();
// 컬렉션 조인
List<Object[]> rows2 = em.createQuery(
"SELECT m.menuName, c.categoryName FROM Section06Category c LEFT JOIN c.menuList m")
.getResultList();
연관 엔티티/컬렉션을 한 번에 로딩. 지연 로딩 대신 즉시 로딩처럼 동작.
List<Menu> list = em.createQuery(
"SELECT m FROM Section06Menu m JOIN FETCH m.category", Menu.class)
.getResultList();
주의
- 컬렉션 페치 조인과 페이징 동시 사용 불가
- 중복 로우가 생길 수 있으니 필요 시
SELECT DISTINCT로 엔티티 중복 제거
WHERE / HAVING에서만 사용 가능(SELECT/FROM 불가).
List<Menu> list = em.createQuery(
"SELECT m FROM Section07Menu m " +
"WHERE m.categoryCode = (" +
"SELECT c.categoryCode FROM Section07Category c WHERE c.categoryName = :name" +
")", Menu.class).setParameter("name", "한식").getResultList();
미리 정의된 정적 쿼리. 애플리케이션 기동 시 파싱되어 안전하고 재사용성 높음.
@NamedQueries({
@NamedQuery(name="Section08Menu.selectMenuList",
query="SELECT m FROM Section08Menu m")
})
// 사용
List<Menu> list = em.createNamedQuery("Section08Menu.selectMenuList", Menu.class)
.getResultList();
spring:
jpa:
mapping-resources:
- section08/namedquery/menu-query.xml
<named-query name="Section08Menu.selectMenuByCode">
<query>
SELECT m FROM Section08Menu m WHERE m.menuCode = :menuCode
</query>
</named-query>
Menu m = em.createNamedQuery("Section08Menu.selectMenuByCode", Menu.class)
.setParameter("menuCode", 20)
.getSingleResult();
getSingleResult() 예외 처리 습관화.setFirstResult(0) 기준.LIKE '%…%'는 인덱스 활용이 어려움. 접두 매칭이나 검색 전용 저장소 고려.=, <, >, <=, >=, <>, BETWEEN, IN, LIKE, IS NULLAND, OR, NOTORDER BY a.prop ASC|DESCGROUP BY, HAVINGJOIN, LEFT JOIN, JOIN FETCHCOUNT, SUM, AVG, MAX, MIN, DISTINCT