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

发布于 2024-09-10 18:40:27 字数 4760 浏览 0 评论 0原文

编辑:重写问题。添加赏金,因为它对我来说很重要。我可以让 findByAttributes 工作的最后一个提示(无需在子类中重新实现它)将得到我的观点。

在我的应用程序中,我正在使用新的 JPA2 Criteria 查询进行类型安全数据库查询。因此,我有一个特征 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 protected]"
)

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

EDIT: Rewrote the question. Added bounty as its important for me. The final hint with which i can get findByAttributes to work (without reimplementing it in subclasses) will get my points.

In my app i'm doing typesafe database queries with the new JPA2 Criteria Query. Therefore I have a trait DAO which should be (re)usable for ALL entities in my application.
So this is how the outline the current trait i'm using looks like (which works):

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

In a concrete DAO, i'm extending this trait like this, setting the type and implementing the methods (removed all except the most important method):

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 is really nice to use. Example:

val userList = userEJB.findByAttributes(
  User_.title -> Title.MR, 
  User_.email -> "[email protected]"
)

I realized, that findByAttributes is so generic, that its the same in ALL classes of my app that implement the DAO. The only thing that changes is the Type used in the method. So in another class wich inherits DAO, its

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

So i created a new abstract class named SuperDAO that should contain the implemented generic methods, so that i don't have to re-implement them in every subclass.
After some help from Landei (thanks), the (most important part of my) current implementation of my SuperDAO looks like this

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

So the current problem is that the line with queryRoot.get produces the following error:

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

Whats meant with $1???

If needed: SingularAttribute Javadoc

EDIT @Landei:

Changing the method signature to

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

And the queryRoot.get to

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

Results in the (much shorter!) error:

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

The solution of @Sandor Murakozi seems to work. Have to test it a bit. But i would also appreciate a shorter solution, if its possible at all!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

黑白记忆 2024-09-17 18:40:27

编辑:按照@ifischer的要求添加了注释

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

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

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

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

// 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
  }
}

EDIT: Added comments as requested by @ifischer

I think your main problem is that you lose valuable type information by just passing m.erasure as this returns Class[_] instead of Class[T] what you actually want here. Doing a cast before the rest will save you some nasty stuff.

Also the unbound wildcards used in JPA 2.0 are a bit annoying as you need to jump some hoops to get around them.

As it does not make much sense to query for no attributes I pulled the first attribute out of the *-parameter. This also means that you do not need to start with conjunction.

I shortened some names so that the code fits in the box without line breaks:

// 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
  }
}
她比我温柔 2024-09-17 18:40:27

这应该(?)工作:

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。我想你想使用 此版本的 get

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

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

或者

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

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

This should (?) work:

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

}

[Edit]

Aaargh!1!11!!!!

I think you need to write findByAttributes(...), not findByAttributes[T](...), else that T shadows the T of the DAO class (which is the "right one"). I'm not sure that this solves your problem, but as it is, it is wrong.

[Edit1]

I didn't read the API careful enough. I think you want to use this Version of get.

So we would have to provide only the second type parameter of the SingularAttribute. The problem is that this would be the same as the one in AttributeValuePair[_]. I honestly don't know how to preceed here. You could try

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

or

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

If this doesn't work, we get at least some interesting error messages, which may give us a hint :-)

苏大泽ㄣ 2024-09-17 18:40:27

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

BTW in < code>SuperDAO.findByAttributes cb.equal 的括号/参数似乎有点混淆。在其他方法中看起来没问题。

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

This one compiles without errors. However, I did not try to run it, so you may get some exceptions (e.g. from queryRoot.asInstanceOf[Root[T]], I have a little bit bad feeling about it):

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

BTW in SuperDAO.findByAttributes the parentheses/params of cb.equal seems to be a bit mixed up. It looks OK in the other method.

About the _$1 type: I think when you say SingularAttribute[T,_] it will be a so called existential type. It is a shorthand for SingularAttribute[T,X] forSome { type X }. So the _ means that we don't really know what X is, but for sure there is a fixed type there. As you can have several existential types the compiler just calls them _$1, _$2 and so on. They are synthetically created names instead of X-es.
Existential types are used mostly when you use Java generics with wildcard or raw types. In these cases some tricks (like introducing an extra method with it's own type param) may be needed for proper type checking.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文