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

时间:2010-07-22 11:04:21

标签: 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] 
}

在一个具体的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]]
  }
}

BTW,findByAttributes真的很好用。例如:

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

我意识到,findByAttributes是如此通用,在我实现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]]
}

所以我创建了一个名为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]]
  }

所以当前的问题是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])

什么意思是$ 1 ???

如果需要:SingularAttribute Javadoc

EDIT @Landei:

将方法签名更改为

def findByAttributesOld[A](attributes:AttributeValuePair[A]*):ArrayList[T] = {

并且queryRoot.get为

queryRoot.get[A](pair._1.asInstanceOf[SingularAttribute[T,A]])

导致(更短!)错误:

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

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

3 个答案:

答案 0 :(得分:2)

这应该(?)工作:

abstract class DAO[T, K <: Serializable](implicit m: Manifest[T]) {
 ...

def findByAttributes[T](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(queryRoot.get(pair._1), pair._2 ))
  cq.where(Seq(criteria):_*)
  val results = em.createQuery(cq).getResultList
  results.asInstanceOf[ArrayList[T]]
}

}

[编辑]

Aaargh!1!11 !!!!

我认为你需要写findByAttributes(...),而不是findByAttributes[T](...),否则T会影响DAO类的T(这是“正确的”)。我不确定这会解决你的问题,但实际上,这是错误的。

[EDIT1]

我没有仔细阅读API。我想你想用this Version of get

因此我们必须只提供SingularAttribute的第二个类型参数。问题是这与AttributeValuePair [_]中的相同。老实说,我不知道如何在这里。你可以试试

def findByAttributes[A](attributes:AttributeValuePair[A]*):ArrayList[T] = {...

queryRoot.get[A](pair._1.asInstanceOf[SingularAttribute[T,A]])

如果这不起作用,我们至少会得到一些有趣的错误消息,这可能会给我们一个提示: - )

答案 1 :(得分:2)

这个编译没有错误。但是,我没有尝试运行它,因此您可能会遇到一些例外情况(例如来自queryRoot.asInstanceOf[Root[T]],我对它有一点不好的感觉):

  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 = pred(pair, cb, queryRoot.asInstanceOf[Root[T]])
      }
    cq.where(Seq(criteria):_*)
    val results = em.createQuery(cq).getResultList
    results.asInstanceOf[ArrayList[T]]
  }


  def pred[A](pair: AttributeValuePair[A], 
      cb: CriteriaBuilder, 
      queryRoot: Root[T]): Predicate = 
    cb.and(cb.equal(queryRoot.get(pair._1),pair._2))

BT SuperDAO.findByAttributes cb.equal的括号/参数似乎有些混乱。在其他方法中它看起来不错。

关于_$1类型:我想当你说SingularAttribute[T,_]它将是一个所谓的存在类型。它是SingularAttribute[T,X] forSome { type X }的简写。所以_意味着我们并不真正知道X是什么,但肯定有一个固定的类型。由于您可以使用多种存在类型,因此编译器只需将它们称为_$1_$2等等。它们是合成创建的名称,而不是X - es。
当您将Java泛型与通配符或原始类型一起使用时,主要使用存在类型。在这些情况下,可能需要一些技巧(比如引入一个额外的方法,使用它自己的类型参数)进行正确的类型检查。

答案 2 :(得分:2)

编辑:根据@ifischer

的要求添加了评论

我认为您的主要问题是,只需传递m.erasure就会丢失有价值的类型信息,因为这会返回Class[_]而不是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
  }
}