如何编写与数据库无关的应用程序并执行初次数据库初始化?

我正在使用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进行testing,并在生产中使用PostgreSQL 。 我应该如何继续?

我能通过覆盖我的unit testing中的驱动程序设置来解决这个问题:

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

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

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

 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方法,以便我可以使用FakeApplicationtesting我的应用程序?

你可以在这里find一个关于如何使用Cake模式/dependency injection来解耦Slick驱动和数据库访问层的例子: https : //github.com/slick/slick-examples 。

如何使用FakeApplication分离Slick驱动程序和testing应用程序

前几天我写了一个用于播放的Slick集成库,它将驱动程序的依赖关系移动到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)实施应用程序/模型/ Account.scala

在滑动集成的情况下,假设您使用自动递增的Longtypes的主键。 PK名称是“ID”。 Table / Mapper实现具有缺省方法(delete,findAll,findById,insert,update)。 您的实体必须实现'insert'方法所需的'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)实现应用程序/模型/ 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)执行testing/testing/ 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 script来完成这项工作。 目前,这种改写脚本必须手写。 DAL.ddl.createStatements可以用来检索create语句。 应该对它们进行sorting,以便与以前的版本相比更好。 然后使用diff(以前的版本)手动创buildalter script。 在这里,evolutions被用来改变db模式。

以下是如何生成(第一个)进化的例子:

 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) } } 

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

我不想在这里讨论蛋糕模式的利弊,但是我find了另一个解决scheme,对于那些有兴趣的人。

基本上,做一个这样的对象:

 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._ 

更新:一个光滑的3.0版本,由trent-ahrens提供:

 package com.lolboxen.scala.slick.driver 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 } } } 

这个游戏画面与其他答案中提出的完全一样,似乎在Play / Typesafe的保护下。

您可以导入import play.api.db.slick.Config.driver.simple._并根据conf/application.confselect适当的驱动conf/application.conf

它还提供了一些更多的东西,如连接池,DDL代…

如果像我一样,你不使用Play! 对于这个项目,Nishruu 在这里提供了一个解决scheme