행복한 아빠

[Grails1.0 사용자 가이드] 5. Object Relational Mapping (GORM) - 2 본문

Grails

[Grails1.0 사용자 가이드] 5. Object Relational Mapping (GORM) - 2

행복한아빠 2008. 3. 2. 15:10


5. Object Relational Mapping (GORM) - 2

  • GROM에서 질의하기


5.4 GORM에서 질의하기
GORM은 Hibernate의 객체지향 질의 언어인 HQL 기반의 동적 finder를 이용한 강력한 질의 방법을 많이 지원한다. Groovy의 collection을 조작하는 GPath와 sort, findAll등의 GROM과 결합한 메소드는 강력한 조합을 만들어낸다.
그럼 기본부터 시작해보자

인스턴스 목록 반환(Listing)
단순히 주어진 클래스의 모든 인스턴스를 얻기 위해서는 list 메소드를 사용할 수 있다.

def books = Book.list()

list 메소드는 페이지를 매기기(pagination) 위한 아규먼트를 지원한다.

def books = Book.list(offset:10, max:20)

또한 정렬도 지원한다.

def books = Book.list(sort:"title", order:"asc")

여기서 sort 아규먼트는 정렬하기 위한 도메인 클래스의 속성이름이고 order 아규먼트는 오름차순일 경우 asc, 내림차순일 경우 desc이다.

데이터베이스 식별자로 조회
두번째 기본적인 형태는 get 메소드를 이용하여 데이터베이스 식별자로 조회하는 것이다.

def book = Book.get(23)

또한 getAll을 이용해 식별자의 집합으로 인스턴스의 목록을 얻을 수 있다.

def books = Book.getAll(23, 93, 81)


5.4.1 동적 Finder들
GORM은 동적(dynamic) finder 개념을 지원한다. 동적 finder는 마치 static 메소드 호출같이 보이지만 코드 수준에서 어떤 형태로도 존재하지 않는다.

주어진 클래스의 속성을 기반으로 실시간 코드 합성(synthesis)를 이용하여 마술과 같이 자동 생성한다. Book 클래스 예제를 보자.

class Book {
    String title
    Date releaseDate
    Author author
}               
class Author {
    String name
}

Book 클래스는 title, releaseDate 그리고 author 속성들을 가지고 있다. "메소드 표현"의 양식으로 findByfindAllBy에서 이것들을 사용한다.

def book = Book.findByTitle("The Stand")
book =
   Book
    .findByTitleLike("Harry Pot%")
book =
  Book
    .findByReleaseDateBetween( firstDate, secondDate )
book =
  Book
    .findByReleaseDateGreaterThan( someDate )
book =
  Book
   .findByTitleLikeOrReleaseDateLessThan( "%Something%", someDate )

메소드표현
GORM에서 메소드 표현은 findBy 같은 접두어 뒤에 하나 이상의 속성이 결합하여 이루어진다. 기본 양식은 다음과 같다.

Book.findBy[속성][접미어]*[Boolean 연산]*[속성][접미어]

별표(*)로 표시된 토큰은 선택적이다. 각 접미어는 SQL의 모습으로 변경된다. 예를 들면

def book = Book.findByTitle("The STand")
book = Book.findByTitleLike("Harry Pot%")

위의 예에서 첫번째 질의는 동등(equality) 연산과 동일하고 뒤의 것은 Like 접미어 때문에 SQL like 연산과 동일하다.

가능한 접미어는 다음과 같다.

  • LessThan: 주어진 값보다 작음
  • LessThanEquals: 주어진 값보다 작거나 같음
  • GreaterThan: 주어진 값보다 큼
  • GreaterThanEquals: 주어진 값보다 크거나 같음
  • Like: SQL line 표현과 동일
  • Ilike: 대소문자 구분하지 않는 것 빼고 Like과 비슷함
  • NotEqual: 같지 않음
  • Between: 두 값 사이 (두 개의 아규먼트 필요)
  • IsNotNull: null 값이 아님 (아규먼트 필요없음)
  • IsNull: null 값임 (아규먼트 필요없음)

아래 예와 같이 마지막 3개는 필요한 아규먼트 개수에 영향을 주는 것을 볼 수 있다.

def now = new Date()
def lastWeek = now - 7
def book = Book.findByReleaseDateBetween(lastWeek, now)

isNull과 isNotNull은 둘 다 아규먼트가 필요없다.

def books = Book.findAllByReleaseDateIsNull()


Boolean 로직 (AND/OR)
또한 메소드 표현은 두 개의 조건을 결합하기 위해 boolean 연산을 사용할 수 있다.

def books =
    Book
     .findAllByTitleLikeAndReleaseDateGreaterThan("%Java%", new Date() - 30)

분명 메소드 이름이 너무 길게 끝날 수 있다. 이런 경우 Criteria 사용을 고려할 수 있다.

관계 질의
질의에서 관계를 사용할 수 있다.

def author = Author.findByName("Stephen King")
def books = author ? Book.findAllByAuthor(author) : []

이 경우 Author 인스턴스가 null이 아니면 주어진 Author에 해당하는 모든 Book 인스턴스를 얻을 수 있는 질의를 사용한다.

페이지 매기기(Pagination) & 정렬
페이지 매기기와 정렬 똑같이 list 메소드에서 가능하다. 역시 마지막 파라메터에 맵으로 전달함으로써 동적 finder에서도 사용할 수 있다.

def books =
    Book.findAllByTitleLike("Harry Pot%", [max:3,
                                           offset:2,
                                           sort:"title",
                                           order:"desc"]
)


5.4.2 조건 (Critera)
Criteria는 타입이 안전하고, 복잡해질 수 있는 질의를 만들기 위해 Groovy 빌더를 사용한다. 이것은 StringBuffer를 사용하는 것보다 나은 대안이다.

Criteria createCriteriawithCriteria 메소드를 통해 사용할 수 있다. 빌더는 Hibernate의 Criteria API를 사용하고 빌더의 노드는 Hibernate Criteria API의 Restrictions 클래스의 static 메소드와 매핑된다. 사용예

def c = Account.createCreteria()
def results = c {
    like("holderFirstName", "Fred%")
    and {
        between("balance", 500, 1000)
        eq ("branch", "London")
    }
    maxResults(10)
    order("holderLastName", "desc")
}


결합과 분리 (Conjunctions and Disjunctions)
이전 예제에서 보여준 것 같이 and {  } 블럭을 이용하여 논리적 AND에 조건을 그룹지을 수 있다.

and {
    between("balance", 500, 1000)
    eq ("branch", "London")
}

논리적 OR도 마찬가지로 동작한다.

or {
    between("balance", 500, 1000)
    eq ("branch", "London")
}

그리고 논리적 NOT도 마찬가지다.

not {
    between("balance", 500, 1000)
    eq ("branch", "London")
}


관계 질의
속성 이름과 매치되는 노드로 관계를 질의할 수 있다. 예를 들어 Account 클래스는 많은 Transaction 객체를 가지고 있다고 보자.

class Account {
    ...
    def hasMany = [transactions:Transaction]
    Set transactions
    ...
}

transactions 속성 이름을 빌더 노드로 이용하여 관계를 질의한다.

def c = Account.createCriteria()
def now = new Date()
def results = c.list {
    transactions {
        between('date', now-10, now)
    }
}

위 코드는 최근 10일 안에 거래(transactions)를 수행한 모든 계좌(Account)를 찾을 것이다. 논리 블럭안에 이런 관계 질의를 포함할 수 있다.

def c = Account.createCriteria()
def now = new Date()
def results = c.list {
    or {
        between('created',now-10,now)
        transactions {
            between('date',now-10, now)
        }
     }
}

여기에서는 최근 10일 안에 거래를 수행했거나 최근 10일 안에 생성된 모든 계좌들 찾을 것이다.


분리(Projections) 질의
결과를 조정(customise)하기 위해 Projections을 사용한다. Projections를 사용하기 위해서는  criteria 빌더 트리에 "projections" 노드를 정의해야 한다. projections 노드의 메소드는 Hibernate Projections에서 찾을 수 있는 메소드들과 동일한다.

def c = Account.createCriteria()
def numberOfBranches = c.get {
    projections {
        countDistinct('branch')
    }
}


스크롤 가능한 결과(Scrollable Results) 이용방법
Hibernate의 ScrollableResults 기능을 사용하려면 scroll 메소드를 호출한다.

def results = crit.scroll {
      maxResults(10)
}
def f = results.first()
def l = results.last()
def n = results.next()
def p = results.previous()
def future = results.scroll(10)
def accountNumber = results.getLong('number')

Hibernate ScrollableResults 문서를 발췌한다.

임의의 증분으로 결과 안을 돌아다닐 수 있도록 하는 result iterator이다. Query / ScrollableResult 패턴은 JDBC PreparedStatement / ResultSet 패턴과 유사하고 이 인터페이스 메소드의 의미는 ResultSet 메소드와 유사하고 비슷하게 이름지었다.

JDBC와 다르게 결과의 컬럼들은 0부터 시작한다.


Criteria 인스턴스의 속성 설정
빌더 트리의 노드가 특정 criterion과 매치되지 않는다면 Criteria 객체 자체의 속성을 설정할 수 있다. 이와 같이 이 클래스의 모든 속성에 접근이 가능하다. 아래는 Criteria 인스턴스의 setMaxResults와 setFirstResult를 호출한 예제이다.

import org.hibernate.FetchMode as FM
    ....
    def results = c.list {
        maxResults(10)
        firstResult(50)
        fetchMode("aRelationship", FM.EAGER)
    }


Eager 패칭으로 질의
Eager와 Lazy 패칭에 대한 장에서 N+1 SELECT 문제를 피하기 위해 어떻게 패칭을 선언하는지 논의했다. 어쨌든 criteria 질의를 이용하여 그 목적을 이룰 수도 있다.

import org.hibernate.FetchMode as FM
// ......def criteria = Task.createCriteria()
def tasks = criteria.list{
    eq("assignee.id", task.assignee.id)
    fetchMode('assignee', FM.EAGER)
    fetchMode('project', FM.EAGER)
    order('priority', 'asc')
}


메소드 레퍼런스
다음과 같이 메소드 이름 없이 빌더를 호출하면

c { … }

빌더는 기본으로 모든 결과를 반환(listing)하기 하므로 위 코드는 아래와 동일하다.

c.list { … }


메소드 설명
list 기본 메소드. 매칭되는 모든 row들을 반환한다.
get 유일한 결과 즉 단지 하나의 row 반환. 조건(criteria)는 오직 하나의 row를 질의하는 형식이어야 한다. 이 메소드는 첫번째 row만 반환하도록 제한(limit)하는 질의와 혼동하지 않는다.
scroll 스크롤이 가능한 결과 집합 반환
listDistinct 서브 query나 관계를 사용할 경우 결과 집합에 동일한 row가 여러 번 나올 수 있다. 이 메소드는 중복을 제거한(distinct) 엔티티들만 반환하도록 한다. 그리고 이것은 CriteriaSpecification 클래스의 DISTINT_ROOT_ENTITY와 동일하다.


5.4.3 Hibernate Query Language (HQL)
GORM 클래스들은 역시 Hibernate 질의 언어인 HQL을 지원한다. 완벽한 레퍼런스는 Hibernate 문서의 Chapter 14. HQL: The Hibernate Query Language 에 있다.
GORM은 find, findAll 그리고 executeQuery를 포함해 HQL을 동작시킬 수 있는 몇 가지 메소드를 제공한다. 질의 예제는 아래를 보자.

def results =
     Book.findAll("from Book as b where b.title like 'Lord of the%'")


위치(Positional)에 의한 파라메터와 이름에 의한(Named) 파라메터
이 경우 질의를 위해 전달된 값이 하드 코딩되어 있다. 어쨌든 위치에 의해 구분되는 파라메터(positional parameter)를 사용할 수 있다.

def results =
      Book.findAll("from Book as b where b.title like ?", ["The Shi%"])

또는 이름으로 구분되는 파라메터(named parameter)를 사용할 수 있다.

def results =
      Book.findAll("from Book as b where b.title like :search or b.author like :search", [search:"The Shi%"])


여러 줄의 질의(SQL)
질의를 여러 줄에 걸쳐 분리해야 할 경우 라인 연결 문자를 사용할 수 있다.

def results = Book.findAll("""\
from Book as b, \
     Author as a \
where b.author = a and a.surname = ?""", ['Smith'])

사용자 삽입 이미지
Groovy의 멀티라인 문자열은 HQL 질의에서 동작하지 않는다.


페이지 매기기(Pagination)과 정렬
HQL 질의에서도 페이지 매기기와 정렬을 사용할 수 있다. 그러기 위해서는 단지 메소드 호출 맨 끝에 페이지와 정렬 옵션을 해시로 지정하면 된다.

def results =
      Book.findAll("from Book as b where b.title like 'Lord of the%'",
                   [max:10, offset:20, sort:"asc", order:"title"])

---
원문: 5.4 Querying with GORM

0 Comments
댓글쓰기 폼