제목 그대로, 한 하루정도 삽질했는데 하도 한글 레퍼런스부터 영문까지도 잘 없어서 포스팅한다.
솔직히 말해 플레이 레퍼런스 없어도 너무 없다. 스칼라 자체는 좀 많은데, 플레이는 자바는 좀 있어도 스칼라 자체는 없으니.. 플레이 하며 프레임워크적인 것은 그렇다 쳐도 대부분은 사실 스칼라 언어의 이해적인 문제이니.. 뭐 implicit나 Future같은 것들 말이다 ㅎㅎ 결국 기본적으로 스칼라를 더 깊게 공부해야 할 필요성을 느낌..
그나저나 내가 너무 앞서서 공부하는 것이 아닌가 싶기도 한데(여기 실리콘벨리에서도 워낙 사용자도 없고 생소해서리) 그래도 스칼라는 정말 풀스택 개발이나 스타트업 환경에서는 정말 좋은 것이 사실이고, 비용 절감에서는 확실히 효율적일 것 같다. 기본적으로 비동기 방식으로 설계된 점이 마음에 들었고, 지금 자바나 여타 언어의 추세를 봐도 스칼라처럼 발전하는 것같이 보이니깐.
서버단으로 보면 Microarchitecture가 대세인 지금, 플레이로 비동기 서버를 여럿 만들어 둔다면 유저의 흐름에 따라 유연하게 대응할 수 있다. ThreadPool방식이 아니니깐, 모든 것이 정말 핵심적으로 순수하게 Micro하게 동작하니깐..
어쨌든, 근 2일간 삽질한 포스팅.
Slick에서 MySQL의 DateTime처리하기
이 부분은 생각보다는 쉬운데, 처음에는 그냥 Timestamp사용해다 하려했는데, 생각처럼 안된다. 그래서 찾다보니 tototoshi님이 만든 slick-joda-mapper 라는게 있더라.
https://github.com/tototoshi/slick-joda-mapper
사용법은 써있긴 한데, build.sbt에 joda라이브러리 설명대로 추가해주고,
위 예제 파일처럼 처리하면 org.joda.time.DateTime 형식을 DAO에서 뭐 MySQL의 경우는
import com.github.tototoshi.slick.MySQLJodaSupport._
이것 한줄로 사용할 수 있다. 안그러면 맵핑 에러가 발생.
Slick에서 부분 Column만 Insert하기.
이게 뭔말이냐면, MySQL의 필드 중 Nullable인 경우는 Insert를 안하고자 할때, 어떻게 할 것이냐는 거다.
이걸 정말 몇 시간동안 삽질했는데.. 슬릭 3.1.1 공용문서의 경우에는
http://slick.typesafe.com/doc/3.1.1/queries.html#inserting
coffees.map(c => (c.name, c.supID, c.price)) += ("Colombian_Decaf", 101, 8.99)
뭐 이렇게만 쓰라는데 도통 되야지.. 내가 잘못 설정했나.. 하도 안되길래 몇 번의 삽질 끝에 그냥 소스 공개한다.
해당 내용은, Checklist라는 모델 안의 여러 필드 중, seq_plan, write_date, checked_value 만 값을 insert하는 경우이다.
// ChecklistDAO.scala package dao import models.Checklist import play.api.Play import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfig} import slick.driver.JdbcProfile import org.joda.time._ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future import com.github.tototoshi.slick.MySQLJodaSupport._ /** * Created by changmatthew on 2/14/16. */ class ChecklistDAO extends HasDatabaseConfig[JdbcProfile]{ private val Checklists = TableQuery[ChecklistsTable] def insert(seq_plan:Int,write_date:DateTime,checked_value:String): Future[Unit] = { val insertAction = DBIO.seq(Checklists.map(c => (c.seq_plan, c.write_date, c.checked_value)).returning(Checklists.map(_.seq)) += ((seq_plan,write_date, checked_value))) db.run(insertAction) } private class ChecklistsTable(tag: Tag) extends Table[Checklist](tag, "Checklist"){ def seq = column[Int]("seq", O.PrimaryKey, O.AutoInc) def seq_plan = column[Int]("seq_plan") def write_date = column[DateTime]("write_date") def start_date = column[DateTime]("start_date") def end_date = column[DateTime]("end_date") def is_checked = column[Int]("is_checked") def checked_value = column[String]("checked_value") def success_percent = column[Double]("success_percent") def * = (seq, seq_plan, write_date, start_date.?, end_date.?, is_checked.?, checked_value.?, success_percent.?) <> ((Checklist.apply _).tupled, Checklist.unapply _) } }
// Checklist.scala package models import java.sql.Timestamp import org.joda.time.DateTime import play.api.libs.json.Json case class Checklist( seq:Int, seq_plan:Int, write_date:DateTime, start_date:Option[DateTime], end_date:Option[DateTime], is_checked:Option[Int], checked_value:Option[String], success_percent:Option[Double] ) object Checklist { //JSON read/write implicit val userFormat = Json.format[Checklist] }
위와 같이 DAO와 모델이 구성되면,
// ChecklistController.scala package controllers import java.text.SimpleDateFormat import java.util.Calendar import com.fasterxml.jackson.annotation.JsonValue import dao.ChecklistDAO import models.Checklist import org.joda.time.DateTime import org.slf4j.{LoggerFactory, Logger} import play.api.libs.json.{Json, JsResult} import play.api.libs.json.Json.toJson import play.api.mvc._ import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.Future class ChecklistController extends Controller{ private final val logger: Logger = LoggerFactory.getLogger(classOf[ChecklistController]) def categoryDao = new CategoryDAO def checklistDao = new ChecklistDAO // 중략 def write(seq_plan:Int,write_date:DateTime,checked_value:String) = Action.async{ request => val rslt = checklistDao.insert(seq_plan, write_date, checked_value) Future(Ok(toJson(new ResponseParam("OK",100,"",toJson(""))))) } }
이렇게 컨트롤러를 구현하고, Routes에서 물려서 처리하면 된다. 핵심은 DBIO.seq내에 넣을 때 새롭게 mapping한 case class를 가지고 += 로 넣어줘야 한다는 것.. 이 외에도 보안로그인을 위해 CSRF/CORS필터처리한 부분이랑 특히 AngularJS와 RESTful로 패킷 동기화 시키는 부분 삽질했었는데, 이부분도 추후 정리해서 올리겠다.