62

我正在使用带有Play Framework 2.1 的Slick ,但遇到了一些麻烦。

给定以下实体...

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 无关的代码,因此使用了两种不同的数据库引擎——一个用于测试,另一个用于生产?

我也不想使用进化,更喜欢让 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 数据库中。

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

  1. 如何确定数据库表是否已经存在?
  2. 如何使上述onStart方法与 DB 无关,以便我可以使用 测试我的应用程序FakeApplication
4

4 回答 4

39

您可以在此处找到有关如何使用 cake 模式/依赖注入将 Slick 驱动程序与数据库访问层分离的示例:https ://github.com/slick/slick-examples 。

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

前几天我写了一个 Slick 集成库供 play 使用,将驱动依赖移动到 Play 项目的 application.conf 中:https ://github.com/danieldietrich/slick-integration 。

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

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

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

添加快照存储库

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

或本地存储库,如果 slick-integration 在本地发布

resolvers += Resolver.mavenLocal

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

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

3) 实现 app/models/Account.scala

在 slick-integration 的情况下,假设您使用自动递增的 Long 类型的主键。pk 名称是“id”。Table/Mapper 实现具有默认方法(delete、findAll、findById、insert、update)。您的实体必须实现“插入”方法所需的“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)。事务由相应组件中的表/映射器实现处理。

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?如果您想保护当前存储在表中的数据,那么更改脚本就可以完成这项工作。目前,这样的修改脚本必须手工编写。可DAL.ddl.createStatements用于检索创建语句。应该对它们进行排序以更好地与以前的版本进行比较。然后使用 diff(与以前的版本)手动创建更改脚本。在这里,进化用于改变数据库模式。

这是一个关于如何产生(第一次)进化的例子:

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)

  }

}
于 2012-12-02T02:31:42.160 回答
28

我也试图解决这个问题:在测试和生产之间切换数据库的能力。将每个 table 对象包装在一个 trait 中的想法是没有吸引力的。

我不是想在这里讨论蛋糕图案的优缺点,但我找到了另一种解决方案,供有兴趣的人使用。

基本上,制作一个像这样的对象:

package mypackage
import scala.slick.driver.H2Driver
import scala.slick.driver.ExtendedProfile
import scala.slick.driver.PostgresDriver

object MovableDriver {
  val simple = profile.simple
  lazy val profile: ExtendedProfile = {
    sys.env.get("database") match {
      case Some("postgres") => PostgresDriver
      case _ => H2Driver
    }
  }
}

显然,你可以在这里做任何你喜欢的决策逻辑。它不必基于系统属性。

现在,而不是:

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

你可以说

import mypackage.MovableDriver.simple._

更新:一个 Slick 3.0 版本,由 trent-ahrens 提供:

package mypackage

import com.typesafe.config.ConfigFactory

import scala.slick.driver.{H2Driver, JdbcDriver, MySQLDriver}

object AgnosticDriver {
  val simple = profile.simple
  lazy val profile: JdbcDriver = {
    sys.env.get("DB_ENVIRONMENT") match {
      case Some(e) => ConfigFactory.load().getString(s"$e.slickDriver") match {
        case "scala.slick.driver.H2Driver" => H2Driver
        case "scala.slick.driver.MySQLDriver" => MySQLDriver
      }
      case _ => H2Driver
    }
  }
}
于 2013-09-28T00:33:06.230 回答
2

play-slick与其他答案中提出的完全相同,并且似乎在 Play/Typesafe 的保护伞下。

您只需导入import play.api.db.slick.Config.driver.simple._ ,它会根据conf/application.conf.

它还提供了更多的东西,比如连接池、DDL 生成......

于 2015-01-27T15:39:36.390 回答
-1

如果像我一样,您没有使用 Play!对于该项目,Nishruu在这里提供了一个解决方案

于 2016-12-21T17:38:50.800 回答