Гладкий универсальный И независимый от водителя

По сути, я хочу достичь комбинации:

Агностицизм базы данных Slick 3.0.0 и универсальный репозиторий Slick 3 для повторного использования

На самом деле я много пробовал, но я не могу заставить это работать вообще.

abstract class BaseModel[T <: slick.lifted.AbstractTable[_]](query: TableQuery[T], val driver: JdbcProfile, val dbTableName: String)
{
  lazy val all: TableQuery[T] = TableQuery[T]
  import driver.api._

  def createTable = all.schema.create
  def dropTable = all.schema.create

  abstract class BaseTable[B](val tag: Tag) extends Table[B](tag, dbTableName)
  {
    def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
  }
}

Теперь у нас уже есть проблема:

def createTable = all.schema.create и то же самое с dropTable... -> schema не может быть решена здесь, хотя я импортирую драйвер раньше.

Но еще большая проблема возникает, когда я подкласс это:

Вот код

class NodeModel(driver: JdbcProfile, dbTableName: String) extends BaseModel[NodeTable](TableQuery[NodeTable], driver, dbTableName) {

  val dbDriver = driver
  import dbDriver.api._

  class NodeTable(tag: Tag) extends BaseTable[Node](tag)
  {
    override def * = id.? <> (Node, Node.unapply)
  }

  //lazy val all: TableQuery[NodeTable] = TableQuery[NodeTable]
  def createTable: DBIO[Unit] = all.schema.create
  def dropTable: DBIO[Unit] = all.schema.drop 
  def insert(node: Node) = all += node
}

Это не скомпилируется, очевидно, потому что я не могу пройти NodeTable как T, но дает представление о том, чего я хочу достичь.

У вас есть идеи, как это решить? Я также пытался с сопутствующими объектами, перемещая BaseTable вне BaseModel и пытается загрузить simpleDriver... но похоже, что эта функциональность была удалена из Slick в последней версии:(

1 ответ

Решение

База данных не зависит от кода и многократно используется

я использую Slick с Playframework и вот как я достиг базы данных, независимой от базы данных, и общего хранилища.

Обратите внимание, что эта работа вдохновлена ​​Active Slick

Я хочу, чтобы базовые операции типа crud были определены на моем case class , Я должен быть в состоянии сделать count , update , delete а также create , Я хочу написать творожный код только один раз и использовать его навсегда.

Вот фрагмент, который демонстрирует это.

case class Dog(name: String, id: Option[Long] = None)
Dog("some_dog").save()
Dog("some_dog").insert()
Dog("some_dog", Some(1)).delete()

CrudActions.scala

import slick.backend.DatabaseConfig
import slick.driver.JdbcProfile

import scala.concurrent.ExecutionContext


trait CrudActions {
  val dbConfig: DatabaseConfig[JdbcProfile]
  import dbConfig.driver.api._

  type Model

  def count: DBIO[Int]

  def save(model: Model)(implicit ec: ExecutionContext): DBIO[Model]

  def update(model: Model)(implicit ec: ExecutionContext): DBIO[Model]

  def delete(model: Model)(implicit ec: ExecutionContext): DBIO[Int]

  def fetchAll(fetchSize: Int = 100)(implicit ec: ExecutionContext): StreamingDBIO[Seq[Model], Model]
}

Теперь давайте посмотрим Entity в картину. Обратите внимание, что Entity это не что иное, как наш случай класса

Entity является case class на котором мы делаем грубые операции. Для нахождения нашей сущности давайте также иметь Id на месте. Id важен для обнаружения и управления объектом или записью в базе данных. Также Id однозначно идентичности для сущности

EntityActionsLike.scala

import slick.backend.DatabaseConfig
import slick.driver.JdbcProfile

import scala.concurrent.ExecutionContext

trait EntityActionsLike extends CrudActions {
  val dbConfig: DatabaseConfig[JdbcProfile]
  import dbConfig.driver.api._

  type Entity

  type Id

  type Model = Entity

  def insert(entity: Entity)(implicit ec: ExecutionContext): DBIO[Id]

  def deleteById(id: Id)(implicit ec: ExecutionContext): DBIO[Int]

  def findById(id: Id)(implicit ec: ExecutionContext): DBIO[Entity]

  def findOptionById(id: Id)(implicit ec: ExecutionContext): DBIO[Option[Entity]]
}

Теперь давайте реализуем эти методы. Для выполнения операций нам нужно Table а также TableQuery , Допустим, у нас есть table а также tableQuery , Преимущество в особенностях заключается в том, что мы можем объявить контракт и оставить детали реализации подклассам или подтипам.

EntityActions.scala

import slick.ast.BaseTypedType
import slick.backend.DatabaseConfig
import slick.driver.JdbcProfile

import scala.concurrent.ExecutionContext

trait EntityActions extends EntityActionsLike {
  val dbConfig: DatabaseConfig[JdbcProfile]
  import dbConfig.driver.api._

  type EntityTable <: Table[Entity]

  def tableQuery: TableQuery[EntityTable]

  def $id(table: EntityTable): Rep[Id]

  def modelIdContract: ModelIdContract[Entity,Id]

  override def count: DBIO[Int] = tableQuery.size.result

  override def insert(entity: Entity)(implicit ec: ExecutionContext): DBIO[Id] = {
    tableQuery.returning(tableQuery.map($id(_))) += entity
  }

  override def deleteById(id: Id)(implicit ec: ExecutionContext): DBIO[Int] = {
    filterById(id).delete
  }

  override def findById(id: Id)(implicit ec: ExecutionContext): DBIO[Entity] = {
    filterById(id).result.head
  }

  override def findOptionById(id: Id)(implicit ec: ExecutionContext): DBIO[Option[Entity]] = {
    filterById(id).result.headOption
  }

  override def save(model: Entity)(implicit ec: ExecutionContext): DBIO[Entity] = {
    insert(model).flatMap { id =>
      filterById(id).result.head
    }.transactionally
  }

  override def update(model: Entity)(implicit ec: ExecutionContext): DBIO[Entity] = {
    filterById(modelIdContract.get(model)).update(model).map { _ => model }.transactionally
  }

  override def delete(model: Entity)(implicit ec: ExecutionContext): DBIO[Int] = {
    filterById(modelIdContract.get(model)).delete
  }

  override def fetchAll(fetchSize: Int)(implicit ec: ExecutionContext): StreamingDBIO[Seq[Entity], Entity] = {
    tableQuery.result.transactionally.withStatementParameters(fetchSize = fetchSize)
  }

  def filterById(id: Id) = tableQuery.filter($id(_) === id)

  def baseTypedType: BaseTypedType[Id]

  protected implicit lazy val btt: BaseTypedType[Id] = baseTypedType

}

ActiveRecord.scala

import slick.dbio.DBIO

import scala.concurrent.ExecutionContext


abstract class ActiveRecord[R <: CrudActions](val repo: R) {
  def model: repo.Model
  def save()(implicit ec: ExecutionContext): DBIO[repo.Model] = repo.save(model)
  def update()(implicit ec: ExecutionContext): DBIO[repo.Model] = repo.update(model)
  def delete()(implicit ec: ExecutionContext): DBIO[Int] = repo.delete(model)
}

ModelContract.scala

case class ModelIdContract[A, B](get: A => B, set: (A, B) => A)

Как пользоваться

Sample.scala

import com.google.inject.{Inject, Singleton}
import play.api.db.slick.DatabaseConfigProvider
import slick.ast.BaseTypedType
import slick.backend.DatabaseConfig
import slick.driver.JdbcProfile
import slick.{ActiveRecord, EntityActions, ModelIdContract}

case class Dog(name: String, id: Option[Long] = None)

@Singleton
class DogActiveRecord @Inject() (databaseConfigProvider: DatabaseConfigProvider) extends EntityActions {

  override val dbConfig: DatabaseConfig[JdbcProfile] = databaseConfigProvider.get[JdbcProfile]

  import dbConfig.driver.api._

  override def tableQuery = TableQuery(new Dogs(_))

  override def $id(table: Dogs): Rep[Id] = table.id

  override def modelIdContract: ModelIdContract[Dog, Id] = ModelIdContract(dog => dog.id.get, (dog, id) => dog.copy(id = Some(id)))

  override def baseTypedType: BaseTypedType[Id] = implicitly[BaseTypedType[Id]]

  override type Entity = Dog
  override type Id = Long
  override type EntityTable = Dogs

  class Dogs(tag: Tag) extends Table[Dog](tag, "DogsTable") {
    def name = column[String]("name")
    def id = column[Long]("id", O.PrimaryKey)
    def * = (name, id.?) <> (Dog.tupled, Dog.unapply)
  }

  implicit class ActiveRecordImplicit(val model: Entity) extends ActiveRecord(this)

  import scala.concurrent.ExecutionContext.Implicits.global

  val result = Dog("some_dog").save()

  val res2 = Dog("some_other_dog", Some(1)).delete()

  val res3 = Dog("some_crazy_dog", Some(1)).update()
}

Теперь мы можем делать операции на Dog прямо как это

Dog("some_dog").save()

Это неявное делает волшебство для нас

implicit class ActiveRecordImplicit(val model: Entity) extends ActiveRecord(this)

Вы также можете добавить scheme создание и удаление логики в EntityActions

tableQuery.schema.create
table.schema.drop
Другие вопросы по тегам