일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 플레이 프레임워크
- reactive
- spring-kafka
- enablekafkastreams
- Kafka
- spring-batch
- kafka streams
- scala
- Logstash
- 카프카
- Elasticsearch
- kafkastream
- 한빛미디어
- RabbitMQ
- Spring
- Elk
- scala 2.10
- confluent
- statestore
- coursera
- Slick
- gradle
- kafkastreams
- schema registry
- spring-cloud-stream
- play framework
- springboot
- aws
- kafka interactive query
- avo
- Today
- Total
b
Slick 본문
어떤 언어를 사용하던지, 무엇을 만들던지 DB와의 연결은 중요한 포인트임이 틀림없다. 각 언어별로 대표적인 프레임웍이 있듯이 스칼라 역시 마찬가지이다. 스칼라를 위한 typesafe는 몇몇 표준 기술 셋을 정의하고 있다 ( http://typesafe.com/platform )
이 중에 오늘 얘기하고자 하는 것은 Slick ( http://typesafe.com/platform/tools/scala/slick ) 이다.
처음 접했을 때 생소한 개념이기도 했고, 정확한 사용방법을 숙지 할 수가 없었기에 현재 사용하고 있는 방법들을 공유하면서 어떤식으로 쓰면 되는지를 공유할 예정이다.
개념등이나 자세한 사용법은 래퍼런스 문서를 참고하면 될것이고, 이렇게 쓰는 거구나~ 정도만 참고 하시면 될듯하다. Play 1에 있던 Anorm이나 Squeryl보다는 좀 더 일반적으로 사용되고, 래퍼런스도 많고, 표준 스택이라 배움에 후회는 없을 것이다.
* 주의 *
Slick은 2013년 중순경 2.0 이 발표되면서 약간의 문법이 변화되었다. 구글링을 할 때 하더라도 버전을 주의해서 보고 아래의 문서를 참고하면 좋을 것이다.
- http://slick.typesafe.com/news/all-news.html : http://typesafe.com/platform/tools/scala/slick
- MIGRATION GUIDE FROM SLICK 1.0 TO 2.0 : http://slick.typesafe.com/doc/2.0.0-RC1/migration.html
Slick은 기존의 JPA와 Hibernate, Mybatis와는 달리 " Slick is a Functional Relational Mapping (FRM) library " 으로 소개한다. (FRM ???) FRM이라고는 하지만 전통적인 쿼리를 지원하므로 다양한 사용 방법을 알아보겠다.
* 아래 코드에 사용된 정보 *
- Scala 2.10.4
- val slickVersion = "2.1.0"
- val slick = "com.typesafe.slick" %% "slick" % slickVersion
- val slickext = "com.typesafe.slick" %% "slick-extensions" % slickVersion
Story1 - Database connection
1-1) http://slick.typesafe.com/doc/2.1.0/gettingstarted.html#database-connection 처럼 사용 할 수 있다.
1-2) Java개발자에 익숙한 DataSource를 사용 할 수도 있다.
lazy val OSlickDataSource = com.typesafe.slick.driver.oracle.OracleDriver.simple.Database.forDataSource( OBasicDataSource)
lazy val HSlickDataSource = scala.slick.driver.MySQLDriver.simple.Database.forDataSource( HBasicDataSource )
* HBasicDataSource와 OBasicDataSource는 DBCP의 BasicDataSource 이다.
* Oracle, MySQL 두개를 사용하고 있고, DataSource가 필요한 Java-Project도 있기 때문에 위와 같이 사용하고 있다.
Story2 - Plain Query
일반적인 Query Base방식을 지원한다. Mybatis나 JDBC에 익숙한 개발자라면 무리없이 이해할 수 있을 것이기 떄문에 이것부터 시작을 해보자.
HSlickDataSource withSession {
implicit session=>
Q.updateNA(s"drop table if exists $readyTableName").execute
(Q.u + "drop table a").execute
처럼 drop table명령을 실행 할 수 있다. 둘 다 같은 방식이다. insert 또한 마찬가지이다.
def insert(c: SynObject) = (Q.u + "INSERT INTO $tempTableName(SVALUE) VALUES (" + c.svalue + ")" ).execute
data.foreach(insert) //data List[SynObject]
처럼 batch insert가 가능하지만
val psmt = session.prepareInsertStatement( s"INSERT INTO $tempTableName(SCODE, SVALUE, STATUS) VALUES (?, ?, ?)" )
for(line <- data.zipWithIndex ) {
psmt.setString(1, line._1.scode)
psmt.setString(2, line._1.svalue)
psmt.setString(3, line._1.status)
psmt.addBatch()
if(line._2 % 1000 == 0) psmt.executeBatch
}
psmt.executeBatch
처럼 PreparedInsert BatchInsert를 이용하여 성능을 향상 시킬수 있다.
Story3 - Lifted Embedding API
Plain Scala의 특징과 장점을 활용하려면 이 방법으로 사용하면 된다.
우선은 1개의 DB 테이블을 Scala Class로 매핑을 하자.
3-1) case class의 선언
case class ForbiddenKeyword( id:Int, collId:String, keyword:String, itemStatus:Option[String])
금칙 키워드 정보를 가지고 있는 Class를 선언한다.
3-2) Table 매핑
class ForbiddenKeywordTable(tag:Tag) extends Table[ForbiddenKeyword](tag, "FORBIDDEN_KEYWORD") {
def id = column[Int]("id", O.PrimaryKey, O.NotNull)
def collId = column[String]("collId", O.NotNull)
def keyword = column[String]("keyword", O.NotNull)
def itemStatus = column[String]("itemStatus")
def * = (id, keyword, itemStatus) <> (ForbiddenKeyword.tupled, ForbiddenKeyword.unapply)
}
FORBIDDEN_KEYWORD 테이블 정보를 ForbiddenKeywordTable 클래스로 선언하였다.
id필드는 PK, NotNull이고 Int타입이고, itemStatus 필드는 String 타입이다. 이후에...
val forbidden = TableQuery[ForbiddenKeywordTable]
def getForbiddenKeywordList(collId:String = null) = {
ds withSession {
implicit session => {
collId match {
case e:String => forbidden.filter(_.collId === collId).list
case _ => forbidden.list
}
}
}
}
select * from FORBIDDEN_KEYWORD
select * from FORBIDDEN_KEYWORD wehre collId = ?
를 수행 할 수 있다.
3-3) Null 허용
위에서 NotNull 필드는 column 정의부에 선언했지만 allow Null은 어떻게 해야할까? Scala에서는 Option으로 처리 할 수 있다. 예를 들어 keyword column이 Null을 허용한다고 할 경우 def * (class mapping 부분)을 아래처럼 .?를 붙이면 된다.
def * = (id, keyword.?, itemStatus) <> (ForbiddenKeyword.tupled, ForbiddenKeyword.unapply)
이 부분은 ColumnExtensionMethods trait class를 참고로 하면 된다. 아래에서 다시 설명 한다.
3-4) Column Extension Method
컬럼을 매핑할 때 컬럼마다 일정 로직을 태우면서 매핑 할 수 있다.
이 부분은 ColumnExtensionMethods trait class를 참고로 하면된다. 이 부분을 조금 더 훑어보면 NumericColumnExtensionMethods, StringColumnExtensionMethods 등을 볼 수 있는데 다양한 메소드를 컬럼에 적용할 수 있음을 볼 수 있다.
예를 들어 Int-Column필드는 절대값을 가져 올수 있고 ( ex: id.abs ) String-Column은 trim을 적용 할 수도 있다. (ex : keyword.trim )
ExtensionMethods.scala
3-5) 테이블 명을 파라미터로 변경
1 테이블과 1 Class로 매핑된다면 문제가 없지만. 1 Class가 여러 테이블에 매핑 (즉, 스키마가 동일한 여러 테이블)이 존재한다면 3-2)방식을 여러개 작성하는 것은 낭비이다. parameter로 테이블 명을 받도록 변경해보자.
class ForbiddenKeywordTable(tag:Tag, tableName:String) extends Table[ForbiddenKeyword](tag, tableName) {...}
으로 선언한 이후에 아래 처럼 선언을 해서 사용을 한다.
val adminKeyword = TableQuery[ForbiddenKeywordTable]((tag:Tag) => new ForbiddenKeywordTable(tag, "ADMIN_FORBIDDEN_KEYWORD"))
val forbidden = TableQuery[ForbiddenKeywordTable]((tag:Tag) => new ForbiddenKeywordTable(tag, "FORBIDDEN_KEYWORD"))
3-6) trait CRUD
이런식의 매핑 정보를 나열하다 보면 어쩔수 없이 중복 코드를 많이 작성하게 된다. 특히 기본적인 CRUD에서 많이 보이는데 이 부분을 공통하 시켜보자.
개인적 요구 사항
#1. MySQL/ Oracle DB2개를 쓰고 각 테이블의 Schema는 동일하다
#2. 동일한 스키마를 여러 테이블이 사용한다.
#3. 공통 CRUD는 가능한 1번만 작성하고 싶다.
trait CRUD[T] {
implicit val driver: JdbcProfile
import driver.simple._
implicit val tableName:String
implicit val prop:TableQuery[_<: Table[T]]
def count(implicit session:Session) = prop.length.run
def findAll(implicit session:Session) = prop.list
def insert(obj:T)(implicit session:Session) = prop += obj
def insert(obj:List[T])(implicit session:Session) = prop.insertAll(obj: _*)
def drop(implicit session:Session) = if ( MTable.getTables(tableName).list.nonEmpty) prop.ddl.drop
def delete(obj:T )(implicit session:Session)
}
jdbcProfile과 tableName을 인자로 사용하도록 하였고, TableQuery를 내재화 시켜서 몇몇 메소드를 공통화 하였다.
delete는 그렇게 할 수 없어서 각자 구현하도록 남겨놓았다.
Story 4 - 사용후기
생각보다 학습비용이 많이 든다. 기존에 사용하던 습관이 남아 있어서 인지... 이건 이렇게 했는데 Slick에서는 어떻게 해야하지 ? 라는 고민을 많이 했다. 생각보다 구글링/so에서 원하는 만큼 결과를 얻지 못해 github.com에서 많은 프로젝트를 참고하기도 하였다.
다른 익숙한 프레임웍만큼 코어까지 살펴보기엔 꽤 오랜 시일이 걸릴 것 같지만, Slick을 처음 적용해 가면서 조금씩 긍적적인 생각이 든다. ...
'language > scala' 카테고리의 다른 글
spray는 멋지군.. (0) | 2014.10.20 |
---|---|
Enum Type을 Slick 에서 사용하기 (0) | 2014.10.07 |
Functional Programming Principles in Scala 끝! (1) | 2013.06.04 |
Scala Tuple (0) | 2013.03.12 |
2.10을 위한 scala-ide plugin 재설치 (0) | 2013.03.12 |