光滑的通用和驱动程序无关

Posted

技术标签:

【中文标题】光滑的通用和驱动程序无关【英文标题】:Slick generic AND driver agnostic 【发布时间】:2017-01-22 18:58:46 【问题描述】:

基本上我想要实现的是以下组合:

Slick 3.0.0 database agnostism 和 Slick 3 reusable generic repository

实际上,我尝试了很多,但我根本无法让它工作。

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.createdropTable...一样 -> 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】:

与数据库无关且代码可高度重用

我正在使用 SlickPlayframework,这就是我实现数据库无关和通用存储库的方式。

请注意,这项工作的灵感来自 Active Slick

我想在我的case class 上定义像这样的基本 crud 操作。我应该能够做到countupdatedeletecreate。我只想编写一次 curd 代码并永远重复使用它。

这是演示这一点的 sn-p。

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 只不过是我们的案例类

Entitycase class,我们在其上进行 crud 操作。为了定位我们的实体,我们还需要设置IdId 对于在数据库中定位和操作实体或记录很重要。还有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]]

现在让我们实现这些方法。为了进行操作,我们需要TableTableQuery。假设我们有tabletableQuery。 trait 的好处是我们可以声明一个契约并将实现细节留给子类或子类型

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)

您还可以在 EntityActions 中添加scheme 创建和删除逻辑

tableQuery.schema.create
table.schema.drop

【讨论】:

除了 ActiveRectord 模式,我非常喜欢它,并会尽快尝试!谢谢好先生! 您能否提供帮助以使表格定义可扩展? 你将如何实现foreignKeys?就像你有一个 DogRelation 或类似的引用 Dog.id @Sorona 你可以用类似的方式来做......在EntityActions 中进行更改并引入新合同 还有一个(非常难的)问题:您将如何添加另一层。在 Dog 下说你想要ChihuahuaDog 仍然不应该是抽象的。您想要做的就是向数据库添加另一列(所以Chihuahuas extends Dogs)。你会怎么做?我似乎找不到方法,因为案例类无法扩展,因此我无法在ChihuahuaEntityTable 中覆盖Entity :( 我完全被困在这里。我了解所有这些是如何工作的,但只要我添加了另一层我迷路了。

以上是关于光滑的通用和驱动程序无关的主要内容,如果未能解决你的问题,请参考以下文章

创建反应应用程序和反应光滑

第四季-专题15-网卡驱动程序设计

虚拟机类加载机制

深入理解Java虚拟机 理解平台无关性

如何给druid指定db2数据库jdbc驱动类

在反应光滑滑块中编辑光滑列表类