【问题标题】:Scala/Slick: using inheritance and mixins to reduce boilerplateScala/Slick:使用继承和混合来减少样板文件
【发布时间】:2016-09-15 14:55:12
【问题描述】:

我是 scala/play/slick 新手,所以如果我问愚蠢的问题,请不要太生气。

问题来了。
我有几个漂亮的表定义,这里是其中之一:

import javax.inject.Inject

import play.api.db.slick.{DatabaseConfigProvider, HasDatabaseConfigProvider}
import play.api.libs.concurrent.Execution.Implicits.defaultContext
import play.db.NamedDatabase
import slick.driver.JdbcProfile

import scala.concurrent.Future

case class User(id: Int, login: String, password: String) extends Identifiable

class UserDAO @Inject()(@NamedDatabase protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
  import driver.api._

  private val users = TableQuery[UsersTable]

  def all(): Future[Seq[User]] = db.run(users.result)
  def insert(dog: User): Future[Unit] = db.run(users += dog).map { _ => () }
  def delete(id: Int): Future[Int] = db.run(users.filter(_.id === id).delete)


  private class UsersTable(tag: Tag) extends Table[User](tag, "USER") {
    def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
    def email = column[String]("email")
    def password = column[String]("password")
    def * = (id, email, password) <> (User.tupled, User.unapply)
  }
}  

想象一下我有更多的表有def id = column[Int]("id", O.PrimaryKey, O.AutoInc) 来消除这个我需要写这样的东西:

trait Identifiable {
  this: Table[_] =>
  def id = column[String]("id", O.PrimaryKey)
}

但是如何以与数据库无关的方式在此处导入表?此外还有更多的改进空间:所有提供可识别表访问的 DAO 对象都可以从包含allinsertfinddelete 方法的公共抽象类继承。类似的东西(无法编译):

abstract class BaseDAO[E <: Identifiable] extends DAO[E] with HasDatabaseConfigProvider[JdbcProfile] {
  import driver.api._
  private val entities = TableQuery[BaseTable]

  def all(): Future[Seq[E]] = db.run(entities.result)
  def insert(entity: E): Future[Unit] = db.run(entities += entity).map { _ => () }
  def delete(entity: E): Future[Int] = db.run(entities.filter(_.id === entity.id).delete)
  def find(id: Int): Future[E] = db.run(entities.filter(_.id === entities.id))

  trait BaseTable { this: Table[_] =>
    def id = column[String]("id", O.PrimaryKey, O.AutoInc)
  }
}

有人可以指出我的错误吗?谢谢。

【问题讨论】:

    标签: scala inheritance playframework slick mixins


    【解决方案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]]
    }
    

    导入 slick.ast.BaseTypedType 导入 slick.backend.DatabaseConfig 导入 slick.driver.JdbcProfile

    导入 scala.concurrent.ExecutionContext

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

    EntityActions.scala

    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
    

    【讨论】:

    • 这是一个非常有趣且详尽的答案!尽管我从您的解决方案中改变了很多,但我也从中继承了很多。谢谢!
    • @mr.nothing 如果您认为该解决方案能达到目的,您可以接受。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-03
    • 2011-09-20
    • 1970-01-01
    • 2021-11-03
    相关资源
    最近更新 更多