Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Info

자바진영의 대표적인 ORM으로 JPA가 있습니다. 네이밍이 의미하듯

JAVA 오브젝트를 사용해야하며 스칼라 프로젝트에서 자바코드와 썩일수가 있습니다.

순수 스칼라 오브젝트만 사용하여 ORM을 이용하고자 할시 Slick이 추천됩니다.

웹에서 MVC패턴은 템플릿을 포함하여 타 웹플랫폼과 크게 다를바가 없습니다.


Dependency

Code Block
languagescala
themeEmacs
titlebuild.sbt
linenumberstrue
libraryDependencies += "com.h2database" % "h2" % "1.4.197"

libraryDependencies ++= Seq(
  "com.typesafe.play" %% "play-slick" % "3.0.0",
  "com.typesafe.play" %% "play-slick-evolutions" % "3.0.0"
)

...

Code Block
languagescala
themeEmacs
titleviews
linenumberstrue
// main.scala.html
@(title: String)(content: Html)

<!DOCTYPE html>

<html>
<head>
    <title>@title</title>
    <link rel="shortcut icon" type="image/png" href="@routes.Assets.at("images/favicon.png")"/>
</head>
<body>
@content
</body>
</html>

// person.scala.html
@(person: Form[CreatePersonForm])(implicit request: MessagesRequestHeader)

@import helper._

@request.flash.get("success").map { key =>
@request.messages(key)
}

@main("Welcome to Play") {
@form(routes.PersonController.addPerson()) {
@inputText(person("name"))
@inputText(person("age"))
@CSRF.formField

<div class="buttons">
    <input type="submit" value="Add Person"/>
</div>
}
}


Model

ORM 과 SQL쿼리방식의 개발 차이는 여기에있습니다. 쿼리 방식에서는 해당 모델을 SQL문으로 한번더 맵핑처리를하여야하며

쿼리를 사용해야합니다. ORM에서는 엔티티와 모델이 일치함으로 추가적인 쿼리개발이 필요가 없습니다. 

Code Block
languagescala
themeEmacs
titlemodels
linenumberstrue
//person.scala
//
package models

import play.api.libs.json._

case class Person(id: Long, name: String, age: Int)

object Person {
  implicit val personFormat = Json.format[Person]
}


// personRepository.scala 
//
package models

import javax.inject.{ Inject, Singleton }
import play.api.db.slick.DatabaseConfigProvider
import slick.jdbc.JdbcProfile

import scala.concurrent.{ Future, ExecutionContext }

/**
  * A repository for people.
  *
  * @param dbConfigProvider The Play db config provider. Play will inject this for you.
  */
@Singleton
class PersonRepository @Inject() (dbConfigProvider: DatabaseConfigProvider)(implicit ec: ExecutionContext) {
  // We want the JdbcProfile for this provider
  private val dbConfig = dbConfigProvider.get[JdbcProfile]

  // These imports are important, the first one brings db into scope, which will let you do the actual db operations.
  // The second one brings the Slick DSL into scope, which lets you define the table and other queries.
  import dbConfig._
  import profile.api._

  /**
    * Here we define the table. It will have a name of people
    */
  private class PeopleTable(tag: Tag) extends Table[Person](tag, "people") {

    /** The ID column, which is the primary key, and auto incremented */
    def id = column[Long]("id", O.PrimaryKey, O.AutoInc)

    /** The name column */
    def name = column[String]("name")

    /** The age column */
    def age = column[Int]("age")

    /**
      * This is the tables default "projection".
      *
      * It defines how the columns are converted to and from the Person object.
      *
      * In this case, we are simply passing the id, name and page parameters to the Person case classes
      * apply and unapply methods.
      */
    def * = (id, name, age) <> ((Person.apply _).tupled, Person.unapply)
  }

  /**
    * The starting point for all queries on the people table.
    */
  private val people = TableQuery[PeopleTable]

  /**
    * Create a person with the given name and age.
    *
    * This is an asynchronous operation, it will return a future of the created person, which can be used to obtain the
    * id for that person.
    */
  def create(name: String, age: Int): Future[Person] = db.run {
    // We create a projection of just the name and age columns, since we're not inserting a value for the id column
    (people.map(p => (p.name, p.age))
      // Now define it to return the id, because we want to know what id was generated for the person
      returning people.map(_.id)
      // And we define a transformation for the returned value, which combines our original parameters with the
      // returned id
      into ((nameAge, id) => Person(id, nameAge._1, nameAge._2))
      // And finally, insert the person into the database
      ) += (name, age)
  }

  /**
    * List all the people in the database.
    */
  def list(): Future[Seq[Person]] = db.run {
    people.result
  }
}

...

Code Block
languagescala
themeEmacs
titlecontrollers/personcontroller.scala
linenumberstrue
package controllers

import javax.inject._

import models._
import play.api.data.Form
import play.api.data.Forms._
import play.api.data.validation.Constraints._
import play.api.i18n._
import play.api.libs.json.Json
import play.api.mvc._

import scala.concurrent.{ExecutionContext, Future}

class PersonController @Inject()(repo: PersonRepository,
                                 cc: MessagesControllerComponents
                                )(implicit ec: ExecutionContext)
  extends MessagesAbstractController(cc) {

  /**
    * The mapping for the person form.
    */
  val personForm: Form[CreatePersonForm] = Form {
    mapping(
      "name" -> nonEmptyText,
      "age" -> number.verifying(min(0), max(140))
    )(CreatePersonForm.apply)(CreatePersonForm.unapply)
  }

  /**
    * The index action.
    */
  def index = Action { implicit request =>
    Ok(views.html.person(personForm))
  }

  /**
    * The add person action.
    *
    * This is asynchronous, since we're invoking the asynchronous methods on PersonRepository.
    */
  def addPerson = Action.async { implicit request =>
    // Bind the form first, then fold the result, passing a function to handle errors, and a function to handle succes.
    personForm.bindFromRequest.fold(
      // The error function. We return the index page with the error form, which will render the errors.
      // We also wrap the result in a successful future, since this action is synchronous, but we're required to return
      // a future because the person creation function returns a future.
      errorForm => {
        Future.successful(Ok(views.html.person(errorForm)))
      },
      // There were no errors in the from, so create the person.
      person => {
        repo.create(person.name, person.age).map { _ =>
          // If successful, we simply redirect to the index page.
          Redirect(routes.PersonController.index).flashing("success" -> "user.created")
        }
      }
    )
  }

  /**
    * A REST endpoint that gets all the people as JSON.
    */
  def getPersons = Action.async { implicit request =>
    repo.list().map { people =>
      Ok(Json.toJson(people))
    }
  }
}

/**
  * The create person form.
  *
  * Generally for forms, you should define separate objects to your models, since forms very often need to present data
  * in a different way to your models.  In this case, it doesn't make sense to have an id parameter in the form, since
  * that is generated once it's created.
  */
case class CreatePersonForm(name: String, age: Int)

...


evolution

최초 DB설정시 테이블생성 마이그레이션등에 활용될수 있는듯 하며 , 테이블을 어플리케이션이 생성하는

방법은 대부분 정책적인 이슈로 금지되며,  개발 초기단계에서  활용이 가능할것으로 보입니다. 

Code Block
languagesql
titleconf/evolutuins/default/1.sql
linenumberstrue
# --- !Ups

create table "people" (
  "id" bigint generated by default as identity(start with 1) not null primary key,
  "name" varchar not null,
  "age" int not null
);

# --- !Downs

drop table "people" if exists;


Image Added