5-1. Simple
1) JPQL(Java Persistence Query Language)
- 엔티티 객체를 중심으로 개발할 수 있는 객체 지향 쿼리
- SQL보다 간겨랗며 DBMS에 상관 없이 개발 가능
(방언을 통해 해결되며 해당 DBMS에 맞는 SQL 실행)
- find() 메소드를 통한 조회와 다르게 항상 데이터베이스에 SQL을 실행해서 결과 조회
(영속성 컨텍스트에 이미 존재하면 기존 엔티티를 반환하고 조회한 것은 버림)
- JPQL은 엔티티 객체를 대상으로 쿼리를 질의하고 SQL은 데이터베이스의 테이블을 대상으로 질의 -> 결국 JPQL은 SQL로 변환
- 기본 문법
- SELECT
- SELECT FROM WHERE GROUP BY HAVING ORDER BY
- INSERT
- ENTITYMANAGER가 제공하는 persist() 메소드 사용
- UPDATE
- DELETE
- 특징
- 엔티티와 속성은 대소문자 구분
- SELECT, FROM 과 같은 기본 키워드는 대소문자 구분 안함
- 별칭을 필수로 사용
- 별칭 없이 작성 시 에러 발생
- JPQL 사용 방법
- 작성한 JPQL 문다열을 em.createQuery 메소드를 통해 쿼리 객체로 만듦
- 쿼리 객체는 TypedQuery, Query 두 가지가 있음
- TypedQuery
- Query
- 반환할 타입을 명확하게 지정할 수 없을 때 사용
- 쿼리 객체에서 제공하는 메소드 getSingleResult() 또는 getResultList()를 호출해서 쿼리를 실행하고 데이터베이스 조회
- getSingleResult()
- getResultList()
- 결과가 2행 이상일 경우 사용하며 컬렉션 반환
@Test
public void TypeQuery를_이용한_단일메뉴_조회_테스트() {
String jpql = "SELECT m.menuName FROM section01_menu as m WHERE m.menuCode=7";
TypedQuery<String> query = entityManager.createQuery(jpql, String.class);
String resultMenuName = query.getSingleResult();
assertEquals("민트미역국", resultMenuName);
}
@Test
public void Query를_이용한_단일메뉴_조회_테스트() {
String jpql = "SELECT m.menuName FROM section01_menu as m WHERE m.menuCode=7";
Query query = entityManager.createQuery(jpql);
Object resultMenuName = query.getSingleResult();
assertTrue(resultMenuName instanceof String);
assertEquals("민트미역국", resultMenuName);
}
@Test
public void JPQL을_이용한_단일형_조회_테스트() {
String jpql = "SELECT m FROM section01_menu as m WHERE m.menuCode=7";
TypedQuery<Menu> query = entityManager.createQuery(jpql, Menu.class);
Menu foundMenu = query.getSingleResult();
assertEquals(7, foundMenu.getMenuCode());
System.out.println(foundMenu);
}
@Test
public void TypedQuery을_이용한_여러행_조회_테스트() {
String jpql = "SELECT m FROM section01_menu as m";
TypedQuery<Menu> query = entityManager.createQuery(jpql, Menu.class);
List<Menu> foundMenuList = query.getResultList();
assertNotNull(foundMenuList);
foundMenuList.forEach(System.out::println);
}
@Test
public void Query을_이용한_여러행_조회_테스트() {
String jpql = "SELECT m FROM section01_menu as m ORDER BY m.menuCode";
Query query = entityManager.createQuery(jpql);
List<Menu> foundMenuList = query.getResultList();
assertNotNull(foundMenuList);
foundMenuList.forEach(System.out::println);
}
@Test
public void distinct를_활용한_중복제거_여러_행_조회_테스트() {
String jpql = "SELECT DISTINCT m.categoryCode FROM section01_menu m ORDER BY m.categoryCode";
TypedQuery<Integer> query = entityManager.createQuery(jpql, Integer.class);
List<Integer> foundList = query.getResultList();
assertNotNull(foundList);
System.out.println(foundList);
}
@Test
public void in_연산자를_활용한_조회_테스트() {
String jpql = "SELECT m FROM section01_menu m WHERE m.categoryCode IN (6,10)";
Query query = entityManager.createQuery(jpql);
List<Menu> foundList = query.getResultList();
assertNotNull(foundList);
foundList.forEach(System.out::println);
}
@Test
public void like_연산자를_활용한_조회_테스트() {
String jpql = "SELECT m FROM section01_menu m WHERE m.menuName like '%마늘%'";
Query query = entityManager.createQuery(jpql);
List<Menu> foundList = query.getResultList();
assertNotNull(foundList);
foundList.forEach(System.out::println);
}
5-2. Parameter
@Test
public void 이름_기준_파라미터_바인딩_메뉴_목록_조회_테스트() {
String menuNameParameter = "한우딸기국밥";
String jpql = "SELECT m FROM section02_menu m WHERE m.menuName = :menuName";
List<Menu> menuList = entityManager.createQuery(jpql, Menu.class)
.setParameter("menuName", menuNameParameter)
.getResultList();
assertNotNull(menuList);
menuList.forEach(System.out::println);
}
@Test
public void 위치_기준_파라미터_바인딩_메뉴_목록_조회_테스트() {
String menuNameParameter = "한우딸기국밥";
String jpql = "SELECT m FROM section02_menu m WHERE m.menuName = ?1";
List<Menu> menuList = entityManager.createQuery(jpql).setParameter(1, menuNameParameter).getResultList();
assertNotNull(menuList);
menuList.forEach(System.out::println);
}
5-3. Projection
- SELECT절에 조회할 대상을 지정하는 것
- 엔티티 프로젝션
- 원하는 객체 바로 조회 가능
- 조회된 엔티티는 영속성 컨텍스트가 관리
- 임베디드 타입 프로젝션
- 엔티티와 거의 비슷하게 사용
- 조회의 시작점이 될 수 없음(from절에 사용 불가)
- 영속성 컨텍스트에서 관리 안됨
- 스칼라 타입 프로젝션
- 숫자, 문자, 날짜 같은 기본 데이터 타입
- 영속성 컨텍스트에서 관리 안됨
- new 명령어를 활용한 프로젝션
5-4. Paging
public void 페이징_API를_이용한_조회_테스트() {
int offset = 5;
int limit = 5;
String jpql = "SELECT m FROM section04_menu m ORDER BY m.menuCode DESC";
List<Menu> menuList = entityManager.createQuery(jpql, Menu.class).setFirstResult(offset)
.setMaxResults(limit)
.getResultList();
assertNotNull(menuList);
menuList.forEach(System.out::println);
}
5-5. GoupFunction
- COUNT, MAX, MIN, SUM, AVG로 SQL의 그룹함수와 별반 차이 없음
- 주의사항
- 그룹함수의 반환 타입은 결과 값이 정수이면 Long, 실수면 Double로 반환
- 값이 없는 상태에서 count를 제외한 그룹 함수는 null이 되고 count만 0
- 반환 값을 담기 위해 선언하는 변수 타입을 기본 자료형으로 하게 되면, 조회결과를 언박싱 할 때 NPE 발생
- Having절에서 그룹 함수 결과값과 비교하기 위한 파라미터 타입은 Long or Double로 해야 함
@Test
public void 특정_카테고리의_등록된_메뉴_수_조회() {
int categoryCodeParameter = 3;
String jpql = "SELECT COUNT(m.menuPrice) FROM section05_menu m WHERE m.categoryCode = :categoryCode";
long countOfMenu = entityManager.createQuery(jpql, Long.class).setParameter("categoryCode", categoryCodeParameter).getSingleResult();
assertTrue(countOfMenu >= 0);
System.out.println("메뉴 갯수 = " + countOfMenu);
}
@Test
public void count를_제외한_다른_그룹함수의_조회결과가_없는_경우_테스트() {
int categoryCodeParameter = 2;
String jpql = "SELECT SUM(m.menuPrice) FROM section05_menu m WHERE m.categoryCode = :categoryCode";
assertThrows(NullPointerException.class, () -> {
long sumOfPrice = entityManager.createQuery(jpql, Long.class).setParameter("categoryCode", categoryCodeParameter).getSingleResult();
});
assertDoesNotThrow(() -> {
Long sumOfPrice = entityManager.createQuery(jpql, Long.class).setParameter("categoryCode", categoryCodeParameter).getSingleResult();
System.out.println(sumOfPrice);
});
}
@Test
public void groupby절과_having절을_사용한_조회_테스트() {
long minPrice = 50000L;
String jpql="SELECT m.categoryCode, SUM(m.menuPrice) FROM section05_menu m GROUP BY categoryCode HAVING SUM(m.menuPrice) >= :minPrice";
List<Object[]> sumPriceOfCategoryList = entityManager.createQuery(jpql, Object[].class).setParameter("minPrice", minPrice).getResultList();
assertNotNull(sumPriceOfCategoryList);
sumPriceOfCategoryList.forEach(row -> {
for(Object column : row) System.out.println(column + " ");
});
}
5-6. Join
- 일반 조인
- 일반적인 SQL조인 의미(내부조인, 외부조인, 컬렉션 조인, 세타조인)
- 패치 조인
- JPQL에서 성능 최적하를 위해 제공하는 기능으로 연관 된 엔티티나 컬렉션을 한 번에 조회 가능
- 지연 로딩이 아닌 즉시 로딩을 수행
- join fetch 명령어 사용
@Test
public void 내부조인을_이용한_조회_테스트() {
String jpql = "SELECT m FROM section06_menu m JOIN m.category c";
List<Menu> menuList = entityManager.createQuery(jpql, Menu.class).getResultList();
assertNotNull(menuList);
menuList.forEach(System.out::println);
}
@Test
public void 외부조인을_이용한_조회_테스트() {
String jpql = "SELECT m.menuName, c.categoryName FROM section06_menu m RIGHT JOIN m.category c ORDER BY m.category.categoryCode";
List<Object[]> menuList = entityManager.createQuery(jpql, Object[].class).getResultList();
assertNotNull(menuList);
menuList.forEach(row -> {
Stream.of(row).forEach(col -> System.out.print(col + " "));
System.out.println();
});
}
@Test
public void 컬렉션조인을_이용한_조회_테스트() {
String jpql = "SELECT c.categoryName, m.menuName FROM section06_category c LEFT JOIN c.menuList m";
List<Object[]> categoryList = entityManager.createQuery(jpql, Object[].class).getResultList();
assertNotNull(categoryList);
categoryList.forEach(row -> {
Stream.of(row).forEach(col -> System.out.print(col + " "));
System.out.println();
});
}
@Test
public void 세타조인을_이용한_조회_테스트() {
String jpql = "SELECT c.categoryName, m.menuName FROM section06_category c, section06_menu m";
List<Object[]> categoryList = entityManager.createQuery(jpql, Object[].class).getResultList();
assertNotNull(categoryList);
categoryList.forEach(row -> {
Stream.of(row).forEach(col -> System.out.print(col + " "));
System.out.println();
});
}
@Test
public void 패치조인을_이용한_조회_테스트() {
String jpql = "SELECT m FROM section06_menu m JOIN FETCH m.category c";
List<Menu> menuList = entityManager.createQuery(jpql, Menu.class).getResultList();
assertNotNull(menuList);
menuList.forEach(System.out::println);
}
5-7. SubQuery
- select, from 절에서 사용 불가.
- where, having 절에서만 사용 가능.
@Test
public void 서브쿼리를_이용한_메뉴_조회_테스트() {
String categoryNameParameter = "한식";
String jpql = "SELECT m FROM section07_menu m WHERE m.categoryCode "
+ "= (SELECT c.categoryCode FROM section07_category c WHERE c.categoryName = :categoryName)";
List<Menu> menuList = entityManager.createQuery(jpql, Menu.class)
.setParameter("categoryName", categoryNameParameter)
.getResultList();
assertNotNull(menuList);
menuList.forEach(System.out::println);
}
5-8. NamedQuery
- 동적 쿼리
- 현재 우리가 사용하는 방식
- EntityManager가 제공하는 메소드를 이용하여 JPQL 문자열로 런타임 시점에 동적으로 쿼리를 만드는 방식
- 동적으로 만들어질 쿼리를 위한 조건식이나 반복문은 자바 코드 이용 가능
- 정적 쿼리
- 미리 쿼리를 정의하고 변경하지 않고 사용하는 쿼리
- 미리 정의한 코드는 이름을 부여해서 사용
@Test
public void 동적쿼리를_이용한_조회_테스트() {
String searchName = "한우";
int searchCategoryCode = 0;
StringBuilder jpql = new StringBuilder("SELECT m FROM section08_menu m ");
if(searchName != null && !searchName.isEmpty() && searchCategoryCode > 0) {
jpql.append("WHERE ");
jpql.append("m.menuName LIKE '%' || : menuName || '%' ");
jpql.append("AND ");
jpql.append("m.categoryCode = :categoryCode");
}else {
if(searchName != null && !searchName.isEmpty()) {
jpql.append("WHERE ");
jpql.append("m.menuName LIKE '%' || : menuName || '%'");
}else if(searchCategoryCode > 0) {
jpql.append("WHERE ");
jpql.append("m.categoryCode = :categoryCode");
}
}
TypedQuery<Menu> query = entityManager.createQuery(jpql.toString(), Menu.class);
if(searchName != null && !searchName.isEmpty() && searchCategoryCode > 0) {
query.setParameter("menuName", searchName);
query.setParameter("categoryCode", searchCategoryCode);
}else {
if(searchName != null && !searchName.isEmpty()) {
query.setParameter("menuName", searchName);
}else if(searchCategoryCode > 0) {
query.setParameter("categoryCode", searchCategoryCode);
}
}
List<Menu> menuList = query.getResultList();
assertNotNull(menuList);
menuList.forEach(System.out::println);
}
@Test
public void 어노테이션_기반_네임드쿼리를_이용한_조회_테스트() {
List<Menu> menuList = entityManager.createNamedQuery("section08_menu.selectMenuList", Menu.class).getResultList();
assertNotNull(menuList);
menuList.forEach(System.out::println);
}
@Test
public void xml기반_네임드쿼리를_이용한_조회_테스트() {
int menuCodeParameter = 21;
Menu foundMenu = entityManager.createNamedQuery("section08_menu.selectMenuNameByCode", Menu.class).setParameter("menuCode", menuCodeParameter).getSingleResult();
assertNotNull(foundMenu);
System.out.println(foundMenu);
}
@Entity(name="section08_menu")
@Table(name="TBL_MENU")
@NamedQueries({
@NamedQuery(name="section08_menu.selectMenuList", query="SELECT m FROM section08_menu m")
})
public class Menu {
@Id
@Column(name="MENU_CODE")
private int menuCode;
@Column(name="MENU_NAME")
private String menuName;
@Column(name="MENU_PRICE")
private int menuPrice;
@Column(name="CATEGORY_CODE")
private int categoryCode;
@Column(name="ORDERABLE_STATUS")
private String orderableStatus;