代码之家  ›  专栏  ›  技术社区  ›  Wolkenarchitekt

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

  •  6
  • Wolkenarchitekt  · 技术社区  · 14 年前

    编辑:重写问题。对我来说更重要的是增加赏金。最后一个提示可以让findbyattributes工作(不需要在子类中重新实现它),这将得到我的观点。

    在我的应用程序中,我使用新的JPA2条件查询执行类型安全数据库查询。因此,我有一个特性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的所有类中都是一样的。唯一改变的是方法中使用的类型。所以在另一个类中,wich继承了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的帮助下(谢谢),我的超级道当前的(最重要的部分)实现如下

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

    解决方案 @三多村口 似乎起作用了。必须测试一下。不过,如果可能的话,我也希望有一个更短的解决方案!

    3 回复  |  直到 14 年前
        1
  •  2
  •   Moritz    14 年前

    编辑:根据@ifischer的要求添加注释

    我认为你的主要问题是你只是通过传递而丢失了有价值的类型信息 m.erasure 因为这个回报 Class[_] 而不是 Class[T] 你到底想要什么。在剩下的之前做一个演员会帮你省下一些讨厌的东西。

    另外,JPA2.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
      }
    }
    
        2
  •  2
  •   Landei    14 年前

    这应该(?)工作:

    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 .

    所以我们只需要提供singularat属性的第二个类型参数。问题是,这将与attributeValuePair[uux]中的相同。我真的不知道怎么在这里说教。你可以试试

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

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

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

        3
  •  2
  •   Sandor Murakozi    14 年前

    这个编译过程没有错误。但是,我没有尝试运行它,因此您可能会收到一些异常(例如 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泛型时,存在类型主要使用。在这些情况下,可能需要一些技巧(例如使用自己的类型参数引入一个额外的方法)来进行正确的类型检查。