반응형
데이터를 조회하는 두 가지 방법(HQL, Criteria)에 대해서 살펴본다.
HQL을 사용한 데이터 조회
Hibernate는 HQL 실행 방식, Criteria 실행 방식 그리고 SQL 직접 실행 방식의 세 가지 데이터 조회 방식을 제공한다. 이들 세 가지 데이터 조회 방식 중 HQL을 사용하는 방법에 대해서 살펴보도록 하자.
HQL은 SQL과 비슷한 형태의 구문을 갖는 Hibernate의 쿼리 언어로서 OR 매핑 파일에서 설정한 객체를 쿼리문구에서 사용할 수 있다는 특징이 있다. 또한, SQL 만큼 쉽기 때문에 다양한 형태로 데이터를 조회하고자 할 때 유용하게 사용될 수 있다.
HQL의 기본
HQL을 이용하는 가장 기본적인 코드는 다음과 같다.
Hibernate Session은 createQuery(String hql) 메소드는 전달받은 HQL을 실제 쿼리로 변환하여 수행하는 net.sf.hibernate.Query 객체를 리턴한다. Query는 쿼리 실행과 관련된 인터페이스로서 다양한 조건을 사용해서 데이터를 조회할 수 있는 메소드를 제공하며, list() 메소드를 통해서 쿼리 실행 결과를 java.util.List로 생성해준다.
createQuery() 메소드가 전달받는 HQL의 가장 기본적인 형태는 다음과 같다.
위 쿼리는 '클래스이름'과 관련된 테이블로부터 모든 행 데이터를 읽어온다. 클래스이름은 매핑 설정 파일에 명시한 클래스의 이름으로서 패키지를 포함한 완전한 클래스 이름을 사용해도 되고 패키지 부분을 제외한 클래스 이름만 사용해도 된다. 예를 들면 아래의 두 경우 모두 올바르게 동작한다.
where 절이나 기타 다른 절에서 관련 객체의 프로퍼티값을 참조하기 위해 다음과 같이 별칭을 부여할 수 있다.
'as' 뒤에 별칭을 입력하는데 'as'는 생략할 수 있다.
where 절과 파라미터 값 매핑
where 절은 SQL과 동일한 방법으로 입력한다. 차이점이 있다면, 테이블 컬럼명 대신에 퍼시스턴트 객체의 프로퍼티명을 사용한다는 점이다. 예를 들어, 특정 Member 객체 중에서 name 프로퍼티의 값이 '최범'으로 시작하는 것의 목록을 추출하고 싶다면 다음과 같이 쿼리를 작성하면 된다.
물론, and, or, not 등을 사용하여 검색 조건을 조합할 수 있다. 예를 들면 아래와 같이 사용할 수 있다.
JDBC의 PreparedStatement와 마찬가지로 HQL도 파라미터를 지정하는 형태로 비교값을 명시할 수 있다. PreparedStatement가 '?'를 사용하여 값을 지정하는 방식만 제공하는 반면에 HQL은 이름을 사용하는 방식과 '?'를 사용하는 방식의 두가지를 제공하고 있다. 먼저 다음은 이름을 사용하는 방식은 다음과 같이 값을 삽입할 부분에 :이름 의 형태를 삽입한다.
Query.setParameter() 메소드를 사용해서 이름 부분에 삽입될 값을 지정한다. 이름을 사용하기 때문에 값의 의미가 명확한 장점이 있다.
'?'를 사용하는 방식은 PreparedStatement를 사용하는 경우와 동일한 방식으로 쿼리를 작성하면 된다. 위 코드를 '?'를 사용하여 작성하면 아래와 같이 바뀐다.
위 코드에서 주의할 점은 JDBC의 PreparedStatement는 '?'의 인덱스가 1부터 시작하는 반면에, HQL의 '?'는 0부터 시작한다는 것이다.
setParameter() 메소드를 사용할 때 할당될 값의 타입에 대해 명확하게 정의하고 싶다면 타입을 세번째 인자로 받는 setParameter() 메소드를 사용하면 된다. 또는 setString(), setDouble(), setEntity()와 같은 메소드를 사용해서 파라미터 값을 지정해주어도 된다.
대부분의 경우는 하이버네이트가 자동으로 값에 대한 매핑을 처리해주기 때문에 setParameter() 메소드만으로도 충분한다.
비교 연산자와 수식
HQL은 SQL과 같은 기본 연산자를 제공하며, 이들은 다음과 같다.
between이나 in, like 등은 not 연산자와 함께 사용될 수 있다. 예를 들어, 특정 목록에 속해 있지 않은 값만 읽어오고 싶다면 다음과 같이 not in 연산자를 사용할 수 있을 것이다.
HQL은 또한 사칙 연산을 수행하는 +, -, *, / 연산자를 제공한다. 아래 코드는 비교 연산자와 수식 연산자를 함께 사용한 예이다.
select 절을 이용한 개별 프로퍼티 읽기
HQL은 SQL과 달리 select 절이 필요없으며 from 절만 입력해도 쿼리가 실행된다. 이렇게 from 절만 필수인 이유는 HQL이 조회한 결과가 퍼시스턴트 객체로 추출되기 때문이다. 하지만, 경우에 따라 특정 프로퍼티만을 추출하고 싶은 경우가 있으며, 이럴 때에는 다음과 같이 select 절을 사용해야 한다.
Query를 실해한 결과 List는 select 절에 표시된 값들을 저장한 객체 배열(Object[])을 갖고 있다. 이 객체 배열은 하나의 행과 관련된 데이터를 저장하고 있다. 배열에 저장되는 순서는 select 절에 나열한 순서와 동일하다. 즉, 위의 코드의 경우 배열의 0번 인덱스에는 member.id가, 1번 인덱스에는 member.name이 저장된다.
함수 사용
select 절이나 where 절에서는 SQL Dialect에 따라 관련 DBMS가 제공하는 함수를 사용할 수 있다. ('Hibernate를 이용한 ORM 2 - 세션(커넥션) 및 트랜잭션 프로퍼티 설정'에서 SQL Dialect를 설정하는 방법을 살펴봤었다.) 예를 들어, 오라클을 사용하는 경우 다음과 같이 오라클이 제공하는 to_date() 함수와 substr() 함수를 사용할 수 있다.
DBMS에 특정한 함수 뿐만 아니라 집합 관련 표준 SQL 함수인 count, avg, sum, max, min 함수도 사용할 수 있다.
Hibernate가 제공하는 HQL 함수/프로퍼티
Hibernate는 HQL에서 콜렉션 프로퍼티와 관련된 쿼리를 쉽게 할 수 있도록 하기 위한 특별한 함수와 프로퍼티를 제공하는데 이들 함수는 다음과 같다.
위 함수들은 다음과 같이 where 절에서 사용될 수 있다.
any, all, in 함수를 조건에서 사용할 수도 있다. 예를 들어, Item과 관련된 모든 Bid의 amount 값이 100보다 큰 Item을 구하고 싶다면 다음과 같은 쿼리를 사용하면 된다.
group by 절과 having 절
group by 절을 사용해서 특정 컬럼 값으로 그룹핑 할 수 있으며, having 절을 사용해서 그룹핑에 대해 조건을 줄 수도 있다. group by 절이나 having 절의 사용법은 SQL에서의 사용방법과 비슷하다. 예를 들어, 관련 Bid의 개수가 4개 이상인 Item의 개수를 구하고 싶은 경우 다음과 같은 쿼리를 사용하면 된다.
결과 순서 처리 및 페이징
HQL에서 조회한 결과를 정렬하고 싶은 경우에는 order by 절을 사용하면 된다. order by 절의 사용법은 SQL과 비슷하다. 예를 들어, Member 객체의 name 프로퍼티 값의 내림차순으로 정렬하고 싶다면 다음과 같은 HQL을 사용하면 된다.
SQL과 마찬가지로 여러 프로퍼티에 대해서 정렬 방식을 명시할 수 있다.
Query 인터페이스는 페이징 처리를 위한 메소드를 제공하고 있는데, 이 두 메소드는 아래와 같이 사용한다.
Query.setFirstResult() 메소드는 Query의 결과 목록 중에서 읽어올 첫번째 항목의 인덱스값을 입력한다. 이때 인덱스 값은 0부터 시작한다. 따라서, 위 코드는 31번째 항목부터 읽어오게 된다. setMaxResults() 메소드는 읽어올 최대 개수를 나타낸다. 따라서, 위 코드는 Query 결과의 31번째 항목부터 최대 15개를 읽어오게 된다. 이 두 메소드를 사용하면 페이징 처리를 쉽게 할 수 있을 것이다.
연관의 조인(join) 처리
Hibernate의 연관은 결국 SQL 조인을 통해서 처리된다. HQL에서 연관을 위한 조인은 다음과 같이 네 가지 방식으로 지원된다.
HQL은 SQL이 제공하는 내부 조인과 외부 조인을 모두 사용할 수 있다. 예를 들어, 일반적인 내부 조인은 다음과 같이 사용할 수 있다.
위 코드에서 눈여겨 볼 부분은 Query.list()가 리턴한 List가 담고 있는 값이 객체 배열(Object[])이라는 것이다. 내부 조인을 했기 때문에 객체 배열의 첫번째 요소에는 Item 객체가 저장되고, 배열의 두번째 요소에는 내부 조인으로 읽어온 Bid 객체가 저장된다.
다음과 같이 외부 조인을 사용할 수도 있다.
외부 조인을 할 때에도 내부 조인을 할 때와 마찬가지로 from 절에 있는 조인된 객체들을 객체 배열에 담아 리턴한다.
내부 조인이든 외부 조인이든, 실행한 결과는 (왼쪽이나 오른쪽의) 조인된 객체에 대해서 중복된 값을 갖게 된다. 예를 들어, 다음의 쿼리를 생각해보자.
id가 1인 Item과 연관된 Bid 객체의 id 값이 1, 2, 3 이라고 해 보자. 이때 Query.list() 메소드는 다음과 같은 객체 배열을 담은 List를 리턴하게 된다.
즉, (내부 또는 외부) 조인을 하면 하나의 Item 객체만 리턴하는 것이 아니라 같은 값을 갖는 Item 객체를 중복해서 리턴하게 되는 것이다. 조인을 사용할 때에는 이 점에 유의해야 한다.
조인되는 객체에 alias를 부여할 수도 있다. 예를 들어, Item과 조인되는 Bid 중에서 amount 프로퍼티의 값이 100 이상인 것만 조인하고 싶다면 다음과 같은 쿼리를 사용할 수 있다.
위와 같이 조인되는 객체에 alias를 부여해서 where 절의 쿼리를 좀더 쉽게 작성할 수 있게 된다.
from 절에서 fetch를 사용한 쿼리 조회
앞서 살펴봤던 'from Item item inner join item.bids where item.id = 1' HQL 쿼리는 다음과 같이 두개의 쿼리를 사용한다.
위 SQL에서 첫번째 쿼리는 Item 객체를 읽어오기 위한 쿼리이며, 두번째 쿼리는 Item 객체를 읽어온 뒤 관련 Bid 객체를 읽어오기 위한 쿼리이다.
fetch 키워드를 사용하면 위와 같이 두 번의 쿼리를 수행하지 않고 한번의 쿼리만으로 조인되는 객체를 읽어올 수 있다. 예를 들어, fetch 키워드를 사용해서 다음과 같은 HQL 쿼리를 실행할 수가 있다.
fetch 키워드를 join 뒤에 붙이면 다음과 같이 한번의 SQL 쿼리로 Item과 연관된 Bid 객체를 읽어오게 된다.
따라서 한번의 SQL 쿼리로 읽어오고 싶다면 fetch 키워드를 사용해서 쿼리 성능을 높일 수 있다.
fetch 모드를 사용할 때 주의할 점은 Query.list()가 리턴한 List가 배열 객체가 아닌 from 절의 퍼시스턴트 객체를 포함한다는 점이다. 앞서 코드를 보면 List에서 객체를 읽어올 때 객체 배열(Object[])이 아닌 Item 으로 형변환하는 것을 알 수 있다.
자동으로 조인 처리하기
Hibernate는 HQL의 where 절이나 order by, group by 등에 연관된 객체의 프로퍼티가 사용되면 자동으로 조인을 수행한다. 예를 들어 다음의 HQL을 보자.
위 HQL 쿼리는 다음과 같은 카타시안 조인을 사용해서 처리된다.
Hibernate는 where 절에서 사용된 값을 추출하기 위해 조인이 필요하다고 판단될 경우 위와 같이 조인을 수행하게 된다.
하지만, 자동으로 조인을 처리할 때 주의할 점은 HQL의 from 절에 명시된 객체만 결과 목록에 포함된다는 것이다. 실제로 사용되는 SQL에는 Item 객체와 매핑되는 ITEM 클래스가 from 절에 포함되지만, HQL에는 from 절에 Bid 객체만 포함되어 있기 때문에 Query.list() 메소드가 리턴하는 List에는 Bid만 포함된다.
where 절과 함께 카타시안 조인 사용하기
카타시안 조인을 직접 수행할 수도 있다. 예를 들어, 다음의 HQL 쿼리를 보자.
위 쿼리를 실행하면 [Member, Item] 배열을 저장한 List가 리턴된다.
조회환 결과를 임의의 클래스 객체에 저장하기
매핑 파일에서 작성한 클래스가 아닌 임의의 클래스에 조회 결과를 저장하고 싶다면 다음과 같이 HQL을 작성하면 된다.
위 코드에서 MemberRow 클래스는 인자를 세개 받는 생성자가 존재해야 한다.
위와 같이 임의의 클래스를 명시했다면 데이터를 조회할 때에도 지정한 클래스를 사용해야 한다.
Criteria API 사용한 데이터 조회
HQL 뿐만 아니라 Criteria API를 사용해서 데이터를 조회할 수 있다. HQL이 SQL에 가까운 쿼리언어를 사용해서 데이터 조회와 관련된 조건을 입력했면, Criteria API는 Criteria가 제공하는 메소드를 사용해서 데이터 조회와 관련된 조건을 입력한다. 즉, Criteria API는 좀더 객체 지향적인 형태로 검색 조건을 명시할 수 있도록 해 준다.
Criteria 기본 사용 형태
Criteria API는 net.sf.hibernate.Criteria 인터페이스를 사용해서 검색 조건을 생성하는데, 다음과 같이 Session.createcriteria() 메소드를 사용해서 기본 검색 조건을 갖는 Criteria 인스턴스를 생성할 수 있다.
위 코드에서 눈여겨 볼 부분은 createCriteria() 메소드의 인자로 매핑 설정 파일에서 명시한 자바 클래스가 온다는 것이다. 위 코드는 다음과 같은 HQL을 이용한 코드와 동일한 결과를 리턴하게 된다.
조건 넣기
HQL과 마찬가지로 검색 조건을 지정하지 않으면 전체 행을 읽어오는데, 대부분의 경우는 검색 조건을 넣기를 원할 것이다. 검색 조건을 추가할 때에는 net.sf.hibernate.expression.Expression 클래스와 net.sf.hibernate.Criterion 인터페이스를 사용한다.
Criteria.add(Criterion crit) 메소드는 Criterion이 저장하고 있는 내용을 검색 조건에 추가하는데, Expression 클래스가 제공하는 메소드를 사용해서 손쉽게 Criterion 인스턴스를 생성할 수 있다. 예를 들어, id가 10 인 Item을 검색하고 싶다면 다음과 같이 Expression.eq() 메소드를 사용해서 Criterion을 추가할 수 있다.
Expression 클래스는 다양한 조건을 처리할 수 있는 메소드를 제공하고 있으며, 이들 메소드 중에서 가장 기본적인 메소드는 다음과 같다.
여러 개의 검색 조건을 추가할 때에는 다음과 같이 두 가지 방법중의 한가지를 사용하면 된다.
StringBuffer.append() 메소드와 마찬가지로 Criteria.add() 메소드는 Criteria 자기 자신을 리턴하기 때문에 위 코드의 두번째 방법과 같이 코드를 생성할 수 있다. (SimpleExpressoin 타입은 Criteria 인터페이스를 구현하고 있으므로, SimpleExpressoin 타입 역시 연속해서 add() 메소드를 사용할 수 있다.)
like() 메소드와 ilike() 메소드를 보면 net.sf.hibernate.expression.MatchMode 클래스를 사용하는 것을 알 수 있는데, MatchMode 클래스는 like 검색을 어떻게 할지를 결정한다. MatchMode는 다음과 같이 네 개의 상수값을 제공하고 있으며, 이들 네 가지 상수값을 통해서 like 검색의 비교 방법을 결정하게 된다.
위 메소드들의 사용예를 들기 위해 다음과 같은 검색 조건이 있다고 해 보자.
위와 같은 조건은 아래의 코드를 사용해서 생성할 수 있다.
결과 순서 처리 및 페이징
Criteria의 addOrder() 메소드를 사용하면 결과의 조회순서를 명시할 수 있다. 예를 들어, name 프로퍼티의 오름차순으로, age의 내림차순으로 정렬하고 싶다면 다음과 같은 코드를 사용하면 된다.
net.sf.hibernate.expression.Order 클래스의 asc() 메소드는 오름차순을, desc() 메소드는 내림차순을 의미한다.
페이징 처리를 위해 사용되는 메소드는 Query 인터페이스와 마찬가지로 setFirstResult() 메소드와 setMaxResults() 메소드를 사용하면 된다.
Criteria의 연관 처리
Criteria.setFetchMode() 메소드를 이용한 연관 객체 조회 모드 지정
연관된 객체를 한번의 쿼리로 읽어오고 싶은 경우에는 setFetchMode() 메소드를 사용할 수 있다. Criteria.setFetchMode() 메소드는 다음과 같이 연관된 프로퍼티를 어떻게 읽어올지를 지정할 수 있도록 해 준다.
net.sf.hibernate.FetchMode 클래스는 setFetcMode() 메소드에 지정할 int 상수값을 정의하고 있으며, 각 상수값은 아래와 같다.
Criteria.createCriteria() 메소드를 사용해서 연관된 객체에 대한 조인 처리를 수행할 수 있다. 다음의 코드를 살펴보자.
alias를 사용해서 조인을 처리할 수도 있는데, alias는 Criteria.createAlias() 메소드를 사용하여 지정한다. 다음은 위 코드를 alias를 사용해서 다시 작성해본 것이다.
Criteria의 데이터 조회 방식
앞서 HQL에서는 조인해서 객체를 가져오는 경우 배열에 저장해서 리턴한 것을 기억할 것이다. 아래 코드는 앞서 살펴봤었던 HQL을 이용한 조인 처리 코드이다.
하지만, Criteria는 조인 처리를 하더라도 리턴되는 값을 최초에 Session.createCriteria() 메소드를 사용해서 명시한 객체이다. 예를 들어, 위와 같은 조인을 처리하는 Criteria 코드는 아래와 같다.
만약 연관된 객체를 동시에 읽고 싶다면, 다음과 같이 Criteria.returnMaps() 메소드를 호출해서 한 행과 관련된 Item과 Bid 객체를 Map에 저장할 수 있다.
관련링크:
HQL을 사용한 데이터 조회
Hibernate는 HQL 실행 방식, Criteria 실행 방식 그리고 SQL 직접 실행 방식의 세 가지 데이터 조회 방식을 제공한다. 이들 세 가지 데이터 조회 방식 중 HQL을 사용하는 방법에 대해서 살펴보도록 하자.
HQL은 SQL과 비슷한 형태의 구문을 갖는 Hibernate의 쿼리 언어로서 OR 매핑 파일에서 설정한 객체를 쿼리문구에서 사용할 수 있다는 특징이 있다. 또한, SQL 만큼 쉽기 때문에 다양한 형태로 데이터를 조회하고자 할 때 유용하게 사용될 수 있다.
HQL의 기본
HQL을 이용하는 가장 기본적인 코드는 다음과 같다.
Query query = session.createQuery("from Item");
List list = query.list();
// 또는 간단히 한줄로
List list = session.createQuery("from Item").list();
for (int i = 0 ; i < list.size() ; i++) {
Item item = (Item)list.get(i);
}
List list = query.list();
// 또는 간단히 한줄로
List list = session.createQuery("from Item").list();
for (int i = 0 ; i < list.size() ; i++) {
Item item = (Item)list.get(i);
}
Hibernate Session은 createQuery(String hql) 메소드는 전달받은 HQL을 실제 쿼리로 변환하여 수행하는 net.sf.hibernate.Query 객체를 리턴한다. Query는 쿼리 실행과 관련된 인터페이스로서 다양한 조건을 사용해서 데이터를 조회할 수 있는 메소드를 제공하며, list() 메소드를 통해서 쿼리 실행 결과를 java.util.List로 생성해준다.
createQuery() 메소드가 전달받는 HQL의 가장 기본적인 형태는 다음과 같다.
from 클래스이름
위 쿼리는 '클래스이름'과 관련된 테이블로부터 모든 행 데이터를 읽어온다. 클래스이름은 매핑 설정 파일에 명시한 클래스의 이름으로서 패키지를 포함한 완전한 클래스 이름을 사용해도 되고 패키지 부분을 제외한 클래스 이름만 사용해도 된다. 예를 들면 아래의 두 경우 모두 올바르게 동작한다.
from javacan.hibernate.test.Item
from Item
from Item
where 절이나 기타 다른 절에서 관련 객체의 프로퍼티값을 참조하기 위해 다음과 같이 별칭을 부여할 수 있다.
from Member as member
from Member member
from Member member
'as' 뒤에 별칭을 입력하는데 'as'는 생략할 수 있다.
where 절과 파라미터 값 매핑
where 절은 SQL과 동일한 방법으로 입력한다. 차이점이 있다면, 테이블 컬럼명 대신에 퍼시스턴트 객체의 프로퍼티명을 사용한다는 점이다. 예를 들어, 특정 Member 객체 중에서 name 프로퍼티의 값이 '최범'으로 시작하는 것의 목록을 추출하고 싶다면 다음과 같이 쿼리를 작성하면 된다.
from Member as member where member.name like '최범%'
물론, and, or, not 등을 사용하여 검색 조건을 조합할 수 있다. 예를 들면 아래와 같이 사용할 수 있다.
from Member as member where member.id like '%mad%' or member.name like '최%'
JDBC의 PreparedStatement와 마찬가지로 HQL도 파라미터를 지정하는 형태로 비교값을 명시할 수 있다. PreparedStatement가 '?'를 사용하여 값을 지정하는 방식만 제공하는 반면에 HQL은 이름을 사용하는 방식과 '?'를 사용하는 방식의 두가지를 제공하고 있다. 먼저 다음은 이름을 사용하는 방식은 다음과 같이 값을 삽입할 부분에 :이름 의 형태를 삽입한다.
Apartment apt = (Apartment)session.get(Apartment.class, "01135001");
Query query = session.
createQuery("from Member as member "+
"where member.id = :id or member.apartment = :apt");
query.setParameter("id", "era13");
query.setParameter("apt", apt);
Query query = session.
createQuery("from Member as member "+
"where member.id = :id or member.apartment = :apt");
query.setParameter("id", "era13");
query.setParameter("apt", apt);
Query.setParameter() 메소드를 사용해서 이름 부분에 삽입될 값을 지정한다. 이름을 사용하기 때문에 값의 의미가 명확한 장점이 있다.
'?'를 사용하는 방식은 PreparedStatement를 사용하는 경우와 동일한 방식으로 쿼리를 작성하면 된다. 위 코드를 '?'를 사용하여 작성하면 아래와 같이 바뀐다.
Apartment apt = (Apartment)session.get(Apartment.class, "01135001");
Query query = session.
createQuery("from Member as member where member.id = ? or member.apartment = ?");
query.setParameter(0, "era13");
query.setParameter(1, apt);
Query query = session.
createQuery("from Member as member where member.id = ? or member.apartment = ?");
query.setParameter(0, "era13");
query.setParameter(1, apt);
위 코드에서 주의할 점은 JDBC의 PreparedStatement는 '?'의 인덱스가 1부터 시작하는 반면에, HQL의 '?'는 0부터 시작한다는 것이다.
setParameter() 메소드를 사용할 때 할당될 값의 타입에 대해 명확하게 정의하고 싶다면 타입을 세번째 인자로 받는 setParameter() 메소드를 사용하면 된다. 또는 setString(), setDouble(), setEntity()와 같은 메소드를 사용해서 파라미터 값을 지정해주어도 된다.
query.setParameter("id", id, Hibernate.STRING);
query.setInteger("no", 10);
query.setEntity("apt", apartment);
query.setInteger("no", 10);
query.setEntity("apt", apartment);
대부분의 경우는 하이버네이트가 자동으로 값에 대한 매핑을 처리해주기 때문에 setParameter() 메소드만으로도 충분한다.
비교 연산자와 수식
HQL은 SQL과 같은 기본 연산자를 제공하며, 이들은 다음과 같다.
= <> < > >= <= between in like
between이나 in, like 등은 not 연산자와 함께 사용될 수 있다. 예를 들어, 특정 목록에 속해 있지 않은 값만 읽어오고 싶다면 다음과 같이 not in 연산자를 사용할 수 있을 것이다.
from Member as mem where mem.apartment not in (?, ?, ?);
HQL은 또한 사칙 연산을 수행하는 +, -, *, / 연산자를 제공한다. 아래 코드는 비교 연산자와 수식 연산자를 함께 사용한 예이다.
from Member mem where (mem.height - 110) * 0.1 < mem.weight - (mem.height - 110)
select 절을 이용한 개별 프로퍼티 읽기
HQL은 SQL과 달리 select 절이 필요없으며 from 절만 입력해도 쿼리가 실행된다. 이렇게 from 절만 필수인 이유는 HQL이 조회한 결과가 퍼시스턴트 객체로 추출되기 때문이다. 하지만, 경우에 따라 특정 프로퍼티만을 추출하고 싶은 경우가 있으며, 이럴 때에는 다음과 같이 select 절을 사용해야 한다.
Query query = session.
createQuery("select member.id, member.name from Member as member "+
"where member.id = ? or member.apartment = ?");
query.setParameter(0, "era13");
query.setParameter(1, apt);
List list = query.list();
for (int i = 0 ; i < list.size() ; i++) {
Object[] row = (Object[])list.get(i);
// row[0] - member.id
// row[1] - member.name
}
createQuery("select member.id, member.name from Member as member "+
"where member.id = ? or member.apartment = ?");
query.setParameter(0, "era13");
query.setParameter(1, apt);
List list = query.list();
for (int i = 0 ; i < list.size() ; i++) {
Object[] row = (Object[])list.get(i);
// row[0] - member.id
// row[1] - member.name
}
Query를 실해한 결과 List는 select 절에 표시된 값들을 저장한 객체 배열(Object[])을 갖고 있다. 이 객체 배열은 하나의 행과 관련된 데이터를 저장하고 있다. 배열에 저장되는 순서는 select 절에 나열한 순서와 동일하다. 즉, 위의 코드의 경우 배열의 0번 인덱스에는 member.id가, 1번 인덱스에는 member.name이 저장된다.
함수 사용
select 절이나 where 절에서는 SQL Dialect에 따라 관련 DBMS가 제공하는 함수를 사용할 수 있다. ('Hibernate를 이용한 ORM 2 - 세션(커넥션) 및 트랜잭션 프로퍼티 설정'에서 SQL Dialect를 설정하는 방법을 살펴봤었다.) 예를 들어, 오라클을 사용하는 경우 다음과 같이 오라클이 제공하는 to_date() 함수와 substr() 함수를 사용할 수 있다.
select to_date(member.regdate, 'YYYY/MM/DD'), substr(member.name, 1, 2)
from Member as member where member.id = ? or member.apartment = ?
from Member as member where member.id = ? or member.apartment = ?
DBMS에 특정한 함수 뿐만 아니라 집합 관련 표준 SQL 함수인 count, avg, sum, max, min 함수도 사용할 수 있다.
select max(member.mark), min(member.mark), avg(mark) from Member member
Hibernate가 제공하는 HQL 함수/프로퍼티
Hibernate는 HQL에서 콜렉션 프로퍼티와 관련된 쿼리를 쉽게 할 수 있도록 하기 위한 특별한 함수와 프로퍼티를 제공하는데 이들 함수는 다음과 같다.
Hibernate가 제공하는 콜렉션 관련 함수/프로퍼티 | |||
함수 | 관련 프로퍼티 | 설명 | |
size() | size | 콜렉션의 크기를 구한다. | |
minIndex() | minIndex | 인덱스의 최소값을 구한다. | |
maxIndex() | maxIndex | 인덱스의 최대값을 구한다. | |
minElement() | minElement | 콜렉션의 요소 중 최소값을 구한다. | |
maxElement() | maxElement | 콜렉션의 요수 중 최대값을 구한다. |
위 함수들은 다음과 같이 where 절에서 사용될 수 있다.
from Item item where maxElement(item.bids) > 1000
from Item item where item.bids.maxIndex > 5
from Item item where item.bids.maxIndex > 5
any, all, in 함수를 조건에서 사용할 수도 있다. 예를 들어, Item과 관련된 모든 Bid의 amount 값이 100보다 큰 Item을 구하고 싶다면 다음과 같은 쿼리를 사용하면 된다.
from Item item where 100 < all (select b.amount from item.bids b)
group by 절과 having 절
group by 절을 사용해서 특정 컬럼 값으로 그룹핑 할 수 있으며, having 절을 사용해서 그룹핑에 대해 조건을 줄 수도 있다. group by 절이나 having 절의 사용법은 SQL에서의 사용방법과 비슷하다. 예를 들어, 관련 Bid의 개수가 4개 이상인 Item의 개수를 구하고 싶은 경우 다음과 같은 쿼리를 사용하면 된다.
from Item item group by item.id having size(item.bids) > 4
결과 순서 처리 및 페이징
HQL에서 조회한 결과를 정렬하고 싶은 경우에는 order by 절을 사용하면 된다. order by 절의 사용법은 SQL과 비슷하다. 예를 들어, Member 객체의 name 프로퍼티 값의 내림차순으로 정렬하고 싶다면 다음과 같은 HQL을 사용하면 된다.
from Member member order by member.name desc
SQL과 마찬가지로 여러 프로퍼티에 대해서 정렬 방식을 명시할 수 있다.
from Member member order by member.name desc, member.apartment.name asc
Query 인터페이스는 페이징 처리를 위한 메소드를 제공하고 있는데, 이 두 메소드는 아래와 같이 사용한다.
Query query = session.createQuery("from Member member order by member.name asc");
query.setFirstResult(30);
query.setMaxResults(15);
query.setFirstResult(30);
query.setMaxResults(15);
Query.setFirstResult() 메소드는 Query의 결과 목록 중에서 읽어올 첫번째 항목의 인덱스값을 입력한다. 이때 인덱스 값은 0부터 시작한다. 따라서, 위 코드는 31번째 항목부터 읽어오게 된다. setMaxResults() 메소드는 읽어올 최대 개수를 나타낸다. 따라서, 위 코드는 Query 결과의 31번째 항목부터 최대 15개를 읽어오게 된다. 이 두 메소드를 사용하면 페이징 처리를 쉽게 할 수 있을 것이다.
연관의 조인(join) 처리
Hibernate의 연관은 결국 SQL 조인을 통해서 처리된다. HQL에서 연관을 위한 조인은 다음과 같이 네 가지 방식으로 지원된다.
- from 절에서 join과 fetch 사용하기
- 자동으로 조인 처리하기
- where 절과 함께 카타시안 조인 사용하기
HQL은 SQL이 제공하는 내부 조인과 외부 조인을 모두 사용할 수 있다. 예를 들어, 일반적인 내부 조인은 다음과 같이 사용할 수 있다.
Query query = session.
createQuery("from Item item inner join item.bids");
List list = query.list();
for (int i = 0 ; i < list.size() ; i++) {
Object[] object = (Object[])list.get(i);
Item item = (Item)object[0];
Bid bid = (Bid)object[1];
System.out.print(item.getId());
System.out.print("-");
System.out.println(bid.getId());
}
createQuery("from Item item inner join item.bids");
List list = query.list();
for (int i = 0 ; i < list.size() ; i++) {
Object[] object = (Object[])list.get(i);
Item item = (Item)object[0];
Bid bid = (Bid)object[1];
System.out.print(item.getId());
System.out.print("-");
System.out.println(bid.getId());
}
위 코드에서 눈여겨 볼 부분은 Query.list()가 리턴한 List가 담고 있는 값이 객체 배열(Object[])이라는 것이다. 내부 조인을 했기 때문에 객체 배열의 첫번째 요소에는 Item 객체가 저장되고, 배열의 두번째 요소에는 내부 조인으로 읽어온 Bid 객체가 저장된다.
다음과 같이 외부 조인을 사용할 수도 있다.
from Item item left outer join item.bids
외부 조인을 할 때에도 내부 조인을 할 때와 마찬가지로 from 절에 있는 조인된 객체들을 객체 배열에 담아 리턴한다.
내부 조인이든 외부 조인이든, 실행한 결과는 (왼쪽이나 오른쪽의) 조인된 객체에 대해서 중복된 값을 갖게 된다. 예를 들어, 다음의 쿼리를 생각해보자.
from Item item inner join item.bids where item.id = 1
id가 1인 Item과 연관된 Bid 객체의 id 값이 1, 2, 3 이라고 해 보자. 이때 Query.list() 메소드는 다음과 같은 객체 배열을 담은 List를 리턴하게 된다.
Item | Bid |
Item (id = 1) | Bid (id = 1) |
Item (id = 1) | Bid (id = 2) |
Item (id = 1) | Bid (id = 3) |
즉, (내부 또는 외부) 조인을 하면 하나의 Item 객체만 리턴하는 것이 아니라 같은 값을 갖는 Item 객체를 중복해서 리턴하게 되는 것이다. 조인을 사용할 때에는 이 점에 유의해야 한다.
조인되는 객체에 alias를 부여할 수도 있다. 예를 들어, Item과 조인되는 Bid 중에서 amount 프로퍼티의 값이 100 이상인 것만 조인하고 싶다면 다음과 같은 쿼리를 사용할 수 있다.
from Item item join item.bids bid where bid.amount >= 100
위와 같이 조인되는 객체에 alias를 부여해서 where 절의 쿼리를 좀더 쉽게 작성할 수 있게 된다.
from 절에서 fetch를 사용한 쿼리 조회
앞서 살펴봤던 'from Item item inner join item.bids where item.id = 1' HQL 쿼리는 다음과 같이 두개의 쿼리를 사용한다.
select item.ITEM_ID, ..., bid.BID_ID
from ITEM item inner join BID bid on item.ITEM_ID = bid.ITEM_ID
where item.ITEM_ID = 1
select bid.BID_ID, ... from BID bid where bid.ITEM_ID = 1
from ITEM item inner join BID bid on item.ITEM_ID = bid.ITEM_ID
where item.ITEM_ID = 1
select bid.BID_ID, ... from BID bid where bid.ITEM_ID = 1
위 SQL에서 첫번째 쿼리는 Item 객체를 읽어오기 위한 쿼리이며, 두번째 쿼리는 Item 객체를 읽어온 뒤 관련 Bid 객체를 읽어오기 위한 쿼리이다.
fetch 키워드를 사용하면 위와 같이 두 번의 쿼리를 수행하지 않고 한번의 쿼리만으로 조인되는 객체를 읽어올 수 있다. 예를 들어, fetch 키워드를 사용해서 다음과 같은 HQL 쿼리를 실행할 수가 있다.
Query query = session.
createQuery("from Item item left join fetch item.bids "+
"where item.id = 1");
List list = query.list();
for (int i = 0 ; i < list.size() ; i++) {
Item item = (Item)list.get(i);
System.out.println(item.getId());
}
createQuery("from Item item left join fetch item.bids "+
"where item.id = 1");
List list = query.list();
for (int i = 0 ; i < list.size() ; i++) {
Item item = (Item)list.get(i);
System.out.println(item.getId());
}
fetch 키워드를 join 뒤에 붙이면 다음과 같이 한번의 SQL 쿼리로 Item과 연관된 Bid 객체를 읽어오게 된다.
select item.ITEM_ID, ..., bid.BID_ID ...
from ITEM item left outer join BID bid on item.ITEM_ID = bid.ITEM_ID
where item.ITEM_ID = 1
from ITEM item left outer join BID bid on item.ITEM_ID = bid.ITEM_ID
where item.ITEM_ID = 1
따라서 한번의 SQL 쿼리로 읽어오고 싶다면 fetch 키워드를 사용해서 쿼리 성능을 높일 수 있다.
fetch 모드를 사용할 때 주의할 점은 Query.list()가 리턴한 List가 배열 객체가 아닌 from 절의 퍼시스턴트 객체를 포함한다는 점이다. 앞서 코드를 보면 List에서 객체를 읽어올 때 객체 배열(Object[])이 아닌 Item 으로 형변환하는 것을 알 수 있다.
자동으로 조인 처리하기
Hibernate는 HQL의 where 절이나 order by, group by 등에 연관된 객체의 프로퍼티가 사용되면 자동으로 조인을 수행한다. 예를 들어 다음의 HQL을 보자.
from Bid bid where bid.item.minPrice >= 1000
위 HQL 쿼리는 다음과 같은 카타시안 조인을 사용해서 처리된다.
select bid.BID_ID, .., bid.ITEM_ID
from BID bid, ITEM item
where item.MINPRICE>=1000 and bid.ITEM_ID = item.ITEM_ID
from BID bid, ITEM item
where item.MINPRICE>=1000 and bid.ITEM_ID = item.ITEM_ID
Hibernate는 where 절에서 사용된 값을 추출하기 위해 조인이 필요하다고 판단될 경우 위와 같이 조인을 수행하게 된다.
하지만, 자동으로 조인을 처리할 때 주의할 점은 HQL의 from 절에 명시된 객체만 결과 목록에 포함된다는 것이다. 실제로 사용되는 SQL에는 Item 객체와 매핑되는 ITEM 클래스가 from 절에 포함되지만, HQL에는 from 절에 Bid 객체만 포함되어 있기 때문에 Query.list() 메소드가 리턴하는 List에는 Bid만 포함된다.
where 절과 함께 카타시안 조인 사용하기
카타시안 조인을 직접 수행할 수도 있다. 예를 들어, 다음의 HQL 쿼리를 보자.
from Member member, Item item
where member.id = 'someman' and item.member = member
where member.id = 'someman' and item.member = member
위 쿼리를 실행하면 [Member, Item] 배열을 저장한 List가 리턴된다.
조회환 결과를 임의의 클래스 객체에 저장하기
매핑 파일에서 작성한 클래스가 아닌 임의의 클래스에 조회 결과를 저장하고 싶다면 다음과 같이 HQL을 작성하면 된다.
select new MemberRow(member.id, member.name, member.securityNo)
from Member member
where ...
from Member member
where ...
위 코드에서 MemberRow 클래스는 인자를 세개 받는 생성자가 존재해야 한다.
위와 같이 임의의 클래스를 명시했다면 데이터를 조회할 때에도 지정한 클래스를 사용해야 한다.
Query query = session.createQuery(
"select new MemberRow(member.id, member.name, member.securityNo) "+
"from Member member where ... ");
list = query.list();
for (int i = 0 ; i < list.size() ; i++) {
MemberRow memberRow = (MemberRow)list.get(i);
}
"select new MemberRow(member.id, member.name, member.securityNo) "+
"from Member member where ... ");
list = query.list();
for (int i = 0 ; i < list.size() ; i++) {
MemberRow memberRow = (MemberRow)list.get(i);
}
Criteria API 사용한 데이터 조회
HQL 뿐만 아니라 Criteria API를 사용해서 데이터를 조회할 수 있다. HQL이 SQL에 가까운 쿼리언어를 사용해서 데이터 조회와 관련된 조건을 입력했면, Criteria API는 Criteria가 제공하는 메소드를 사용해서 데이터 조회와 관련된 조건을 입력한다. 즉, Criteria API는 좀더 객체 지향적인 형태로 검색 조건을 명시할 수 있도록 해 준다.
Criteria 기본 사용 형태
Criteria API는 net.sf.hibernate.Criteria 인터페이스를 사용해서 검색 조건을 생성하는데, 다음과 같이 Session.createcriteria() 메소드를 사용해서 기본 검색 조건을 갖는 Criteria 인스턴스를 생성할 수 있다.
Criteria crit = session.createCriteria(Member.class);
List list = crit.list();
List list = crit.list();
위 코드에서 눈여겨 볼 부분은 createCriteria() 메소드의 인자로 매핑 설정 파일에서 명시한 자바 클래스가 온다는 것이다. 위 코드는 다음과 같은 HQL을 이용한 코드와 동일한 결과를 리턴하게 된다.
Query query = session.createQuery("from Member");
List list = query.list();
List list = query.list();
조건 넣기
HQL과 마찬가지로 검색 조건을 지정하지 않으면 전체 행을 읽어오는데, 대부분의 경우는 검색 조건을 넣기를 원할 것이다. 검색 조건을 추가할 때에는 net.sf.hibernate.expression.Expression 클래스와 net.sf.hibernate.Criterion 인터페이스를 사용한다.
Criteria.add(Criterion crit) 메소드는 Criterion이 저장하고 있는 내용을 검색 조건에 추가하는데, Expression 클래스가 제공하는 메소드를 사용해서 손쉽게 Criterion 인스턴스를 생성할 수 있다. 예를 들어, id가 10 인 Item을 검색하고 싶다면 다음과 같이 Expression.eq() 메소드를 사용해서 Criterion을 추가할 수 있다.
Criteria crit = session.createCriteria(Item.class);
crit.add( Expression.eq("id", new Integer(10) );
List list = crit.list();
crit.add( Expression.eq("id", new Integer(10) );
List list = crit.list();
Expression 클래스는 다양한 조건을 처리할 수 있는 메소드를 제공하고 있으며, 이들 메소드 중에서 가장 기본적인 메소드는 다음과 같다.
Expression의 기본 조건 관련 Criterion 생성 메소드 | |||
메소드 | 설명 | ||
SimpleExpression eq(String propertyName, Object value) | 프로퍼티의 값이 value와 같은지 검사하는 조건을 생성한다. | ||
Criterion eqProperty(String propertyName, String otherPropertyName) | 두 프로퍼티의 값이 같은지 검사하는 조건을 생성한다. | ||
Criterion between(String propertyName, Object lo, Object hi) | 프로퍼티가 두 값 사이에 존재하는 지 검사하는 조건을 생성한다. | ||
SimpleExpression ge(String propertyName, Object value) | 프로퍼티의 값이 value보다 크거나 같은 지 검사하는 조건을 생성한다. ( >= 연산) | ||
SimpleExpression gt(String propertyName, Object value) | 프로퍼티의 값이 value보다 큰지 검사하는 조건을 생성한다. | ||
SimpleExpression lt(String propertyName, Object value) | 프로퍼티의 값이 value보다 작은지 검사하는 조건을 생성한다. | ||
Criterion ltProperty(String propertyName, String otherPropertyName) | 프로퍼티 값이 다른 프로퍼티 값보다 작은 지 검사하는 조건을 생성한다. | ||
SimpleExpression le(String propertyName, Object value) | 프로퍼티 값이 value보다 작거나 같은 지 검사하는 조건을 생성한다. | ||
Criterion leProperty(String propertyName, String otherPropertyName) | 프로퍼티 값이 다른 프로퍼티 값보다 작거나 같은 지 검사하는 조건을 생성한다. | ||
Criterion in(String propertyName, Collection values) | 프로퍼티 값이 values 목록에 포함되는 지 검사하는 조건을 생성한다. | ||
Criterion in(String propertyName, Object[] values) | 프로퍼티 값이 values 목록에 포함되는 지 검사하는 조건을 생성한다. | ||
SimpleExpression like(String propertyName, Object value) | like 검색 조건을 생성한다. | ||
SimpleExpression like(String propertyName, String value, MatchMode matchMode) | like 검색 조건을 생성한다. | ||
Criterion ilike(String propertyName, Object value) | 대소문자를 구분하지 않는 (Postgres의 ilike와 같은) 검색 조건을 생성한다. | ||
Criterion ilike(String propertyName, String value, MatchMode matchMode) | 대소문자를 구분하지 않는 (Postgres의 ilike와 같은) 검색 조건을 생성한다. | ||
Criterion isNotNull(String propertyName) | 프로퍼티 값이 null이 아닌지 검사하는 조건을 생성한다. | ||
Criterion isNull(String propertyName) | 프로퍼티 값이 null인지 검사하는 조건을 생성한다. | ||
Criterion allEq(Map propertyNameValues) | map의 키를 프로퍼티 이름으로 값을 비교할 값으로 사용해서, map에 저장된 모든 프로퍼티의 값이 관련 키의 값과 같은 지 검사하는 조건을 생성한다. |
여러 개의 검색 조건을 추가할 때에는 다음과 같이 두 가지 방법중의 한가지를 사용하면 된다.
Criteria crit = session.createCriteria(Item.class);
crit.add( Expression.gt("minValue", new Integer(5000) );
crit.add( Expression.eq("user", someUser) );
// 또는 다음과 같이 연속된 방법을 조건 생성 가능
Criteria crit = session.createCriteria(Item.class)
.add( Expression.gt("minValue", new Integer(5000) )
.add( Expression.eq("user", someUser) );
crit.add( Expression.gt("minValue", new Integer(5000) );
crit.add( Expression.eq("user", someUser) );
// 또는 다음과 같이 연속된 방법을 조건 생성 가능
Criteria crit = session.createCriteria(Item.class)
.add( Expression.gt("minValue", new Integer(5000) )
.add( Expression.eq("user", someUser) );
StringBuffer.append() 메소드와 마찬가지로 Criteria.add() 메소드는 Criteria 자기 자신을 리턴하기 때문에 위 코드의 두번째 방법과 같이 코드를 생성할 수 있다. (SimpleExpressoin 타입은 Criteria 인터페이스를 구현하고 있으므로, SimpleExpressoin 타입 역시 연속해서 add() 메소드를 사용할 수 있다.)
like() 메소드와 ilike() 메소드를 보면 net.sf.hibernate.expression.MatchMode 클래스를 사용하는 것을 알 수 있는데, MatchMode 클래스는 like 검색을 어떻게 할지를 결정한다. MatchMode는 다음과 같이 네 개의 상수값을 제공하고 있으며, 이들 네 가지 상수값을 통해서 like 검색의 비교 방법을 결정하게 된다.
- MatchMode.ANYWHERE - like '%값%'의 형태로 검색한다.
- MatchMode.START - like '값%'의 형태로 검색한다.
- MatchMode.END - like '%값'의 형태로 검색한다.
- MatchMode.MATCH - like '값'의 형태로 검색한다.
Expression의 기본 조건 관련 Criterion 생성 메소드 | |||
메소드 | 설명 | ||
Criterion or(Criterion lhs, Criterion rhs) | 두 조건을 OR로 처리한다. | ||
Criterion and(Criterion lhs, Criterion rhs) | 두 조건을 AND로 처리한다. | ||
Conjunction conjunction() | conjunction() 메소드가 생성한 Conjunction의 add() 메소드로 추가한 조건을 AND로 연결한다. | ||
Disjunction disjunction() | disjunction() 메소드가 생성한 Disjunction의 add() 메소드로 추가한 조건을 OR로 연결한다. | ||
Criterion not(Criterion expression) | 관련 검색 조건(expression)의 not에 해당하는 조건을 생성한다. |
위 메소드들의 사용예를 들기 위해 다음과 같은 검색 조건이 있다고 해 보자.
where (age > 10 and sex = 'F') or (sex = 'M' or age <= 10 or bisexual = 'T')
위와 같은 조건은 아래의 코드를 사용해서 생성할 수 있다.
Criteria crit = session.createCriteria(Member.class);
crit.add(
Expression.or( /* 중간의 or */
Expression.and( /* 좌측의 (age > 10 and sex = 'F') */
Expression.gt("age", new Integer(10)), Expression.eq("sex", "F")
)
,
Expression.disjunction() /* 우측의 (sex = 'M' or age <= 10 or bisexual = 'T') */
.add(Expression.eq("sex", "M") )
.add(Expression.le("age", new Integer(10)) )
.add(Expression.eq("bisexual", "T"))
)
)
crit.add(
Expression.or( /* 중간의 or */
Expression.and( /* 좌측의 (age > 10 and sex = 'F') */
Expression.gt("age", new Integer(10)), Expression.eq("sex", "F")
)
,
Expression.disjunction() /* 우측의 (sex = 'M' or age <= 10 or bisexual = 'T') */
.add(Expression.eq("sex", "M") )
.add(Expression.le("age", new Integer(10)) )
.add(Expression.eq("bisexual", "T"))
)
)
결과 순서 처리 및 페이징
Criteria의 addOrder() 메소드를 사용하면 결과의 조회순서를 명시할 수 있다. 예를 들어, name 프로퍼티의 오름차순으로, age의 내림차순으로 정렬하고 싶다면 다음과 같은 코드를 사용하면 된다.
Criteria crit = session.createCriteria(Member.class);
crit.addOrder(Order.asc("name"));
crit.addOrder(Order.desc("age"));
crit.addOrder(Order.asc("name"));
crit.addOrder(Order.desc("age"));
net.sf.hibernate.expression.Order 클래스의 asc() 메소드는 오름차순을, desc() 메소드는 내림차순을 의미한다.
페이징 처리를 위해 사용되는 메소드는 Query 인터페이스와 마찬가지로 setFirstResult() 메소드와 setMaxResults() 메소드를 사용하면 된다.
Criteria crit = session.createCriteria(Member.class);
crit.addOrder( Order.asc("name") );
crit.setFirstResult(30);
crit.setMaxResults(15);
crit.addOrder( Order.asc("name") );
crit.setFirstResult(30);
crit.setMaxResults(15);
Criteria의 연관 처리
Criteria.setFetchMode() 메소드를 이용한 연관 객체 조회 모드 지정
연관된 객체를 한번의 쿼리로 읽어오고 싶은 경우에는 setFetchMode() 메소드를 사용할 수 있다. Criteria.setFetchMode() 메소드는 다음과 같이 연관된 프로퍼티를 어떻게 읽어올지를 지정할 수 있도록 해 준다.
Criteria crit = session.createCriteria(Item.class);
crit.setFetchMode("bids", FetchMode.EAGER)
.add( Expression.gt("minValue", new Integer(100) );
crit.setFetchMode("bids", FetchMode.EAGER)
.add( Expression.gt("minValue", new Integer(100) );
net.sf.hibernate.FetchMode 클래스는 setFetcMode() 메소드에 지정할 int 상수값을 정의하고 있으며, 각 상수값은 아래와 같다.
- FetchMode.DEFAULT - 매핑 설정 파일에 있는 값을 그대로 사용한다.
- FetchMode.EAGER - 외부 조인을 사용해서 한번의 쿼리로 연관된 객체를 읽어온다. (매핑에서 outer-join="true"로 지정한 것과 같다.)
- FetchMode.LAZY - lazy 모드로 읽어온다. (매핑에서 outer-join="false"로 지정한 것과 같다.)
Criteria.createCriteria() 메소드를 사용해서 연관된 객체에 대한 조인 처리를 수행할 수 있다. 다음의 코드를 살펴보자.
Criteria itemCrit = session.createCriteria(Item.class);
itemCrit.add( Expression.ge("minValue", new Integer(100)) );
Criteria bidCrit = itemCrit.createCriteria("bids"); // 연관된 객체에 대한 검색 조건 생성
bidCrit.add( Expression.gt("amount", new Integer(100)) );
List list = itemCrit.list();
itemCrit.add( Expression.ge("minValue", new Integer(100)) );
Criteria bidCrit = itemCrit.createCriteria("bids"); // 연관된 객체에 대한 검색 조건 생성
bidCrit.add( Expression.gt("amount", new Integer(100)) );
List list = itemCrit.list();
alias를 사용해서 조인을 처리할 수도 있는데, alias는 Criteria.createAlias() 메소드를 사용하여 지정한다. 다음은 위 코드를 alias를 사용해서 다시 작성해본 것이다.
Criteria itemCrit = session.createCriteria(Item.class);
itemCrit.createAlias("bids", "bid");
itemCrit.add( Expression.ge("minValue", new Integer(100)) );
itemCrit.add( Expression.gt("bid.amount", new Integer(100));
itemCrit.createAlias("bids", "bid");
itemCrit.add( Expression.ge("minValue", new Integer(100)) );
itemCrit.add( Expression.gt("bid.amount", new Integer(100));
Criteria의 데이터 조회 방식
앞서 HQL에서는 조인해서 객체를 가져오는 경우 배열에 저장해서 리턴한 것을 기억할 것이다. 아래 코드는 앞서 살펴봤었던 HQL을 이용한 조인 처리 코드이다.
Query query = session.
createQuery("from Item item inner join item.bids");
List list = query.list();
for (int i = 0 ; i < list.size() ; i++) {
Object[] object = (Object[])list.get(i);
Item item = (Item)object[0];
Bid bid = (Bid)object[1];
...
}
createQuery("from Item item inner join item.bids");
List list = query.list();
for (int i = 0 ; i < list.size() ; i++) {
Object[] object = (Object[])list.get(i);
Item item = (Item)object[0];
Bid bid = (Bid)object[1];
...
}
하지만, Criteria는 조인 처리를 하더라도 리턴되는 값을 최초에 Session.createCriteria() 메소드를 사용해서 명시한 객체이다. 예를 들어, 위와 같은 조인을 처리하는 Criteria 코드는 아래와 같다.
Criteria itemCrit = session.createCriteria(Item.class);
Criteria bidCrit = itemCrit.createCriteria("bids");
List list = itemCrit.list();
for (int i = 0 ; i < list.size() ; i++) {
Item item = (Item)list.get(i);
...
}
Criteria bidCrit = itemCrit.createCriteria("bids");
List list = itemCrit.list();
for (int i = 0 ; i < list.size() ; i++) {
Item item = (Item)list.get(i);
...
}
만약 연관된 객체를 동시에 읽고 싶다면, 다음과 같이 Criteria.returnMaps() 메소드를 호출해서 한 행과 관련된 Item과 Bid 객체를 Map에 저장할 수 있다.
Criteria itemCrit = session.createCriteria(Item.class);
itemCrit.createAlias("bids", "bid");
itemCrit.add( Expression.gt("bid.amount", new Integer(100)) );
List list = itemCrit.returnMap().list();
for (int i = 0 ; i < list.size() ; i++) {
Map map = (Map)list.get(i);
Item item = (Item)map.get("this");
Bid bid = (Bid)map.get("bid");
...
}
itemCrit.createAlias("bids", "bid");
itemCrit.add( Expression.gt("bid.amount", new Integer(100)) );
List list = itemCrit.returnMap().list();
for (int i = 0 ; i < list.size() ; i++) {
Map map = (Map)list.get(i);
Item item = (Item)map.get("this");
Bid bid = (Bid)map.get("bid");
...
}
관련링크: