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

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

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

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


Dependency

build.sbt
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"
)

App Setting

conf/application.conf
## Evolutions
# https://www.playframework.com/documentation/latest/Evolutions
# ~~~~~
# Evolutions allows database scripts to be automatically run on startup in dev mode
# for database migrations. You must enable this by adding to build.sbt:
#
# libraryDependencies += evolutions
#
play.evolutions {
  # You can disable evolutions for a specific datasource if necessary
  #db.default.enabled = false
}

## Database Connection Pool
# https://www.playframework.com/documentation/latest/SettingsJDBC
# ~~~~~
# Play doesn't require a JDBC database to run, but you can easily enable one.
#
# libraryDependencies += jdbc
#
play.db {
  # The combination of these two settings results in "db.default" as the
  # default JDBC pool:
  #config = "db"
  #default = "default"

  # Play uses HikariCP as the default connection pool.  You can override
  # settings by changing the prototype:
  prototype {
    # Sets a fixed JDBC connection pool size of 50
    #hikaricp.minimumIdle = 50
    #hikaricp.maximumPoolSize = 50
  }
}

## JDBC Datasource
# https://www.playframework.com/documentation/latest/JavaDatabase
# https://www.playframework.com/documentation/latest/ScalaDatabase
# ~~~~~
# Once JDBC datasource is set up, you can work with several different
# database options:
#
# Slick (Scala preferred option): https://www.playframework.com/documentation/latest/PlaySlick
# JPA (Java preferred option): https://playframework.com/documentation/latest/JavaJPA
# EBean: https://playframework.com/documentation/latest/JavaEbean
# Anorm: https://www.playframework.com/documentation/latest/ScalaAnorm
#
db {
  # You can declare as many datasources as you want.
  # By convention, the default datasource is named `default`

  # https://www.playframework.com/documentation/latest/Developing-with-the-H2-Database
  #default.driver = org.h2.Driver
  #default.url = "jdbc:h2:mem:play"
  #default.username = sa
  #default.password = ""

  # You can turn on SQL logging for any datasource
  # https://www.playframework.com/documentation/latest/Highlights25#Logging-SQL-statements
  #default.logSql=true
}

slick.dbs.default.driver="slick.driver.H2Driver$"
slick.dbs.default.db.profile="org.h2.Driver"
slick.dbs.default.db.url="jdbc:h2:mem:play;DB_CLOSE_DELAY=-1"


Router Setting

conf/routes
GET     /orm/                           controllers.PersonController.index
POST    /orm/person                     controllers.PersonController.addPerson
GET     /orm/persons                    controllers.PersonController.getPersons


View

views
// 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에서는 엔티티와 모델이 일치함으로 추가적인 쿼리개발이 필요가 없습니다. 

models
//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
  }
}


Controller

controllers/personcontroller.scala
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설정시 테이블생성 마이그레이션등에 활용될수 있는듯 하며 , 테이블을 어플리케이션이 생성하는

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

conf/evolutuins/default/1.sql
# --- !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;







  • No labels