일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 스위프트
- Actor
- 파이썬 머신러닝
- Akka
- 파이썬 강좌
- play 강좌
- 플레이프레임워크
- 스칼라
- 주키퍼
- 파이썬 데이터분석
- 안드로이드 웹뷰
- 이더리움
- hyperledger fabric
- 엔터프라이즈 블록체인
- Play2 로 웹 개발
- 스칼라 강좌
- Golang
- akka 강좌
- 파이썬
- 파이썬 동시성
- CORDA
- 블록체인
- Adapter 패턴
- 하이퍼레저 패브릭
- 스칼라 동시성
- Play2
- 하이브리드앱
- Hyperledger fabric gossip protocol
- play2 강좌
- 그라파나
- Today
- Total
HAMA 블로그
Anorm 2.5 (4) - Parser API 사용하기 본문
Anorm 2.5 문서에서 parser API 에 대해 번역했습니다. 원문 바로가기
Parser API 사용하기
일반적이고 재사용가능한 파서를 만들기 위해 paser API 를 이용할 수 있습니다. 그것은 어떤 SELECT 쿼리의 결과도 파싱 할 수 있죠.
Note: 웹 어플리케이션이 대부분 비슷한 데이터셋을 리턴한다는것을 볼때 파서 api 는 매우 실용적이다. 예를들어 만약 Country 라는 객체를 결과 셋으로 부터 파싱할 수 있게 파서를 정의해 놓으면 또다른 People 이라는 파서와 함께 쉽게 그것들을 구성하여 Count 와 People 의 조인 쿼리를 처리 할 수 있게됩니다.
먼저 anorm.SqlParser._ 를 임포트하는것으로 시작하죠.
단일 결과 얻기
먼저 RowParser 가 필요하다. 하나의 로우를 파싱 해서 하나의 scala 값을 얻는다. 예를들어 싱글 컬럼 결과 셋 로우를 스칼라 Long 으로 변환하는 파서를 정의 해 보겠다.
val rowParser = scalar[Long]
그리고 나서 그것을 ResultSetParser 로 변환해야한다. 싱글 로우를 파싱하는 파서를 만들것이다.
val rsParser = scalar[Long].single
그래서 이 파서는 결과 셋을 파싱 할것이다. 그리고 Long 을 리턴할것이다. 간단한 SQL 즉 SELECT count 같은 쿼리에 의해 만들어지는 결과를 파싱하는데 유용할 것이다.
val count: Long =
SQL("select count(*) from Country").as(scalar[Long].single)
만약 기대된 단일 결과가 옵셔널 (0 또는 1 로우) 라면 , scalar 파서는 singleOpt 로 엮여질것이다.
val name: Option[String] =
SQL"SELECT name FROM Country WHERE code = $code" as scalar[String].singleOpt
단일 옵셔널 결과 얻기
country 이름을 가지고 country_id 를 얻고 싶다고 해보자. 근데 쿼리는 널을 리턴할 수 있다고 하자. 우린 singleOpt 파서를 이용할 수 있을것이다.
val countryId: Option[Long] =
SQL("SELECT country_id FROM Country C WHERE C.country='France'")
.as(scalar[Long].singleOpt)
좀더 복잡한 결과셋을 가지고 놀아보자.
좀 더 복잡한 파서를 만들어 볼까?
str("name") ~ int("population") 이것으로 RowParser 를 만들것인데 이 파서는 문자열 name 컬럼과 숫자형 population 컬럼을 파싱 할 수 있고 . 그리고 나서 ResultSetParser 을 만드는데 이것은 * 를 사용하여 이런 종류의 많은 로우를 파싱 할 겁니다.
val populations: List[String ~ Int] =
SQL("SELECT * FROM Country").as((str("name") ~ int("population")).*)
보다시피, 이 쿼리 결과 타입은 List[String ~ Int] 인데 country name 과 population items 의 리스트이죠.
동일한 코드를 다시 요렇게 쓸 수 있다.
val result: List[String ~ Int] = SQL("SELECT * FROM Country").
as((get[String]("name") ~ get[Int]("population")).*)
String~Int 타입은 뭘까? 이것은 Anorm 에서 만들어진 타입인데 다른데서 사용하기 편리한것은 아니다. 대신해서 (String, Int) 같은 같단한 튜플이 더 낫다. RowParser 에서 map 함수를 사용하여 그것들의 결과를 좀 더 편리한 타입으로 변환 시킬 수 있다.
val parser = str("name") ~ int("population") map { case n ~ p => (n, p) }
Note: 여기선 (String, Int) 튜플을 만들었는데 커스텀 케이스 클래스같은 다른 타입으로 결과를 변환 할 수 있다.
A ~ B ~ C 타입을 보통 (A,B,C) 로 변환하는 것 이 일반적이기 때문에 , 아예 flattern 함수같은 것을 미리 제공한다.
val parser = str("name") ~ int("population")
val result: List[(String, Int)] = SQL("select * from Country").as(parser.flatten.*)
이제 리턴값이 정상적인 튜플의 리스트가 되었다. (이전엔 Anorm 타입의 리스트)
RowParser
는 추출된 컬럼들과 함께 적용 될 어떤 함수와도 엮일 수 있다.
import anorm.SqlParser.{ int, str, to }
def display(name: String, population: Int): String =
s"The population in $name is of $population."
val parser = str("name") ~ int("population") map (to(display _))
Note: 매핑 함수는 반드시 부분적으로 적용 될 것이다. (syntax
fn _
) (see SLS 6.26.2, 6.26.5 - Eta expansion).
만약 리스트가 비어있지 않으면, parser.+ 는 parser.* 대신해서 사용 될 수 있다..
Anorm 은 파서 컴비네이터를 제공한다. 하나: ~>
, <~
.
import anorm.{ SQL, SqlParser }, SqlParser.{ int, str }
// Combinator ~>
val String = SQL("SELECT * FROM test").as((int("id") ~> str("val")).single)
// int 컬럼 id 와 문자열 val 컬럼을 가져야하며 결과로 val 을 유지
val Int = SQL("SELECT * FROM test").as((int("id") <~ str("val")).single)
//
int 컬럼 id 와 문자열 val 컬럼을 가져야하며 결과로 id 을 유지
더 복잡한 예제
좀 더 복잡한 예제를 시도해 볼까? 어떻게 다음 쿼리의 결과를 파싱할 수 있을까? country code 로 country name 와 모든 spoken languages 를 가져오기 위해서 말이지..
select c.name, l.language from Country c join CountryLanguage l
on l.CountryCode = c.Code where c.code = 'FRA'
1 대 다 조인이다.
자 List[(String,String)] 으로 모든 로우를 파싱해보자. ( name, language 튜플의 리스트):
var p: ResultSetParser[ List[(String,String)] ] = {
str("name") ~ str("language") map(flatten) *
}
이런 류의 결과를 얻게된다.
List(
("France", "Arabic"),
("France", "French"),
("France", "Italian"),
("France", "Portuguese"),
("France", "Spanish"),
("France", "Turkish")
)
그리고 나서 스칼라 콜렉션 API 를 사용 할 수 있게 된다. 우리가 원하는 걸로 변환 하는거다.
case class SpokenLanguages(country:String, languages:Seq[String])
languages.headOption.map { f =>
SpokenLanguages(f._1, languages.map(_._2))
}
마지막으로 우린 이렇게 편리한 함수를 얻게 되었다. (단일 객체 리턴)
case class SpokenLanguages(country:String, languages:Seq[String])
def spokenLanguages(countryCode: String): Option[SpokenLanguages] = {
val languages: List[(String, String)] = SQL(
"""
select c.name, l.language from Country c
join CountryLanguage l on l.CountryCode = c.Code
where c.code = {code};
"""
)
.on("code" -> countryCode)
.as(str("name") ~ str("language") map(flatten) *)
languages.headOption.map { f =>
SpokenLanguages(f._1, languages.map(_._2))
}
}
역주 ) headOption 은 리스트에서 첫번째 아이템을 Some(value) 타입으로 리턴받는다. 만약 리스트에 없으면 None 을 리턴한다. 따라서 위의 f 에는 하나의 튜플만이 담겨져서 SpokenLanguages 클래스의 인자로 "튜플의 첫번째 요소" 와 "튜플리스트의 두번째 요소 만의 리스트" 들이 담겨져서 리턴된다. 이 예제는 1대 다 조인의 결과를 1대 다로 데이터를 객체로 저장할 때 사용된다. 즉 사용자 id 와 그 사용자가 구매한 목록 같은거 말이다.
계속해서 우리의 예제를 좀 더 복잡 하게 해보자. 공식 언어를 분리 하기 위해서 ~
case class SpokenLanguages(
country:String,
officialLanguage: Option[String],
otherLanguages:Seq[String]
)
def spokenLanguages(countryCode: String): Option[SpokenLanguages] = {
val languages: List[(String, String, Boolean)] = SQL(
"""
select * from Country c
join CountryLanguage l on l.CountryCode = c.Code
where c.code = {code};
"""
)
.on("code" -> countryCode)
.as {
str("name") ~ str("language") ~ str("isOfficial") map {
case n~l~"T" => (n,l,true)
case n~l~"F" => (n,l,false)
} *
}
languages.headOption.map { f =>
SpokenLanguages(
f._1, // 리스트의 첫번째 튜플의 첫번째 튜플 요소
languages.find(_._3).map(_._2), // true 인것을 찾아서 두번째 튜플 요소 리턴
languages.filterNot(_._3).map(_._2) // true 인것들을 거르고 두번째 튜플요소의 리스트리턴
)
}
}
MySQL의 sample database 라면 이런것을 얻을 수 있게 된다.
$ spokenLanguages("FRA")
> Some(SpokenLanguages(France,
Some(French), // 공식 언어
List(Arabic, Italian, Portuguese, Spanish, Turkish // 나머지 언어들
))
)
역주 : Play 2 사용자가 많아 졌으면 ...
'PlayFramework2' 카테고리의 다른 글
Play2.4 로 웹 개발 시작하기 - (2) 프로젝트 살펴보기 (0) | 2016.08.27 |
---|---|
Play2.4 로 웹 개발 시작하기 - (1) 설치 및 프로젝트 만들기 (0) | 2016.08.27 |
Anorm 2.5 (3) - Streamming 과 Pattern 매칭 사용하기 (0) | 2016.08.26 |
Anorm 2.5 (2) - Anorm 시작하기 (0) | 2016.08.26 |
Anorm 2.5 (1) - Anorm 이란 ? (0) | 2016.08.26 |