2010-07-22 6 views
6

편집 : 질문을 다시 작성하십시오. 나를 위해 중요성을 알린 바운티를 추가했습니다. findByAttributes를 (서브 클래스에서 다시 구현하지 않고) 얻을 수있는 최종 힌트는 내 포인트를 얻을 것이다.제네릭 메서드 구현을 추상 수퍼 클래스로 이동

내 응용 프로그램에서는 새로운 JPA2 기준 쿼리를 사용하여 typesafe 데이터베이스 쿼리를 수행하고 있습니다. 따라서 필자는 응용 프로그램의 모든 엔티티에서 사용할 수 있어야하는 특성 DAO를 가지고 있습니다. 그래서이 방법 (작동) 같은 내가 외모를 사용하고 현재 특징 개요 : 콘크리트의 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] 
} 

, 난이 같은이 특성을 확장하고있어, 방법을 유형을 설정하고 실행 (가장 중요한 방법을 제외하고 모두 제거됨) :

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 :

가에서

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

결과에

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

그리고 queryRoot.get에 메소드 서명 변경 (훨씬 짧음 !) 오류 :

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

일반적인 용어주의가 적용됩니다 : 코드 줄 바꿈없이 상자에 맞도록

내가 몇 가지 이름을 단축 당신이 쓸 때'데프 FBA (...) : SomeType = {... }'함수가 아닌 * 메소드 *를 정의하고 있습니다. Scala에서 함수를 가져 오는 데는 여러 가지 방법이 있습니다. 예 :, 부분적인 어플리케이션으로, 메소드를 상응하는 함수로 옮기는 데 사용할 수 있습니다 :'def mFBA (...) : ... = {...}; val fFBA = mFBA _'입니다. –

+0

나는 두 권리 사이의 구별을 결코 얻지 못할 것이다. 그러나 당신의 의견은 도움이된다. "function"을 "method"로 대체했습니다. – ifischer

+0

"def findByAttributesOld [A] ..."버전 정보 : 속성 목록이 실제로 균질하지 않기 때문에 매우 정확하지 않다고 생각합니다. Int 및 String 특성이므로 A는 결국 Any가됩니다 (적어도 가장 일반적인 경우). 또한 도움이 될지 확신 할 수 없습니다. 실존 유형 문제에 대한 제 추측이 맞다면 그렇게 생각하지 않습니다. –

답변

2

편집 : @ifischer

의 요청에 따라 추가 코멘트 나는 당신의 가장 큰 문제는 이것이 당신이 실제로 여기에 원하는 Class[_] 대신 Class[T] 반환 당신이 단지 m.erasure을 전달하여 가치있는 유형 정보를 잃게 생각합니다. 나머지를하기 전에 캐스트를하면 불쾌한 것들을 줄일 수 있습니다.

또한 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 
    } 
} 
+0

감사합니다. 불행히도, 나는 단지 작은 테스트 만 할 수 있었지만, 현재는 작동하는 것처럼 보입니다. 가장 깨끗한 솔루션이므로 100 점을주지 마십시오. – ifischer

+0

이 솔루션에 다시 한 번 감사드립니다. 나는 여전히 인상 깊었습니다.) 당신은 저에게 은혜를 베풀고 코드에 대한 설명과 설명을 해줄 수 있습니까? 특히 메소드 서명, Field, checks 및 JList. 더 나은 이해를 위해 많은 도움이 될 것입니다. 나는이 물건에 대한 논문을 쓰고있어, 그래서 그것을 완전히 이해해야한다. – ifischer

+1

@ 그것을 받아 주셔서 감사합니다. 몇 가지 의견을 추가했습니다. 불분명 한 부분이 아직도 있는지 물어보십시오. – Moritz

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 [_]의 것과 동일하다는 것입니다. 나는 정직하게 여기에 선행하는 법을 모른다. 문제가 해결되지 않으면 당신은 우리가

+0

감사합니다. 내 질문에 관련 수정 사항을 추가했습니다. – ifischer

+0

javax.persistence.criteria.Path (루트의 수퍼 클래스)에 get의 네 가지 버전이 있으므로 컴파일러에 힌트를 제공 할 수 있습니다. ... queryRoot.get [SingularAttribute [T, _]] ] (pair._1) ... – Landei

+0

고마워요! 그러나 여전히 작동하지 않습니다. 다른 편집을 추가했습니다. – ifischer

2

이 하나의 컴파일 :-) 우리에게 힌트를 줄 수있는, 적어도 몇 가지 흥미로운 오류 메시지를 얻을

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

또는

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

을 시도 할 수 오류없이. 그러나, 나는 그것을 실행하려고하지 않았다, 그래서 당신은 몇 가지 예외를받을 수 있습니다 (queryRoot.asInstanceOf[Root[T]]에서 예를 들어, 내가 조금 나쁜 그것에 대해 느낌이) : SuperDAO.findByAttributes에 BTW

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

cb.equal의 괄호 /의 PARAMS이 보인다 조금 뒤죽박죽이다. 다른 방법에서도 괜찮아 보입니다.

_$1 유형에 대해 : 당신이 SingularAttribute[T,_]라고 말하면 소위 실존 적 유형이라고 생각합니다. SingularAttribute[T,X] forSome { type X }의 약어입니다. 따라서 _은 X가 무엇인지는 알지 못한다는 것을 의미합니다. 그러나 거기에는 고정 된 유형이 있습니다. 실존 타입을 여러 개 가질 수 있기 때문에 컴파일러는 _$1, _$2 등등을 호출합니다. 그들은 X-es 대신 합성 적으로 만들어진 이름입니다.
존재 유형은 주로 와일드 카드 또는 원시 유형의 Java 제네릭을 사용할 때 사용됩니다. 이러한 경우에는 적절한 형식 검사를 위해 (자체 메서드 매개 변수를 사용하여 추가 메서드를 도입하는 것과 같은) 몇 가지 트릭이 필요할 수 있습니다.

+0

인상적. 지금까지 일하는 것 같습니다! 몇 가지 테스트를 해 ... – ifischer

+0

차가운. 나는 그것이 효과가 있기를 바랍니다. 한 가지 생각은 T의 대신에 m.erasure가 제공 한 타입을 사용할 수 있다는 것입니다. 이 방법을 사용하면 입력 관점에서 100 % 정확하지 않은 추악한 asInstanceOf를 피할 수 있습니다. Java는 지우는 이유로 자바에 관심이 없을 것입니다. –

관련 문제