运维开发网

scala – 如何编写数据库无关的播放应用程序和执行首次数据库初始化?

运维开发网 https://www.qedev.com 2020-03-20 22:50 出处:网络 作者:运维开发网整理
我使用 Slick与 Play Framework 2.1和我有一些麻烦。 给定以下实体… package models import scala.slick.driver.PostgresDriver.simple._ case class Account(id: Option[Long], email: String, password: String) object Accounts
我使用 Slick与 Play Framework 2.1和我有一些麻烦。

给定以下实体…

package models

import scala.slick.driver.PostgresDriver.simple._

case class Account(id: Option[Long], email: String, password: String)

object Accounts extends Table[Account]("account") {
  def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
  def email = column[String]("email")
  def password = column[String]("password")
  def * = id.? ~ email ~ password <> (Account, Account.unapply _)
}

…我必须导入一个特定数据库驱动程序的包,但我想使用H2测试和PostgreSQL生产。我应该如何进行?

我能够解决这个问题,通过覆盖我的单元测试中的驱动程序设置:

package test

import org.specs2.mutable._

import play.api.test._
import play.api.test.Helpers._

import scala.slick.driver.H2Driver.simple._
import Database.threadLocalSession

import models.{Accounts, Account}

class AccountSpec extends Specification {

  "An Account" should {
    "be creatable" in {
      Database.forURL("jdbc:h2:mem:test1", driver = "org.h2.Driver") withSession {
        Accounts.ddl.create                                                                                                                                          
        Accounts.insert(Account(None, "user@gmail.com", "Password"))
        val account = for (account <- Accounts) yield account
        account.first.id.get mustEqual 1
      }
    }
  }
}

我不喜欢这个解决方案,我想知道是否有一个优雅的方式编写DB-agnostic代码,所以有两个不同的数据库引擎使用 – 一个在测试和另一个在生产?

我不想使用evolution,也喜欢让Slick为我创建数据库表:

import play.api.Application
import play.api.GlobalSettings
import play.api.Play.current
import play.api.db.DB

import scala.slick.driver.PostgresDriver.simple._
import Database.threadLocalSession

import models.Accounts

object Global extends GlobalSettings {

  override def onStart(app: Application) {
    lazy val database = Database.forDataSource(DB.getDataSource())

    database withSession {
      Accounts.ddl.create
    }
  }
}

第一次启动应用程序时,一切工作正常…然后,当然,我第二次启动应用程序它崩溃,因为表已经存在于PostgreSQL数据库中。

也就是说,我的最后两个问题是:

>如何确定数据库表是否已存在?

>如何使onStart方法上面的DB-agnostic,以便我可以测试我的应用程序与FakeApplication?

您可以在这里找到一个关于如何使用蛋糕模式/依赖注入来从数据库访问层解耦Slick驱动程序的示例: https://github.com/slick/slick-examples。

如何使用FakeApplication解耦Slick驱动程序和测试应用程序

几天前我写了一个Slick集成库来播放,它将驱动程序的依赖关系移动到Play项目的application.conf:https://github.com/danieldietrich/slick-integration。

在这个库的帮助下,您的示例将实现如下:

1)将依赖项添加到项目/ Build.scala

"net.danieldietrich" %% "slick-integration" % "1.0-SNAPSHOT"

添加快照存储库

resolvers += "Daniel's Repository" at "http://danieldietrich.net/repository/snapshots"

或本地存储库,如果光滑整合在本地发布

resolvers += Resolver.mavenLocal

2)将slick驱动程序添加到conf / application.conf

slick.default.driver=scala.slick.driver.H2Driver

3)实现app / models / Account.scala

在浮动积分的情况下,假设您使用类型Long的主键,它们是自动递增的。 pk名称是’id’。 Table / Mapper实现有默认方法(delete,findAll,findById,insert,update)。您的实体必须实现’insertId’方法所需的’withId’。

package models

import scala.slick.integration._

case class Account(id: Option[Long], email: String, password: String)
    extends Entity[Account] {
  // currently needed by Mapper.create to set the auto generated id
  def withId(id: Long): Account = copy(id = Some(id))
}

// use cake pattern to 'inject' the Slick driver
trait AccountComponent extends _Component { self: Profile =>

  import profile.simple._

  object Accounts extends Mapper[Account]("account") {
    // def id is defined in Mapper
    def email = column[String]("email")
    def password = column[String]("password")
    def * = id.? ~ email ~ password <> (Account, Account.unapply _)
  }

}

4)实现app / models / DAL.scala

这是控制器用于访问数据库的数据访问层(DAL)。事务由相应组件中的Table / Mapper实现来处理。

package models

import scala.slick.integration.PlayProfile
import scala.slick.integration._DAL
import scala.slick.lifted.DDL

import play.api.Play.current

class DAL(dbName: String) extends _DAL with AccountComponent
    /* with FooBarBazComponent */ with PlayProfile {

  // trait Profile implementation
  val profile = loadProfile(dbName)
  def db = dbProvider(dbName)

  // _DAL.ddl implementation
  lazy val ddl: DDL = Accounts.ddl // ++ FooBarBazs.ddl

}

object DAL extends DAL("default")

5)实现test / test / AccountSpec.scala

package test

import models._
import models.DAL._
import org.specs2.mutable.Specification
import play.api.test.FakeApplication
import play.api.test.Helpers._
import scala.slick.session.Session

class AccountSpec extends Specification {

  def fakeApp[T](block: => T): T =
    running(FakeApplication(additionalConfiguration = inMemoryDatabase() ++
        Map("slick.default.driver" -> "scala.slick.driver.H2Driver",
          "evolutionplugin" -> "disabled"))) {
      try {
        db.withSession { implicit s: Session =>
          try {
            create
            block
          } finally {
            drop
          }
        }
      }
    }

  "An Account" should {
    "be creatable" in fakeApp {
      val account = Accounts.insert(Account(None, "user@gmail.com", "Password"))
      val id = account.id
      id mustNotEqual None 
      Accounts.findById(id.get) mustEqual Some(account)
    }
  }

}

如何确定数据库表是否已存在

我不能给你一个足够的答案这个问题…

…但也许这不是真的你想要做的。如果你向表中添加一个属性,如Account.active,该怎么办?如果你想保护表中当前存储的数据,那么alter脚本将会完成这项工作。目前,这样的alter脚本必须用手写。 DAL.ddl.createStatements可以用于检索create语句。它们应该被排序以更好地与以前的版本相比。然后使用diff(使用以前的版本)手动创建alter脚本。这里,演变用于改变数据库模式。

这里有一个关于如何生成(第一个)进化的例子:

object EvolutionGenerator extends App {

  import models.DAL

  import play.api.test._
  import play.api.test.Helpers._

    running(FakeApplication(additionalConfiguration = inMemoryDatabase() ++
        Map("slick.default.driver" -> "scala.slick.driver.PostgresDriver",
          "evolutionplugin" -> "disabled"))) {


    val evolution = (
      """|# --- !Ups
         |""" + DAL.ddl.createStatements.mkString("\n", ";\n\n", ";\n") +
      """|
         |# --- !Downs
         |""" + DAL.ddl.dropStatements.mkString("\n", ";\n\n", ";\n")).stripMargin

    println(evolution)

  }

}
0

精彩评论

暂无评论...
验证码 换一张
取 消