2016-09-15 64 views
0

我是斯卡拉/播放/光滑的新手,所以请不要太生气,如果我问哑巴问题。斯卡拉/光滑:使用继承和mixins来减少样板

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

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对象可以从包含all,insert,finddelete方法的通用抽象类继承。类似(无法编译它):

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

请问有人能指点我的错误吗?谢谢。

回答

2

数据库无关和代码是高度可重用

我使用SlickPlayframework,这是怎么实现的数据库无关和通用库。

注意,这项工作从Active油滑

我想有这样的基本的CRUD操作的启发,在我的case class定义。我应该可以做到count,update,deletecreate。我想只写一次curd代码并永久重用它。

这是演示这个的片段。

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。有关性状好的是我们可以宣告合同,离开的实施细节,以子类或亚型

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) 

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

tableQuery.schema.create 
table.schema.drop 
+0

,这是非常有趣和详尽的答案!虽然我从你的解决方案中改变了很多,但我也从中继承了很多。谢谢! –

+0

@先生,如果您认为它符合目的,您可以接受该解决方案。 – pamu