Slick 3 可重用通用存储库

Slick 3 reusable generic repository

我在以通用方式使用 Slick 的 TableQuery 时遇到问题。

观察常规情况:

class AccountRepository {
override protected val dbConfig = DatabaseConfigProvider.get[JdbcProfile](Play.current)
val accounts = TableQuery[Accounts]
def all = db.run(accounts.result)
...

我们的想法是将所有可能的内容提取到通用特征或抽象中 class 以避免重复。为了简单起见,我只包含了有问题的代码。

abstract class GenericRepository[T] extends HasDatabaseConfig[JdbcProfile] {
override protected val dbConfig = DatabaseConfigProvider.get[JdbcProfile(Play.current)
val table = TableQuery[T]
}

并像这样使用它:

class AccountRepository extends GenericRepository[Accounts] {

但是,这会产生编译错误:

type arguments [T] conform to the bounds of none of the overloaded alternatives of value apply: [E <: slick.lifted.AbstractTable[]]=> slick.lifted.TableQuery[E] [E <: slick.lifted.AbstractTable[]](cons: slick.lifted.Tag => E)slick.lifted.TableQuery[E]

尝试通过设置边界来解决问题也无济于事。

abstract class GenericRepository[T <: slick.lifted.AbstractTable[T]] extends HasDatabaseConfig[JdbcProfile] {

但是,我们最终遇到了不同的错误:

class type required but T found

在以下位置:

val table = TableQuery[T]

知道解决方案吗?

我猜如果你能解决tableQuery的初始化,那你就可以继续你的GenericRepository了。我在 PostgreSQL 中使用 Slick 3.0。

slick.lifted.TableQuery中,有如下方法

// object TableQuery
def apply[E <: AbstractTable[_]](cons: Tag => E): TableQuery[E] =
    new TableQuery[E](cons)

因此,如果我们可以即时获得 instance of E,那么我们就可以获得创建 TableQuery 的通用方法。所以反射似乎是一种可能的解决方法。

 import scala.reflect.runtime.{ universe => ru }
 import slick.lifted.{ AbstractTable, ProvenShape, Tag }
 import slick.driver.PostgresDriver.api._


  object Reflection {
    val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)

    def getTypeTag[T: ru.TypeTag] = ru.typeTag[T]

    def createClassByConstructor[T: ru.TypeTag](args: Any*) =
      runtimeMirror.reflectClass(getTypeTag[T].tpe.typeSymbol.asClass)  
       .reflectConstructor(ru.typeOf[T].declaration(ru.nme.CONSTRUCTOR)
       .asMethod)(args: _*).asInstanceOf[T]
  }


  // context bound here is for createClassByConstructor to use
  abstract class GenericTableQuery[U, T <: AbstractTable[U]: ru.TypeTag] {

    import Reflection._

    // look at following code: Students, if you want to initialize Students
    // you're gonna need a tag parameter, that's why we pass tag here
    val tableQuery = TableQuery.apply(tag => createClassByConstructor[T](tag))

  }

 // Sample Table
 case class Student(name: String, age: Int)
 class Students(tag: Tag) extends Table[Student](tag, "students") {
    def name = column[String]("name")
    def age = column[Int]("age")
    override def * : ProvenShape[Student] = (name, age) 
      <> (Student.tupled, Student.unapply _)
 }

 // get TableQuery
 object TestGenericTableQuery extends GenericTableQuery[Student, Students] {
    val studentQuery = tableQuery
 }

上面提到的代码只是针对通用TableQuery的问题,尝试将其与您的GenericRepository结合使用,您的问题可能会得到解决。

总之,希望对你有所帮助。

您必须手动传递 table 查询,

 abstract class GenericRepository[T <: slick.lifted.AbstractTable[_]](query: TableQuery[T])

并在实施中,

class AccountRepository extends GenericRepository[Accounts](TableQuery[Accounts])

希望这能解决您的问题。