Slick:如何通过示例实现查找,即一般的 findByExample?

Slick: how to implement find by example i.e. findByExample generically?

我正在探索如何使用最新的 Slick 3.1.1 to boost productivity and yes there is need for it because basing the service layer of my Play Web application on TableQuery alone leads to a lot of boilerplate code. One of the methods I'd like to feature in my generic DAO implementation is the findByExample, possible in JPA with the help of the Criteria API. In my case, I'm using the Slick Code Generator 实现通用 DAO 以从 sql 脚本生成模型 classes 的不同可能性。

我需要以下内容才能动态访问取自 的属性名称:

import scala.reflect.runtime.universe._

def classAccessors[T: TypeTag]: List[MethodSymbol] = typeOf[T].members.collect {
    case m: MethodSymbol if m.isCaseAccessor => m
}.toList 

findByExample 的实施草案为:

def findByExample[T, R](example: R) : Future[Seq[R]] = {
  var qt = TableQuery[T].result
  val accessors = classAccessors[R]
  (0 until example.productArity).map { i =>
    example.productElement(i) match {
      case None => // ignore
      case 0 => // ignore
      // ... some more default values => // ignore  
      // handle a populated case   
      case Some(x) => {
        val columnName = accessors(i)
        qt = qt.filter(_.columnByName(columnName) == x)
      }
    }
  }
  qt.result
}

但这不起作用,因为我需要更好的 Scala Kungfu。 T 是实体 table 类型,R 是作为案例生成的行类型 class,因此是有效的 Scala Product 类型。

该代码中的第一个问题是效率太低,因为而不是像

qt.filter(_.firstName === "Juan" && _.streetName === "Rosedale Ave." && _.streetNumber === 5)

正在做:

// find all
var qt = TableQuery[T].result
// then filter by each column at the time
qt = qt.filter(_.firstName === "Juan")
qt = qt.filter(_.streetName === "Rosedale Ave.")
qt = qt.filter(_.streetNumber === 5)

其次,我看不到如何在过滤方法中动态访问列名,即

qt.filter(_.firstName == "Juan") 

我需要

qt.filter(_.columnByName("firstName") == "Juan")

但是使用filter函数显然没有这种可能性?

通过动态提供的列名实现过滤器和排序的最佳方法可能是简单的 SQL 或扩展代码生成器以生成扩展方法,如下所示:

implicit class DynamicPersonQueries[C[_]](q: Query[PersonTable, PersonRow, C]){
  def dynamicFilter( column: String, value: String ) = column {
    case "firstName" => q.filter(_.firstName === value)
    case "streetNumber" => q.filter(_.streetNumber === value.toInt)
    ...
  }
}

您可能需要 fiddle 一些类型才能使其编译(最好在之后更新此 post :))。

然后您可以像这样按所有提供的值进行过滤:

val examples: Map[String, String] = ...
val t = TableQuery[PersonTable]
val query = examples.foldLeft(t){case (t,(column, value)) => t.dynamicFilter(column, value)
query.result

这里解释了代码生成器的扩展:http://slick.lightbend.com/doc/3.1.1/code-generation.html#customization

进一步研究后发现如下博客post Repository Pattern / Generic DAO Implementation.

在那里,他们声明并实现了一个适用于任何模型实体类型的通用 filter 方法,因此在我看来,它是更多 JPA findByExample.[=14= 的有效功能替代品]

T <: Table[E] with IdentifyableTable[PK]
E <: Entity[PK]
PK: BaseColumnType

def filter[C <: Rep[_]](expr: T => C)(implicit wt: CanBeQueryCondition[C]) : Query[T, E, Seq] = tableQuery.filter(expr)