如何编写与数据库无关的Play应用程序并执行首次数据库初始化? [英] How to write database-agnostic Play application and perform first-time database initialization?

查看:63
本文介绍了如何编写与数据库无关的Play应用程序并执行首次数据库初始化?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在将 Slick

I'm using Slick with a Play Framework 2.1 and I have some troubles.

给出以下实体...

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 .我应该如何进行?

...I have to import a package for a specific database driver, but I want to use H2 for testing and PostgreSQL in production. How should I proceed?

我能够通过在单元测试中覆盖驱动程序设置来解决此问题:

I was able to workaround this by overriding the driver settings in my unit test:

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

我不喜欢这种解决方案,我想知道是否有一种优雅的方式来编写与数据库无关的代码,因此使用了两种不同的数据库引擎-一种用于测试,另一种用于生产?

I don't like this solution and I'm wondering if there is an elegant way to write DB-agnostic code so there are two different database engines used - one in testing and another in production?

我也不想使用Evolution,而是希望让Slick为我创建数据库表:

I don't want to use evolution, either, and prefer to let Slick create the database tables for me:

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数据库中.

The first time I start the application, everything works fine... then, of course, the second time I start the application it crashes because the tables already exist in the PostgreSQL database.

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

That said, my last two questions are:

  1. 如何确定数据库表是否已经存在?
  2. 如何使onStart方法在数据库不可知的上方,以便可以使用FakeApplication测试我的应用程序?
  1. How can I determine whether or not the database tables already exist?
  2. How can I make the onStart method above DB-agnostic so that I can test my application with FakeApplication?

推荐答案

您可以在此处找到有关如何使用蛋糕模式/依赖项注入将Slick驱动程序与数据库访问层分离的示例:

You find an example on how to use the cake pattern / dependency injection to decouple the Slick driver from the database access layer here: https://github.com/slick/slick-examples.

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

A few days ago I wrote a Slick integration library for play, which moves the driver dependency to the application.conf of the Play project: https://github.com/danieldietrich/slick-integration.

借助该库,您的示例将按以下方式实现:

With the help of this library your example would be implemented as follows:

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

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

添加快照存储库

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

或本地存储库,如果集成在本地发布

Or local repository, if slick-integration is published locally

resolvers += Resolver.mavenLocal

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

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

3)实施app/models/Account.scala

在平滑集成的情况下,假定您使用的是Long类型的主键,这些键是自动递增的. pk名称为"id".表/映射器实现具有默认方法(删除,findAll,findById,插入,更新).您的实体必须实现插入"方法所需的"withId".

In the case of slick-integration, it is assumed that you use primary keys of type Long which are auto incremented. The pk name is 'id'. The Table/Mapper implementation has default methods (delete, findAll, findById, insert, update). Your entities have to implement 'withId' which is needed by the 'insert' method.

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),控制器使用它来访问数据库.事务由相应Component中的Table/Mapper实现处理.

This is the Data Access Layer (DAL) which is used by the controllers to access the database. Transactions are handled by the Table/Mapper implementation within the corresponding Component.

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

}

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

对于这个问题,我无法给您足够的答案...

How to determine whether or not the database tables already exist

I cannot give you a sufficient answer to this question...

...但是也许这并不是您真正想做的.如果将属性添加到表中,例如Account.active,该怎么办?如果要保护当前存储在表中的数据的安全,则可以使用alter脚本来完成此工作.当前,这种更改脚本必须手动编写. DAL.ddl.createStatements可用于检索create语句.应该对它们进行排序,以便与以前的版本更好地进行比较.然后,使用diff(具有以前的版本)来手动创建alter脚本.在这里,演进用于更改数据库架构.

... but perhaps this is not really s.th you want to do. What if you add an attribute to an table, say Account.active? If you want to safe the data currently stored within your tables, then an alter script would do the job. Currently, such an alter script has to be written by hand. The DAL.ddl.createStatements could be used to retrieve the create statements. They should be sorted to be better comparable with previous versions. Then a diff (with previous version) is used to manually create the alter script. Here, evolutions are used to alter the db schema.

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

Here's an example on how to generate (the first) evolution:

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)

  }

}

这篇关于如何编写与数据库无关的Play应用程序并执行首次数据库初始化?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆