【问题标题】:Move the implementation of a generic method to an abstract super class将泛型方法的实现移动到抽象超类
【发布时间】:2011-03-19 11:56:24
【问题描述】:

编辑:重写问题。添加赏金对我很重要。我可以让 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]]
  }
}

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

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

我意识到,findByAttributes 是如此通用,以至于它在我的应用程序的所有实现 DAO 的类中都是相同的。唯一改变的是方法中使用的类型。所以在另一个继承 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

编辑@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 的解决方案似乎有效。必须测试一下。但如果可能的话,我也希望有一个更短的解决方案!

【问题讨论】:

  • 通常的术语警告适用:当您编写def fBA(...): SomeType = { ... } 时,您定义的是一个方法,而不是一个函数。在 Scala 中有很多方法可以获取函数。例如,部分应用,可用于将方法提升到相应的函数:def mFBA(...): ... = { ... }; val fFBA = mFBA _.
  • 我永远无法区分两者之间的区别...但是您的评论有所帮助。将“函数”替换为“方法”。
  • 关于“def findByAttributesOld[A]...”版本:我认为这不太正确,因为属性列表并不是真正同质的:它可以包含例如Int 和 String 属性,所以 A 最终会变成 Any(至少在最一般的情况下)。我也不确定它是否有帮助:如果我对存在类型问题的猜测是正确的,那么我认为这不太可能。

标签: generics scala scala-2.8


【解决方案1】:

编辑:根据@ifischer 的要求添加了 cmets

我认为您的主要问题是仅传递 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
  }
}

【讨论】:

  • 谢谢。不幸的是,我只能做一个小测试,但目前它似乎有效。因为它是最干净的解决方案,所以我给你 100 分。
  • 再次感谢这个解决方案。我仍然印象深刻;)你能帮我一个忙,在代码中做一些 cmets 和解释吗?尤其是方法签名、字段、检查和 JList。会帮助我更好地理解。我正在写一篇关于这些东西的论文,所以我必须完全理解它
  • @ifischer 感谢您接受它。我添加了一些cmets。如果还有不清楚的部分,请随时询问。
  • 您的意思是写“...您丢失了有价值的类型信息...”而不是“...您使用了有价值的类型信息...”?
【解决方案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]]
}

}

[编辑]

啊啊!1!11!!!!

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

[编辑1]

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

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

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

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

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

【讨论】:

  • javax.persistence.criteria.Path(它是Root的超类)中有四个版本的get,所以可以尝试给编译器一个提示: ... queryRoot.get[SingularAttribute [T,_]](pair._1)...
【解决方案3】:

这个编译没有错误。但是,我没有尝试运行它,所以您可能会遇到一些异常(例如来自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))

顺便说一句,SuperDAO.findByAttributes 中的 cb.equal 的括号/参数似乎有点混乱。在其他方法中看起来没问题。

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

【讨论】:

  • 酷。我希望它会奏效。我想到的一件事是,也许您可​​以在方法内部使用 m.erasure 给出的类型而不是 T (或从中提取的另一个类型)。这样你也许可以避免丑陋的 asInstanceOf,从打字的角度来看,它不是 100% 正确的,只是 Java 可能因为擦除而不关心它。
猜你喜欢
  • 2015-03-08
  • 2012-12-10
  • 1970-01-01
  • 1970-01-01
  • 2018-10-29
  • 2020-05-14
  • 1970-01-01
  • 2012-03-01
  • 1970-01-01
相关资源
最近更新 更多