2010-07-22 3 views
6

EDIT: Rewrote die Frage. Bounty als wichtig für mich hinzugefügt. Der letzte Hinweis, mit dem ich findByAttributes zum Arbeiten bringen kann (ohne ihn in Unterklassen neu zu implementieren), wird meine Punkte bekommen.Verschieben Sie die Implementierung einer generischen Methode in eine abstrakte Superklasse

In meiner App mache ich typsichere Datenbankabfragen mit der neuen JPA2-Kriterienabfrage. Daher habe ich eine Eigenschaft DAO, die für alle Entitäten in meiner Anwendung (wiederverwendbar) sein sollte. Das ist also, wie der Umriss der aktuelle Merkmal i Aussehen bin mit wie (die Werke):

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 einem konkreten DAO, bin erstreckt ich dieses Merkmal wie diese, die Art Festlegung und Umsetzung der Methoden (alle mit Ausnahme der wichtigsten Verfahren entfernt):

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, ist findByAttributes wirklich nett zu benutzen. Beispiel:

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

Ich erkennen, dass findByAttributes ist so allgemein, dass es das gleiche in allen Klassen meiner app, die die DAO implementieren. Das einzige, was sich ändert, ist der Typ, der in der Methode verwendet wird. So in eine andere wich Klasse erbt DAO, seine

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

Also habe ich eine neue abstrakte Klasse namens SuperDAO, die die Umsetzung generische Methoden enthalten sollte, so dass ich muss sie in jeder Unterklasse nicht erneut implementieren. Nach etwas Hilfe von Landei (danke), der (wichtigste Teil meiner) aktuelle Umsetzung meiner SuperDAO sieht wie folgt aus

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 das aktuelle Problem ist, dass die Leitung mit queryRoot.get den folgenden Fehler erzeugt:

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

Was ist mit $ 1 gemeint ??

Bei Bedarf: SingularAttribute Javadoc

EDIT @Landei:

Ändern der Methodensignatur

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

Und die queryRoot.get zu

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

Ergebnisse in der (viel kürzer !) Fehler:

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

Die Lösung von @Sandor Muraközi scheint zu funktionieren. Ich muss es ein wenig testen. Aber ich würde auch eine kürzere Lösung schätzen, wenn es überhaupt möglich ist! (?)

+0

Die übliche terminologische Vorbehalt gilt: Wenn Sie schreiben 'def FBA (...): Sometype = {... } 'Sie definieren eine * Methode *, nicht eine Funktion. Es gibt viele Möglichkeiten, Funktionen in Scala zu bekommen. Z.B.Teilanwendung, die verwendet werden kann, um eine Methode auf eine entsprechende Funktion zu heben: 'def mFBA (...): ... = {...}; val fFBA = mFBA _ '. –

+0

Ich werde nie die Unterscheidung zwischen beiden richtig bekommen ... aber Ihr Kommentar geholfen. Ersetzte "Funktion" durch "Methode". – ifischer

+0

Über die "def findByAttributesOld [A] ..." Version: Ich denke, es ist nicht ganz richtig, weil die Liste der Attribute nicht wirklich homogen ist: Es kann z. Int und String-Attribute, so dass A schließlich Any wird (zumindest im allgemeinsten Fall). Ich bin mir auch nicht sicher, ob es helfen kann: Wenn meine Vermutung über die existenziellen Probleme richtig ist, dann denke ich, dass es nicht sehr wahrscheinlich ist. –

Antwort

2

EDIT: Hinzugefügt Kommentare wie von @ifischer angefordert

denke ich, Ihr Hauptproblem ist, dass man nur durch Passieren m.erasure wertvolle Typinformationen zu verlieren, da dies Class[_] kehrt statt Class[T], was Sie eigentlich hier wollen. Einen Cast vor dem Rest zu machen, wird dir einige böse Sachen ersparen.

Auch die ungebundenen Wildcards, die in JPA 2.0 verwendet werden, sind ein bisschen nervig, da man einige Ringe springen muss, um sie zu umgehen.

Da es nicht sinnvoll ist, nach keinen Attributen zu suchen, habe ich das erste Attribut aus dem * -Parameter gezogen. Dies bedeutet auch, dass Sie nicht mit conjunction beginnen müssen.

ich einige Namen verkürzt, so dass der Code ohne Zeilenumbrüche in die Box passt:

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

Danke. Leider konnte ich nur einen kleinen Test machen, aber im Moment scheint es so zu sein. Da es die sauberste Lösung ist, gebe ich dir die 100 Punkte. – ifischer

+0

nochmals vielen Dank für diese Lösung. Ich bin immer noch beeindruckt;) Könntest du mir einen Gefallen tun und einige Kommentare und Erklärungen in den Code schreiben? insbesondere die Methodensignatur, Field, checks und JList. Würde mir sehr helfen für ein besseres Verständnis. Ich schreibe eine Diplomarbeit über dieses Zeug, also muss ich es komplett verstehen – ifischer

+1

@ifischer danke für die Annahme. Ich habe einige Kommentare hinzugefügt. Fühlen Sie sich frei zu fragen, ob es noch Teile gibt, die unklar sind. – Moritz

2

Dies sollte funktionieren:

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

} 

[Bearbeiten]

Aaargh 1 11 !!!!!

Ich denke, Sie müssen schreiben findByAttributes(...), nicht findByAttributes[T](...), sonst, dass T Schatten der T der DAO-Klasse (das ist die "richtige"). Ich bin mir nicht sicher, ob das dein Problem löst, aber so wie es ist, ist es falsch.

[Edit1]

ich nicht die API vorsichtig genug gelesen. Ich denke, dass Sie this Version of get verwenden möchten.

Also müssten wir nur den zweiten Typparameter des SingularAttribute bereitstellen. Das Problem ist, dass dies dasselbe wie das in AttributeValuePair [_] wäre. Ich weiß ehrlich nicht, wie ich hier vorgehen soll. Sie könnten

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

oder

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

versuchen Wenn dies nicht funktioniert, wir zumindest einige interessante Fehlermeldungen, die uns einen Hinweis geben kann :-)

+0

Danke. Eine zugehörige Änderung zu meiner Frage hinzugefügt. – ifischer

+0

Es gibt vier Versionen erhalten in javax.persistence.criteria.Path (das ist eine übergeordnete Klasse von Root ist), so dass Sie den Compiler einen Hinweis zu geben versuchen: ... queryRoot.get [SingularAttribute [T, _] ] (pair._1) ... – Landei

+0

Danke! Aber immer noch nicht funktioniert. Eine weitere Bearbeitung hinzugefügt. – ifischer

2

diesem einen compiliert ohne Fehler. Allerdings habe ich nicht versuchen, es zu laufen, so können Sie einige Ausnahmen erhalten (zB von queryRoot.asInstanceOf[Root[T]], ich habe ein wenig schlechtes Gefühl dabei):

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 die Klammern/params von cb.equal scheint ein bisschen durcheinander sein. Es sieht in der anderen Methode OK aus.

Über die _$1 Art: Ich denke, wenn Sie SingularAttribute[T,_] sagen, wird es ein sogenannter existentieller Typ sein. Es ist eine Kurzschrift für SingularAttribute[T,X] forSome { type X }. Also die _ bedeutet, dass wir nicht wirklich wissen, was X ist, aber sicher gibt es dort einen festen Typ. Da Sie mehrere existentielle Typen haben können, ruft der Compiler sie einfach _$1, _$2 und so weiter. Sie sind synthetisch erzeugte Namen anstelle von X -e.
Existentialtypen werden hauptsächlich verwendet, wenn Sie Java-Generika mit Platzhalter- oder Raw-Typen verwenden. In diesen Fällen können einige Tricks (wie die Einführung einer zusätzlichen Methode mit einem eigenen Typ-Parameter) für eine ordnungsgemäße Typprüfung erforderlich sein.

+0

Beeindruckend. Scheint so weit zu arbeiten! Will ein paar Tests machen ... – ifischer

+0

Cool. Ich hoffe, dass es funktionieren wird. Eine Sache, die mir in den Sinn kam, war, dass Sie vielleicht den Typ verwenden könnten, der von m.arasure anstelle von T innerhalb der Methode (oder in einer anderen, die daraus extrahiert wurde) stammt. Auf diese Weise können Sie das hässliche asInstanceOf vermeiden, das vom Typisierungspunkt nicht 100% korrekt ist, nur Java interessiert es wahrscheinlich nicht wegen des Löschens. –

Verwandte Themen