编辑:重写了这个问题。添加赏金对我来说很重要。我可以使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 的解决方案似乎有效。必须测试一下。但如果可能的话,我也会感谢更短的解决方案!
答案 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
}
}