값 타입
- 값 타입
- JPA의 데이터 타입은 엔티티 타입과 값 타입으로 나눌 수 있으며
엔티티 타입은 객체이기 때문에 식별자를 통해 지속적으로 추적할 수 있지만
값 타입은 단순히 값으로 사용하는 자바 기본 타입이나 객체를 말하기 때문에
식별자가 없고 숫자나 문자같은 속성만 있으므로 추적할 수 없음 - 값 타입에는 자바가 제공하는 기본 데이터 타입인 기본값 타입,
사용자가 직접 정의하는 값 타입인 임베디드 타입,
하나 이상의 값 타입을 저장할 때 사용하는 컬렉션 값 타입이 존재
- JPA의 데이터 타입은 엔티티 타입과 값 타입으로 나눌 수 있으며
- 기본값 타입
- 자바가 제공하는 기본 데이터 타입
- 식별자 값이 없어 엔티티에 의존하므로 엔티티 인스턴스를 제거하면 함께 제거되게 되므로 값 타입은 공유하지 않음
만약 같은 값을 사용하고 싶다면 복사를 이용하도록 함
- 임베디드 타입 (복합 값 타입)
- JPA에서 새로운 값 타입을 직접 정의해서 사용할 수 있는 값 타입
- 임베디드 타입을 이용하면 엔티티가 더욱 의미 있고 응집력 있게 변할 수 있음
- 임베디드 타입은 값 타입을 포함하거나 엔티티를 참조할 수도 있음
- 같은 임베디드 타입을 가지고 있는 엔티티와 같은 경우에는
임베디드 타입에 정의한 매핑 정보를 재정의하기 위해서는 @AttributeOverride를 사용 - 임베디드 타입의 경우 엔티티가 영속될 때 함께 저장되고 조회할 때 함께 조회됨
- 예) 집 주소, 근무 기간 등을 함께 엮어 응집력 있게 표현 가능하며 정의한 값 타입들은 재사용할 수 있음
- 값 타입과 불변 객체
- 임베디드 타입 같은 값 타입은 여러 엔티티에 공유 참조하면 하나의 값만 변경되는 것이 아니라
공유된 값도 변경되어 위험하므로 값을 공유하는 대신, clone()을 이용해 복사해서 사용하도록 함 - 또한 객체에서 수정자(Setter)를 만들지 않아 불변 객체로 만들게 되면 값을 공유하더라고 수정할 수 없으므로
부작용을 원천 차단할 수 있으며 만약 값을 수정해야 한다면 새로운 객체를 생성해서 수정하도록 함
- 임베디드 타입 같은 값 타입은 여러 엔티티에 공유 참조하면 하나의 값만 변경되는 것이 아니라
- 값 타입의 비교
- 자바가 제공하는 객체 비교에는 동일성 비교와 동등성 비교가 존재
동일성 비교의 경우 인스턴스의 참조 값을 비교하며 ==를 사용하고,
동등성 비교의 경우 인스턴스의 값을 비교하며 equals()를 사용함 - 값 타입의 경우 인스턴스가 달라도 그 안에 값이 같으면 같은 것으로 보아야 하기 때문에
값 타입을 비교할 때는 equals()를 사용해서 동등성 비교를 해야하므로 equals() 메소드의 재정의가 필요함
- 자바가 제공하는 객체 비교에는 동일성 비교와 동등성 비교가 존재
- 값 타입 컬렉션
- 값 타입을 하나 이상 저장하려면 컬렉션에 보관하고 @ElementCollection, @CollectionTable 어노테이션을 사용함
- 관계형 데이터베이스의 테이블은 컬럼 안에 컬렉션을 보관할 수 없으므로
별도의 테이블을 추가하고 @CollectionTable을 사용해서 추가한 테이블을 매핑하도록 함 - 값 타입 컬렉션은 엔티티와 별도로 저장되며 지연 로딩을 설정할 경우 실제 컬렉션을 사용할 때 호출되게 됨
- 값 타입의 경우 값을 변경하면 데이터베이스에 저장된 원본 데이터를 찾기 어려우므로
값 타입 컬렉션이 매핑된 테이블의 연관된 모든 데이터를 삭제하고
현재 값 타입 컬렉션 객체에 있는 모든 값을 데이터베이스에 다시 저장하게 됨
객체지향 쿼리 언어 ① - 소개
- 객체지향 쿼리 언어
- JPA는 복잡한 검색 조건을 사용해서 엔티티 객체를 조회할 수 있는 다양한 쿼리 기술인 JPQL, Criteria, QueryDSL을 지원
- 객체지향 쿼리 소개
- 가장 단순한 검색 방법으로는 식별자로 조회와 객체 그래프 탐색을 하는 것이며
식별자로 엔티티 하나를 조회한 후, 조회한 엔티티에 객체 그래프 탐색을 사용하여 연관된 엔티티를 찾는 것 - 하지만 이러한 방법만으로는 애플리케이션을 개발하기 힘드므로 좀 더 현실적이고 복잡한 검색 방법이 필요함
- JPA가 공식 지원하는 다양한 검색 방법에는 JPQL, Criteria 쿼리, 네이티브 SQL이 존재하며
JPQL을 편하게 작성하도록 도와주는 빌더 클래스 모음인 QueryDSL이 있고 필요하면 JDBC를 직접 사용할 수도 있음
- 가장 단순한 검색 방법으로는 식별자로 조회와 객체 그래프 탐색을 하는 것이며
- JPQL 소개
- 엔티티 객체를 조회하는 객체지향 쿼리로
JPA는 JPQL을 SQL로 변환해서 데이터베이스를 조회한 후, 조회한 결과로 엔티티를 생성해서 반환함 - 특정 데이터베이스에 의존하지 않으므로 데이터베이스 방언만 변경하면 선택한 방언에 따라 적절한 SQL 함수가 실행됨
- 엔티티 객체를 조회하는 객체지향 쿼리로
select m from Member as m where m.username = 'kim'
- Criteria 쿼리 소개
- JPQL을 생성하는 빌더 클래스로
쿼리가 실행되는 런타임 시점에 오류가 발생하는 문자기반 쿼리인 JPQL의 단점과 달리,
프로그래밍 코드로 JPQL을 작성하므로 컴파일 시점에 오류를 발견할 수 있음 - 엔티티 필드명으로 조회를 하는 JPQL과 달리, 메타 모델을 사용해 필드명도 코드로 작성 가능함
- JPQL을 생성하는 빌더 클래스로
// Criteria 사용 준비
CriteriaBuilder cb = eb.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(eq).getResultList();
- QueryDSL 소개
- Criteria처럼 JPQL 빌더 역할을 하는 오픈소스 프로젝트로
코드 기반이면서 Criteria와 달리 작성한 코드가 JPQL과 비슷하므로 한 눈에 이해가 가능함
- Criteria처럼 JPQL 빌더 역할을 하는 오픈소스 프로젝트로
// 준비
JPAQuery query = new JPAQuery(em);
QMember member = QMember.member;
// 쿼리, 결과 조회
List<Member> members =
query.from(member)
.where(member.username.eq("kim"))
.list(member);
- 네이티브 SQL 소개
- SQL을 직접 사용할 수 있는 기능으로
JPQL을 사용해도 가끔은 특정 데이터베이스에 의존하는 기능을 사용해야 할 때 사용 - 네이티브 SQL은 특정 데이터베이스에 의존하는 SQL을 작성해야 하므로 데이터베이스가 변경되면 함께 수정이 필요
- SQL을 직접 사용할 수 있는 기능으로
String sql = "SELECT ID, AGE, TEAM_ID, NAME FROM MEMBER WHERE NAME = 'kim'";
Query nativeQuery = em.createNativeQuery(sql, Member.class);
- JDBC 직접 사용, 마이바티스 같은 SQL 매퍼 프레임워크 사용
- JDBC 커넥션에 직접 접근하고 싶으면 JPA는 이를 제공하지 안으므로 JPA 구현체가 제공하는 방법을 사용해야 함
- 하이버네이트의 경우 JPA EntityManager에서 하이버네이트 Session을 구한 후 Session의 doWork() 메소드를 호출함
- JDBC나 마이바티스를 JPA와 함께 사용할 때는 JPA를 우회해서 데이터베이스에 접근하므로
우회한 SQL에 대해서는 JPA가 인식하지 못하므로 영속성 컨텍스트와 데이터베이스의 무결성이 훼손될 수 있으므로
JPA를 우회해서 SQL을 실행하기 직전에 영속성 컨텍스트를 수동으로 플러시해서 둘을 동기화시켜야 함 - 스프링 프레임워크의 경우 AOP를 사용해 JPA를 우회해 접근할 경우 플러시하도록 활용 가능
객체지향 쿼리 언어 ② - JPQL
- JPQL
- JPQL은 객체지향 쿼리 언어이므로 테이블을 대상으로 쿼리를 하는 것이 아니라 엔티티 객체를 대상으로 쿼리를 함
- JPQL은 SQL을 추상화하므로 특정 데이터베이스 SQL에 의존하지 않음
- 특정 데이터베이스에 의존하지 않으므로 데이터베이스 방언만 변경하면 선택한 방언에 따라 적절한 SQL 함수가 실행됨
- 기본 문법과 쿼리 API
- SQL과 비슷하게 SELECT, UPDATE, DELETE 문을 사용할 수 있으며
엔티티를 저장할 때는 INSERT 문 대신 EntityManager.persist() 메소드를 사용함 - JPQL을 작성할 때는 엔티티 대신 별칭을 사용하여 작성해야만 함
- 작성한 JPQL을 실행할 때 반환할 타입을 명확하게 지정할 수 있으면 TypeQuery, 그렇지 않으면 Query 객체를 사용함
- 결과를 조회할 때 결과가 정확히 하나면 getSingleResult()를 사용하고, 그렇지 않으면 getResultList()를 사용함
- SQL과 비슷하게 SELECT, UPDATE, DELETE 문을 사용할 수 있으며
- 파라미터 바인딩
- JPQL은 이름 기준 파라미터 바인딩과 이름 기준 파라미터 바인딩을 모두 지원함
- 이름 기준 파라미터는 파라미터를 이름으로 구분하며 이름 기준 파라미터 앞에 :를 사용하고
위치 기준 파라미터는 ? 다음에 위치 값을 주면 되며 위치 값은 1부터 시작함 - 위치 기준 파라미터보다는 이름 기준 파라미터 바인딩을 사용하는 것이 더 명확함
- 프로젝션
- SELECT 절에 조회할 대상을 지정하는 것으로 [SELECT (프로젝션 대상) FROM]으로 대상을 선택함
- 프로젝션 대상에는 엔티티 프로젝션, 임베디드 타입 프로젝션, 스칼라 타입 프로젝션이 존재함
- 또한 Query 객체를 사용하고 제네릭에 Object[]를 사용하면 프로젝션에 여러 값을 선택할 수 있음
- Object[]를 직접 사용하지 않고 객체로 변환해서 사용하기 위해서는 NEW 명령어를 사용하면 반환받을 클래스를 지정 가능
- 페이징 API
- 페이징을 처리하는 SQL 문법을 위해 JPA는 페이징을 두 API인 setFirstResult()와 setMaxResults()를 통해
조회 시작 위치를 적고 조회할 데이터 수를 적어 추상화함 - 데이터베이스 방언 덕분에 데이터베이스에 따라 다른 페이징 처리를 같은 API로 처리가 가능하지만
페이징 SQL을 더 최적화하고 싶다면 JPA가 제공하는 페이징 API가 아닌 네이티브 SQL을 직접 사용해야 함
- 페이징을 처리하는 SQL 문법을 위해 JPA는 페이징을 두 API인 setFirstResult()와 setMaxResults()를 통해
- 집합과 정렬
- 집합은 집합 함수와 함께 통계 정보를 구할 때 사용하며 집합 함수에는 COUNT, MAX, MIN, AVG, SUM이 존재함
- GROUP BY를 사용하면 통계 데이터를 구할 때 특정 그룹끼리 묶을 수 있으며
HAVING을 함께 사용해 그룹화한 통계 데이터를 기준으로 필터링도 가능함 - 정렬은 ORDER BY를 통해 결과를 정렬하며 오름차순의 경우 ASC, 내림차순의 경우 DESC를 사용함
- JPQL 조인
- JPQL도 조인을 지원하며 SQL 조인과 기능은 같고 문법만 약간 다음
- 내부 조인의 경우 INNER JOIN을 사용하고 외부 조인의 경우 LEFT JOIN을 사용하며
다른 엔티티와 연관관계를 가지기 위해 사용하는 필드인 연관 필드를 사용함 - 일대다 관계나 다대다 관계처럼 컬렉션을 사용하는 곳에 조인하기 위해서는 컬렉션 조인을 사용
- WHERE 절을 사용해서 내부 조인에서 세타 조인을 할 수 있어 전혀 관계없는 엔티티도 조회 가능함
- ON 절을 사용하면 조인 대상을 필터링하고 조인할 수 있음
- 페치 조인
- 페치 조인은 조인의 종류가 아니라 JPQL에서 성능 최적화를 위해 제공하는 기능으로
연관된 엔티티나 컬렉션을 한 번에 같이 조회할 수 있어 SQL 한 번으로 연관된 여러 엔티티를 조회할 수 있음 - 즉 일반 JPQL 조인의 경우 연관관계까지 고려하지 않아 지정한 엔티티만 조회하지만 페치 조인의 경우 함께 조회가 가능함
- 하지만 페치 조인은 대상에게 별칭을 줄 수 없어 SELECT, WHERE 절, 서브 쿼리에 사용할 수 없음
- 만약 여러 테이블을 조인해서 엔티티가 가진 모양이 아닌 전혀 다른 결과를 내야할 때는
페치 조인을 사용하기 보다는 여러 테이블에서 필요한 필드들만 조회해서 DTO로 반환하는 것이 효과적
- 페치 조인은 조인의 종류가 아니라 JPQL에서 성능 최적화를 위해 제공하는 기능으로
- 경로 표현식
- JPQL에서 점을 찍어 객체 그래프를 탐색하는 것으로 상태 필드 경로, 단일 값 연관 경로, 컬렉션 값 연관 경로를 지원함
- 상태 필드 경로의 경우 경로 탐색의 끝으로 더는 탐색을 할 수 없지만
단일 값 연관 경로는 묵시적으로 내부 조인이 일어나므로 계속하여 탐색할 수 있고
컬렉션 값 연관 경로를 묵시적으로 내부 조인이 일어나며 FROM 절에서 조인을 통해 별칭을 얻으면 탐색이 가능함
- 서브 쿼리
- JPQL은 SQL처럼 WHERE, HAVING 절에 서브 쿼리를 지원함
- 서브 쿼리 함수에는 EXISTS, {ALL | ANY | SOME}, IN 함수가 존재
- 조건식
- 타입 표현에는 문자, 숫자, 날짜, Boolean, Enum, 엔티티 타입이 존재
- 연산자 우선 순위에는 경로 탐색 연산, 수학 연산, 비교 연산, 논리 연산이 존재
- Between 식, IN 식, Like 식, NULL 비교식이 존재
- 컬렉션에만 사용하는 특별한 기능인 컬렉션 식이 있으며 빈 컬렉션 비교 식과 컬렉션의 멤버 식이 존재
- 스칼라 식에는 수학식, 문자함수, 수학함수, 날짜함수가 존재
- CASE 식에는 기본 CASE, 심플 CASE, COALESCE, NULLIF가 존재
- 다형성 쿼리
- JPQL의 다형성 쿼리를 통해 부모 엔티티를 조회하면서 자식 엔티티도 함께 조회가 가능함
- WHERE 절에 type을 사용해 엔티티의 상속 구조에서 조회 대상을 특정 자식 타입으로 한정할 수도 있으며
treat를 사용하면 상속 구조에서 부모 타입을 특정 자식 타입으로 다룰 수 있음
- 사용자 정의 함수 호출
- JPA 2.1부터 사용자 정의 함수를 지원하므로 하이버네이트 구현체를 사용할 경우에는
방언 클래스를 상속해서 사용자 정의 함수를 구현하고 사용할 데이터베이스 함수를 미리 등록하면 사용이 가능함
- JPA 2.1부터 사용자 정의 함수를 지원하므로 하이버네이트 구현체를 사용할 경우에는
- 기타 정리
- 추가적으로 enum의 경우 = 비교 연산만 지원하며 임베디드 타입의 경우 비교를 지원하지 않음
- JPA 표준은 ''을 길이 0인 Empty String으로 정했지만 데이터베이스에 따라 ''를 NULL로 사용하는 경우도 있음
- 엔티티 직접 사용
- JPQL에서 엔티티의 아이디가 아닌, 엔티티의 별칭을 직접 넘겨주어 엔티티 객체를 직접 사용하면
JPQL이 SQL로 변환될 때 SQL에서는 해당 엔티티의 기본 값을 사용하게 됨
그러므로 엔티티의 아이디인 식별자 값을 직접 사용하는 코드와 SQL 결과가 같아지게 됨 - 마찬가지로 외래 키가 아닌 엔티티를 직접 사용하는 코드도
외래 키와 매핑되어 있으므로 SQL이 변환될 때 해당 엔티티의 외래 키 값을 사용하게 됨
- JPQL에서 엔티티의 아이디가 아닌, 엔티티의 별칭을 직접 넘겨주어 엔티티 객체를 직접 사용하면
- Named 쿼리 (정적 쿼리)
- JPQL 쿼리는 크게 JPQL을 문자로 완성해서 직접 넘겨주는 동적 쿼리와
미리 정의한 쿼리에 이름을 부여해서 필요할 때 사용하는 정적 쿼리로 나누어짐
정적 쿼리의 경우 쿼리에 이름을 부여해서 사용하므로 Named 쿼리라고 하며 한 번 정의하면 변경할 수 없음 - Named 쿼리는 애플리케이션 로딩 시점이 JPQL 문법을 체크하고 미리 파싱해두므로
오류를 빨리 확인할 수 있고 사용되는 시점에 파싱된 결과를 재사용하므로 성능상 이점이 존재함
또한 변하지 않는 정적 SQL이 생성되므로 데이터베이스 조회 성능 최적화에도 도움이 됨 - Named 쿼리는 @NamedQuery 어노테이션을 사용해서 자바 코드를 작성하거나 XML 문서에 작성함
- JPQL 쿼리는 크게 JPQL을 문자로 완성해서 직접 넘겨주는 동적 쿼리와
객체지향 쿼리 언어 ③ - Criteria
- Criteria
- JPQL을 자바 코드로 작성하도록 도와주는 빌더 클래스 API
- 문자가 아닌 코드로 JPQL을 작성하므로 문법 오류를 컴파일 단계에서 잡을 수 있으나
코드가 복잡하고 장황해서 직관적으로 이해하기 힘듦
- Criteria 기초
- Criteria 쿼리를 생성하기 위해서는 EntityManager나 EntityManagerFactory로부터 Criteria 빌더를 얻어와야 함
- Criteria 쿼리 생성
- Criteria를 사용하려면 얻어온 Criteria 빌더의 createQuery() 메소드로 Criteria 쿼리를 생성하게 되며
Criteria 쿼리를 생성할 때 파라미터로 쿼리 결과에 대한 반환 타입을 지정할 수 있음
만약 반환 타입을 지정할 수 없거나 반환 타입이 둘 이상이면 타입을 지정하지 않고 Object로 반환하게 함
- Criteria를 사용하려면 얻어온 Criteria 빌더의 createQuery() 메소드로 Criteria 쿼리를 생성하게 되며
- 조회
- 이후 조회를 위해서 만들어진 Criteria 쿼리에 select문을 추가하고 조회 대상이 여러 건일 경우 multiselct문을 추가함
- 중복을 없애기 위해서는 distinct()를 사용함
- Object[]를 직접 사용하지 않고 객체로 변환해서 사용하기 위해서는 JPQL의 NEW 명령어 대신 construct()를 사용함
- 만약 반환 타입을 튜플로 지정한다면 createTupleQuery() 또는 createQuery(Tuple.class)로 Criteria 쿼리를 생성함
- 집합
- 집합을 위해 groupBy를 사용하면 특정 그룹끼리 묶을 수 있으면 having을 함께 사용해 그룹화 필터링도 가능함
- 정렬
- 정렬은 orderBy를 통해 결과를 정렬하며 오름차순의 경우 ASC, 내림차순의 경우 DESC를 사용함
- 조인
- 조인의 join() 메소드와 JoinType 클래스를 이용해 조인 타입을 정해주며
페치 조인의 경우 fetch() 메소드와 JoinType 클래스를 이용함
- 조인의 join() 메소드와 JoinType 클래스를 이용해 조인 타입을 정해주며
- 서브 쿼리
- 서브 쿼리는 where, having 절에 지원 가능하며
메인 쿼리와 서브 쿼리 간에 관련이 없을 경우에는 단순히 생성한 서브 쿼리를 사용하지만
메인 쿼리와 서브 쿼리 간에 서로 관련이 있을 경우에는 서브 쿼리에서 메인 쿼리의 정보를 사용하기 위해
메인 쿼리에서 사용한 별칭을 얻어야 하므로 메인 쿼리의 Root나 Join을 통해 별칭을 얻어와 사용해야 함
- 서브 쿼리는 where, having 절에 지원 가능하며
- IN 식
- in() 메소드를 사용
- CASE 식
- selectCase() 메소드와 when(), otherwise() 메소드를 사용
- 파라미터 정의
- parameter(타입, 파라미터 이름) 메소드를 사용해서 파라미터를 정의할 수 있음
- 네이티브 함수 호출
- 네이티브 SQL 함수를 호출하기 위해서는 function() 메소드를 사용함
- 동적 쿼리
- 다양한 검색 조건에 따라 실행 시점에 쿼리를 생성하는 동적 쿼리를 사용하기 위해서는
문자 기반인 JPQL보다는 코드 기반인 Criteria로 작성하는 것이 버그를 만날 가능성이 줄어들지만 코드가 읽기 힘듦
- 다양한 검색 조건에 따라 실행 시점에 쿼리를 생성하는 동적 쿼리를 사용하기 위해서는
- 함수 정리
- Criteria는 JPQL 빌더 역할을 하므로 JPQL 함수를 코드로 지원하며
JPQL에서 사용하는 함수는 대부분 CriteriaBuilder에 정의되어 있음 - 그러므로 Expression의 메소드, 조건 함수, 스칼라와 기타 함수, 집합 함수, 분기 함수 등을 지원함
- Criteria는 JPQL 빌더 역할을 하므로 JPQL 함수를 코드로 지원하며
- Criteria 메타 모델 API
- Criteria는 코드 기반이므로 엔티티 필드명으로 조회를 하는 JPQL과 달리,
메타 모델 API를 사용해 필드명도 코드로 작성이 가능하여 오류가 발생할 경우 컴파일 시점에서 알 수 있음 - 이를 위해 코드 생성기를 설정하면 코드 자동 생성기가 엔티티 클래스를 기반으로 메타 모델 클래스를 만들어주게 됨
- Criteria는 코드 기반이므로 엔티티 필드명으로 조회를 하는 JPQL과 달리,
객체지향 쿼리 언어 ④ - QueryDSL
- QueryDSL
- Criteria처럼 JPQL 빌더 역할을 하며 복잡하고 어려운 Criteria를 대체할 수 있음
- QueryDSL 설정
- QueryDSL을 사용하기 위한 QueryDSL JPA 라이브러리와 쿼리 타입을 생성할 때 필요한 라이브러리를 추가한 후,
엔티티를 기반으로 쿼리 타입이라는 쿼리용 클래스를 생성하기 위한 쿼리 타입 생성용 플러그인도 함께 추가가 필요
- QueryDSL을 사용하기 위한 QueryDSL JPA 라이브러리와 쿼리 타입을 생성할 때 필요한 라이브러리를 추가한 후,
- 시작
- QueryDSL을 사용하기 위해서는 쿼리 타입이라는 쿼리용 클래스가 필요하며
이를 생성하기 위해서는 생성자에 별칭을 직접 지정해주거나 보관되고 있는 쿼리 타입의 기본 인스턴스를 사용하도록 함
- QueryDSL을 사용하기 위해서는 쿼리 타입이라는 쿼리용 클래스가 필요하며
- 검색 조건 쿼리
- QueryDSL의 where절에는 and나 or을 사용할 수 있으며
그 외에도 쿼리 타입의 필드는 대부분의 메소드를 명시적으로 제공하고 있으며 검색 조건도 사용할 수 있음
- QueryDSL의 where절에는 and나 or을 사용할 수 있으며
- 결과 조회
- 쿼리 작성이 끝나고 결과 조회 메소드를 호출하면 실제 데이터베이스를 조회하게 됨
- 결과 조회 API에는 uniqueResult(), singResult(), list()가 존재하며
uniqueResult()는 조회 결과가 한 건일 때 사용하며 결과가 하나 이상이면 예외가 발생하게 되고,
singResult()는 조회 결과가 하나 이상이면 처음 데이터를 반환하며,
list()는 조회 결과가 하나 이상일 때 사용하게 됨
- 페이징과 정렬
- 정렬은 orderBy를 사용하며 쿼리 타입이 제공하는 오름차순인 asc(), 내림차순인 desc()를 사용하게 됨
- 페이징은 offset과 limit을 적절히 조합해서 사용하거나 restrict() 메소드의 QueryModifiers 파라미터를 사용함
- 페이징을 처리하려면 검색된 전체 데이터 수를 알아야하므로 결과 조회 API로 list() 대신 listResults()를 사용하게 됨
이는 전체 데이터 조회를 위한 count 쿼리를 한 번 더 실행한 후 반환하게 되므로 전체 데이터 수를 조회할 수 있음
- 그룹
- 그룹은 groupBy를 사용하고 그룹화된 결과를 제한하려면 having을 사용함
- 조인
- innerJoin, leftJoin, rightJoin, fullJoin을 사용할 수 있고 JPQL의 on과 성능 최적화를 위한 fetch 조인도 사용 가능함
- 조인을 사용하려면 첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭으로 사용할 쿼리 타입을 지정함
- 그 외에도 from 절에 여러 조인을 사용하는 세타 조인도 사용 가능함
- 서브 쿼리
- 서브 쿼리를 지원하므로 JPASubQuery를 생성해서 사용함
- 서브 쿼리 결과가 하나일 때는 unique(), 여러 건일 때는 list()를 조회 결과 API로 사용함
- 프로젝션과 결과 반환
- select 절에 조회 대상을 지정하는 프로젝션을 사용할 때
프로젝션 대상이 하나이면 해당 타입으로 반환하고, 여러 필드를 대상으로 선택하면 튜플로 반환되도록 사용함 - 만약 쿼리 결과를 엔티티가 아닌 특정 객체로 받고 싶으면 DTO 클래스를 생성한 후 Projections을 사용하여
bean으로 프로퍼티 접근, fields로 필드 직접 접근, contructor로 생성자 사용으로 DTO 클래스의 값을 채우도록 함
- select 절에 조회 대상을 지정하는 프로젝션을 사용할 때
- 수정, 삭제 배치 쿼리
- QueryDSL은 수정, 삭제 같은 배치 쿼리를 지원하므로 JPAUpdateClause와 JPADeleteClause를 사용하여
영속성 컨텍스트를 무시하고 데이터베이스를 직접 쿼리하여 일괄적으로 한꺼번에 대량의 처리가 가능함
- QueryDSL은 수정, 삭제 같은 배치 쿼리를 지원하므로 JPAUpdateClause와 JPADeleteClause를 사용하여
- 동적 쿼리
- BooleanBuilder를 사용하면 특정 조건에 따른 동적 쿼리를 편리하게 생성할 수 있음
- 메소드 위임
- 메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건을 직접 정의할 수 있음
이를 위해서는 정적 메소드를 만들고 QueryDelegate 어노테이션에 속성으로 이 기능을 적용할 엔티티를 지정한 후
정적 메소드의 첫 번째 파라미터에는 대상 엔티티의 쿼리 타입, 두 번째에는 필요한 파라미터를 정의하면 됨
- 메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건을 직접 정의할 수 있음
객체지향 쿼리 언어 ⑤ - 네이티브 SQL
- 네이티브 SQL
- JPQL은 특정 데이터베이스에 종속적인 기능을 지원하지 않으므로
특정 데이터베이스에 종속적인 기능이 필요할 때 등의 다양한 이유로 JPQL을 사용할 수 없을 때 네이티브 SQL을 사용함 - JPA는 SQL을 직접 사용할 수 있는 네이티브 SQL 기능을 제공하므로
JPQL을 사용하면 JPA가 SQL을 생성하는 반면, 네이티브 SQL은 SQL을 개발자가 직접 정의하게 됨
- JPQL은 특정 데이터베이스에 종속적인 기능을 지원하지 않으므로
- 네이티브 SQL 사용
- 네이티브 쿼리 API에는 엔티티 조회, 값 조회, 결과 매핑 사용 3가지가 존재함
- 엔티티 조회를 위해서 네이티브 SQL은 createNativeQuey(SQL, 결과 클래스)를 사용하며,
조회한 엔티티는 영속성 컨텍스트에서 관리하게 됨 - 값 조회의 경우 엔티티로 조회하지 않고 단순히 값들만 조회하게 되므로 createNativeQuery(sql)을 사용하게 됨
- 엔티티와 스칼라 값을 함께 조회하는 것처럼 매핑이 복잡해지면 @SqlResultSetMapping 어노테이션을 정의해서 사용함
- Named 네이티브 SQL
- JPQL처럼 네이티브 SQL도 Named 네이티브 SQL인 @NamedNativeQuery 어노테이션을 사용해서 정적 SQL을 작성함
- 네이티브 SQL XML에 정의
- Named 네이티브 쿼리를 어노테이션을 사용하는 것이 아닌, XML에 정의할 수 있음
- 이를 위해서는 XML에 <named-native-query>를 먼저 정의하고 <sql-result-set-mappings>를 정의함
- 네이티브 SQL과 JPQL API
- 네이티브 SQL은 JPQL을 사용할 때와 마찬가지로 Query, TypeQuery를 반환하므로
JPQL API를 그대로 사용할 수 있어 페이징 처리 API 등을 적용할 수 있음
- 네이티브 SQL은 JPQL을 사용할 때와 마찬가지로 Query, TypeQuery를 반환하므로
- 스토어드 프로시저
- 스토어드 프로시저는 실행, 처리에 사용되며 여러 쿼리를 한 번에 수행할 수 있도록 하는 쿼리문들의 집합이라고 함
이를 통해 어떤 동작을 여러 쿼리를 거쳐서 일괄적으로 처리할 때 사용되게 됨 - MySQL에는 단순히 입력 값을 두 배로 증가시켜주는 proc_multiply라는 스토어드 프로시저가 존재하므로
이를 이용해 입력 값을 두 배로 증가시켜 결과 값을 만들 수 있음 - 또한 실제 호출할 프로시저에 @NamedStoredProcedureQuery 어노테이션을 이용하면
프로시저에 새로운 이름을 부여해서 사용할 수 있음
- 스토어드 프로시저는 실행, 처리에 사용되며 여러 쿼리를 한 번에 수행할 수 있도록 하는 쿼리문들의 집합이라고 함
객체지향 쿼리 언어 ⑥ - 심화
- 벌크 연산
- 여러 건을 한 번에 수정하거나 삭제하기 위해서 벌크 연산을 사용함
- 벌크 연산은 executeUpdate() 메소드를 사용하며 벌크 연산으로 영향을 받은 엔티티 건수를 반환하게 됨
- 벌크 연산을 사용할 때는 벌크 연산이 영속성 컨텍스트를 무시하고 데이터베이스에 직접 쿼리하므로
영속성 컨텍스트에 있는 엔티티와 데이터베이스에 있는 엔티티의 값이 달라지는 문제가 발생할 수 있음
그러므로 벌크 연산을 수행한 직후 em.refresh()를 사용해 데이터베이스에서 엔티티를 다시 조회하거나,
엔티티를 먼저 조회하지 않고 벌크 연산을 먼저 실행한 후에 엔티티를 조회하거나,
벌크 연산 수행 후 영속성 컨텍스트를 초기화해서 영속성 컨텍스트에 남아있는 엔티티를 제거 해야 함
- 영속성 컨텍스트와 JPQL
- JPQL의 조회 대상에는 엔티티, 임베디드 타입, 값 타입 같이 다양한 종류가 있지만
JPQL로 엔티티를 조회하면 영속성 컨텍스트에서 관리되지만 엔티티가 아니면 영속성 컨텍스트에서 관리되지 않음 - JPQL로 데이터베이스에서 조회한 엔티티가 영속성 컨텍스트에 이미 있을 경우에는
조회한 데이터베이스 결과와 영속성 컨텍스트를 식별자 값을 기준으로 비교하여
만약 이미 영속성 컨텍스트에 있을 경우 기존에 있던 엔티티를 반환하고, 없을 경우 영속성 컨텍스트에 추가하여 반환함 - em.find() 메소드는 엔티티를 영속성 컨텍스트에서 먼저 찾고 없을 경우 데이터베이스에서 찾으므로 성능상 이점이 존재함
하지만 JPQL은 항상 먼저 데이터베이스에서 SQL을 실행해서 결과를 조회하고 비교를 하여 반환하도록 함
- JPQL의 조회 대상에는 엔티티, 임베디드 타입, 값 타입 같이 다양한 종류가 있지만
- JPQL과 플러시 모드
- JPA는 플러시가 일어날 때 영속성 컨텍스트에서 등록, 수정, 삭제한 엔티티를 찾아서 SQL을 만들어 데이터베이스에 반영함
이를 위해 보통 플러시 모드에 따라 커밋하기 직전이나 쿼리 실행 직전에 자동으로 플러시가 호출됨 - JPQL의 경우 영속성 컨텍스트에 있는 데이터를 고려하지 않고 데이터베이스에서 데이터를 조회하므로
JPQL을 실행하기 전에 영속성 컨텍스트의 내용을 데이터베이스에 반영해야만 데이터 무결성 문제가 발생하지 않음
그러므로 플러시 모드를 AUTO로 하여 쿼리 실행 직전에 영속성 컨텍스트를 플러시 하도록 함 - 하지만 플러시가 너무 자주 일어나는 경우 플러시 횟수를 줄이기 위해 COMMIT 모드를 사용해 성능을 최적화할 수 있음
- 추가적으로 JDBC로 쿼리를 직접 수행하면 JPA는 JDBC가 실행한 쿼리를 인식할 방법이 없으므로 플러시가 일어나지 않음
그러므로 쿼리를 실행하기 직전에 em.flush()를 호출하여 영속성 컨텍스트의 내용을 데이터베이스에 동기화하는 것이 안전
- JPA는 플러시가 일어날 때 영속성 컨텍스트에서 등록, 수정, 삭제한 엔티티를 찾아서 SQL을 만들어 데이터베이스에 반영함
+) 자세한 설명 자료
더보기
[자바 ORM 표준 JPA 프로그래밍] 값 타입 - 실전 예제
[자바 ORM 표준 JPA 프로그래밍] 객체지향 쿼리 언어 ① - 소개
[자바 ORM 표준 JPA 프로그래밍] 객체지향 쿼리 언어 ② - JPQL (1)
[자바 ORM 표준 JPA 프로그래밍] 객체지향 쿼리 언어 ② - JPQL (2)
[자바 ORM 표준 JPA 프로그래밍] 객체지향 쿼리 언어 ③ - Criteria
[자바 ORM 표준 JPA 프로그래밍] 객체지향 쿼리 언어 ④ - QueryDSL
'Community > SOLUX' 카테고리의 다른 글
[221222-221228] 2022 2학기 프로젝트 - 5차 스터디 (0) | 2022.12.27 |
---|---|
[221215-221221] 2022 2학기 프로젝트 - 4차 스터디 (0) | 2022.12.21 |
[221201-221207] 2022 2학기 프로젝트 - 2차 스터디 (0) | 2022.12.05 |
[221124-221130] 2022 2학기 프로젝트 - 1차 스터디 (0) | 2022.11.29 |
[221123] 2022 2학기 프로젝트 - 5차 회의 (0) | 2022.11.23 |