'Slick'에 해당되는 글 3건

  1. 2014.10.07 Enum Type을 Slick 에서 사용하기
  2. 2014.10.05 Slick
  3. 2014.08.01 Slick MultiDatasource, TableName, CRUD trait...

Enum Type을 Slick 에서 사용하기

language/scala 2014.10.07 16:08 posted by dev.bistro


1. Enum 타입의 사용

scala.Enumeration을 상속한다.

object REQUEST_METHOD_TYPE extends Enumeration {
  type REQUEST_METHOD_TYPE = Value
  val GET, POST = Value
}


2. Slick의 선언

implicit val MethodMapper  = MappedColumnType.base[REQUEST_METHOD_TYPE,String] ( s=>s.toString, s=>REQUEST_METHOD_TYPE.withName(s))

def method = column[REQUEST_METHOD_TYPE]("method", O.NotNull, O.Default(REQUEST_METHOD_TYPE.GET), O.DBType("VARCHAR(20)"))


신고

'language > scala' 카테고리의 다른 글

scala의 lazy 비용  (0) 2014.10.21
spray는 멋지군..  (0) 2014.10.20
Enum Type을 Slick 에서 사용하기  (0) 2014.10.07
Slick  (0) 2014.10.05
Functional Programming Principles in Scala 끝!  (1) 2013.06.04
Scala Tuple  (0) 2013.03.12
TAG enum, scala, Slick

Slick

language/scala 2014.10.05 01:47 posted by dev.bistro

어떤 언어를 사용하던지, 무엇을 만들던지 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
Slick  (0) 2014.10.05
Functional Programming Principles in Scala 끝!  (1) 2013.06.04
Scala Tuple  (0) 2013.03.12
2.10을 위한 scala-ide plugin 재설치  (0) 2013.03.12
TAG Slick

Slick MultiDatasource, TableName, CRUD trait...

분류없음 2014.08.01 19:08 posted by dev.bistro


원하는거 : 부끄럽지 않은 코드를 만들고 싶다. 중복을 줄이고싶다-_-;;


요구사항

1. Oracle, Mysql 2개Datasource에 접근해야함 => local import
2. 테이블명이 각각 다름 -> class 생성자
3. CRUD의 중복을 줄이고 싶음 trait사용...


삽질한것.

1. dirver.simple._를 분리해야 한다. 아니면 oracle, mysql의 ddl 코드가 제네레이션 될때 문제가 발생한다. (오라클용 insert가 mysql에서 사용될려는 등)

2. extends CRUD 부분. 좀 더 깔끔하게 할 방법이 없을까? tableName과 driver, TableQuery 3개를 DI해야하는데 좋은 방법이 떠오르지 않아서 implicit로 처리했다. 좀 더 좋은게 있을듯 한데?


ps. 리팩토링 관련 조언 좀 부탁드려요.



trait CRUD[T] {


  implicit val driver: JdbcProfile

  import driver.simple._


  implicit val tableName:String

  implicit val prop:TableQuery[_<: Table[T]]


  def drop(implicit  session:Session) =  if ( MTable.getTables(tableName).list().nonEmpty)   prop.ddl.drop

  def create(implicit  session:Session) =  if ( MTable.getTables(tableName).list().isEmpty) prop.ddl.create


  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: _*)

}




case class City ( synId:Int, cityId:String, citySynonym:String, moddttm:String, regdttm:String, status:String, datahubstatus:String )

class CityDao (val driver: JdbcProfile, val tableName:String) extends CRUD[City] {

  import driver.simple._

  class HubSynCity(tag: Tag, tableName: String) extends Table[City](tag, tableName) {

    def synId = column[Int]("CITY_SYN_ID")

    def cityId = column[String]("CITY_ID")

    def citySynonym = column[String]("CITY_SYNONYM")

    def moddttm = column[String]("MOD_DTTM")

    def regdttm = column[String]("REG_DTTM")

    def status = column[String]("STATUS")

    def datahubstatus = column[String]("DATAHUBSTATUS")

    def * = (synId, cityId, citySynonym, moddttm, regdttm, status, datahubstatus) <>(City.tupled, City.unapply)

  }

  val prop = TableQuery[HubSynCity]((tag:Tag) => new HubSynCity(tag, tableName))

}



신고
TAG scala, Slick, Trait


티스토리 툴바