如何编写与数据库无关的应用程序并执行初次数据库初始化?
我正在使用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数据库。
这就是说,我最后两个问题是:
- 我怎样才能确定数据库表是否已经存在?
- 如何使数据库不可知的
onStart
方法,以便我可以使用FakeApplication
testing我的应用程序?
你可以在这里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.conf
select适当的驱动conf/application.conf
。
它还提供了一些更多的东西,如连接池,DDL代…
如果像我一样,你不使用Play! 对于这个项目,Nishruu 在这里提供了一个解决scheme