Гладкий универсальный И независимый от водителя
По сути, я хочу достичь комбинации:
Агностицизм базы данных 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