将泛型方法的实现移动到抽象超类

Ing*_*her 6 generics scala scala-2.8

编辑:重写了这个问题.添加赏金对我来说很重要.我可以使findByAttributes工作的最终提示(无需在子类中重新实现)将得到我的观点.

在我的应用程序中,我正在使用新的JPA2 Criteria Query进行类型安全的数据库查询.因此,我有一个特性DAO,它应该(重新)可用于我的应用程序中的所有实体.所以这就是我正在使用的当前特征的轮廓如何(有效):

trait DAO[T, K](implicit m: Manifest[T]) {
  @PersistenceContext 
  var em:EntityManager = _

  lazy val cb:CriteriaBuilder = em.getCriteriaBuilder

  def persist(entity: T)
  def update(entity: T)
  def remove(entity: T)
  def findAll(): ArrayList[T]

  // Pair of SingularAttribute and corresponding value
  // (used for queries for multiple attributes)
  type AttributeValuePair[A] = Pair[SingularAttribute[T, A], A]

  // Query for entities where given attribute has given value
  def findByAttribute[A](attribute:AttributeValuePair[A]):ArrayList[T]

  // Query for entities with multiple attributes (like query by example)
  def findByAttributes[A](attributes:AttributeValuePair[_]*):ArrayList[T] 
}
Run Code Online (Sandbox Code Playgroud)

在一个具体的DAO中,我正在扩展这个特性,设置类型和实现方法(删除除了最重要的方法之外的所有方法):

class UserDAO extends DAO[User, Long] {
  override type AttributeValuePair[T] = Pair[SingularAttribute[User, T], T]

  override def findByAttributes[T](attributes:AttributeValuePair[_]*):ArrayList[User] = {
    val cq = cb.createQuery(classOf[User])
    val queryRoot = cq.from(classOf[User])
    var criteria = cb.conjunction
    for (pair <- attributes) 
      criteria = cb.and(cb.equal(queryRoot.get(pair._1), pair._2 ))
    cq.where(Seq(criteria):_*)
    val results = em.createQuery(cq).getResultList
    results.asInstanceOf[ArrayList[User]]
  }
}
Run Code Online (Sandbox Code Playgroud)

顺便说一句,findByAttributes真的很好用.例:

val userList = userEJB.findByAttributes(
  User_.title -> Title.MR, 
  User_.email -> "email@test.com"
)
Run Code Online (Sandbox Code Playgroud)

我意识到,这findByAttributes是非常通用的,它在我的app的所有类中实现DAO都是一样的.唯一改变的是方法中使用的Type.所以在另一个类中继承DAO,它的

def findByAttributes[T](attributes:AttributeValuePair[_]*):ArrayList[Message] = {
  val cq = cb.createQuery(classOf[Message])
  val queryRoot = cq.from(classOf[Message])
  var criteria = cb.conjunction
  for (pair <- attributes) 
    criteria = cb.and(cb.equal(queryRoot.get(pair._1), pair._2 ))
  cq.where(Seq(criteria):_*)
  val results = em.createQuery(cq).getResultList
  results.asInstanceOf[ArrayList[User]]
}
Run Code Online (Sandbox Code Playgroud)

所以我创建了一个名为SuperDAO的新抽象类,它应该包含已实现的泛型方法,这样我就不必在每个子类中重新实现它们.在Landei的一些帮助下(谢谢),我当前实施的SuperDAO(我最重要的部分)就是这样的

abstract class SuperDAO[T, K](implicit m: Manifest[T]) {
  @PersistenceContext
  var em:EntityManager = _

  lazy val cb:CriteriaBuilder = em.getCriteriaBuilder

  type AttributeValuePair[A] = Pair[SingularAttribute[T, A], A]

  def findByAttributes(attributes:AttributeValuePair[_]*):ArrayList[T] = {
    val cq = cb.createQuery(m.erasure)
    val queryRoot = cq.from(m.erasure)
    var criteria = cb.conjunction
      for (pair <- attributes) { 
        criteria = cb.and(
          cb.equal(
            // gives compiler error
            queryRoot.get[SingularAttribute[T,_]](pair._1)
          )
          ,pair._2
        )
      }
    cq.where(Seq(criteria):_*)
    val results = em.createQuery(cq).getResultList
    results.asInstanceOf[ArrayList[T]]
  }
Run Code Online (Sandbox Code Playgroud)

所以当前的问题是queryRoot.get行产生以下错误:

overloaded method value get with alternatives:   
(java.lang.String)javax.persistence.criteria.Path
[javax.persistence.metamodel.SingularAttribute[T, _]] <and>
(javax.persistence.metamodel.SingularAttribute[_ >: Any, 
javax.persistence.metamodel.SingularAttribute[T,_]])
javax.persistence.criteria.Path
[javax.persistence.metamodel.SingularAttribute[T, _]]  
cannot be applied to 
(javax.persistence.metamodel.SingularAttribute[T,_$1])
Run Code Online (Sandbox Code Playgroud)

这意味着1美元???

如果需要:SingularAttribute Javadoc

编辑@Landei:

将方法签名更改为

def findByAttributesOld[A](attributes:AttributeValuePair[A]*):ArrayList[T] = {
Run Code Online (Sandbox Code Playgroud)

而queryRoot.get为

queryRoot.get[A](pair._1.asInstanceOf[SingularAttribute[T,A]])
Run Code Online (Sandbox Code Playgroud)

结果(更短!)错误:

overloaded method value get with alternatives:  
(java.lang.String)javax.persistence.criteria.Path[A] <and>   
(javax.persistence.metamodel.SingularAttribute[_ >: Any,     A])
javax.persistence.criteria.Path[A]  cannot be applied to 
(javax.persistence.metamodel.SingularAttribute[T,A])
Run Code Online (Sandbox Code Playgroud)

@Sandor Murakozi的解决方案似乎有效.必须测试一下.但如果可能的话,我也会感谢更短的解决方案!

Mor*_*itz 2

编辑:根据@ifischer的要求添加评论

我认为你的主要问题是,你会因为返回而丢失有价值的类型信息,m.erasureClass[_]不是Class[T]你在这里真正想要的信息。在进行其他操作之前先进行转换可以避免一些令人讨厌的事情。

此外,JPA 2.0 中使用的未绑定通配符有点烦人,因为您需要跳一些圈子才能绕过它们。

由于查询没有属性没有多大意义,因此我从*- 参数中提取了第一个属性。这也意味着您不需要从 开始conjunction

我缩短了一些名称,以便代码可以无换行地放入框中:

// import java.util.list as JList, so it does not shadow scala.List
import java.util.{List => JList}

abstract class SuperDAO[T <: AnyRef, K](implicit m: Manifest[T]) {

  @PersistenceContext
  var em: EntityManager = _

  // pretend that we have more type info than we have in the Class object.
  // it is (almost) safe to cast the erasure to Class[T] here
  def entityClass = m.erasure.asInstanceOf[Class[T]]

  lazy val cb: CriteriaBuilder = em.getCriteriaBuilder

  // Type alias for SingularAttributes accepted for this DAOs entity classes
  // the metamodel will only ever provide you with Attributes of the form
  // SingularAttribute<? super X,E>, where X is the entity type (as your
  // entity class may extend from another) and E is the element type.
  // We would actually like to use a contravariant definition of the first
  // type parameter here, but as Java has no notion of that in the definition
  // side, we have to use an existential type to express the contravariance
  // similar to the way it would be done in Java.
  type Field[A] = (SingularAttribute[_ >: T,A],A)

  // As we need at least one attribute to query for, pull the first argument out
  // of the varargs.
  def findByAttributes(attribute: Field[_], attributes: Field[_]*): JList[T] = {
    val cq = cb.createQuery(entityClass)
    val root = cq.from(entityClass)

    // shorthand for creating an equal predicate as we need
    // that multiple times below
    def equal(a: Field[_]) = cb.equal(root.get(a._1), a._2)

    // the Seq of Predicates to query for:
    def checks = Seq(
      // if there is only one argument we just query for one equal Predicate
      if (attributes.isEmpty) equal(attribute)

      // if there are more, map the varargs to equal-Predicates and prepend
      // the first Predicate to them. then wrap all of them in an and-Predicate
      else cb.and(equal(attribute) +: attributes.map(equal) : _*)
    )

    // as we already casted the entityClass we do not need to cast here
    em.createQuery(cq.where(checks : _*)).getResultList
  }
}
Run Code Online (Sandbox Code Playgroud)