2010-07-22 7 views
6

EDITAR: volvió a escribir la pregunta. La recompensa añadida es importante para mí. La última sugerencia con la que puedo hacer que FindByAttributes funcione (sin volver a implementarla en subclases) obtendrá mis puntos.Mueva la implementación de un método genérico a una superclase abstracta

En mi aplicación realizo consultas de base de datos de tipo seguro con la nueva consulta de criterios JPA2. Por lo tanto, tengo un rasgo DAO que debería ser (re) utilizable para TODAS las entidades de mi aplicación. Así que esta es la forma en el contorno del rasgo actual que estoy usando se parece (que trabaja):

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

En un DAO concreto, les extiendo este rasgo de esta manera, ajustar el tipo y la aplicación de los métodos (eliminado todos excepto el método más importante):

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

Por cierto, findByAttributes es muy agradable de usar. Ejemplo:

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

me di cuenta, que findByAttributes es tan genérico, que es la misma en todas las clases de mi aplicación que implementan la DAO. Lo único que cambia es el tipo utilizado en el método. Así, en otra clase hereda wich DAO, su

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

así que creé una nueva clase abstracta llamada SuperDAO que debe contener los métodos genéricos implementadas, por lo que no tengo que volver a aplicar en cada subclase. Después de una cierta ayuda de Landei (gracias), el (la parte más importante de mi) implementación actual de mi SuperDAO se parece a esto

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

Así que el problema actual es que la línea con queryRoot.get produce el siguiente 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]) 

Cuál es significaba $ 1 ???

si es necesario: SingularAttribute Javadoc

EDITAR @Landei:

Cambio del método de firma a

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

Y el queryRoot.get a

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

Los resultados en el (mucho más corto !) 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]) 

La solución de @Sandor Murakozi parece funcionar. Tengo que probarlo un poco. Pero también agradecería una solución más corta, ¡si es posible! (?)

+0

La advertencia terminológica habitual: Cuando se escribe 'def fBA (...): SomeType = {... } 'estás definiendo un * método *, no una función. Hay muchas formas de obtener funciones en Scala. P.ej., aplicación parcial, que se puede utilizar para elevar un método a una función correspondiente: 'def mFBA (...): ... = {...}; val fFBA = mFBA _'. –

+0

Nunca tendré la distinción entre los dos ... pero tu comentario ayudó. Reemplazó "función" por "método". – ifischer

+0

Acerca de la versión "def findByAttributesOld [A] ...": Creo que no es del todo correcto, porque la lista de atributos no es realmente homogénea: puede contener, p. Int y String atributos, por lo que A finalmente se convertirá en Any (al menos en el caso más general). Tampoco estoy seguro de si puede ayudar: si mi conjetura acerca de los problemas de tipo existencial es correcta, entonces creo que no es muy probable. –

Respuesta

2

EDIT: Añadido a lo solicitado por los comentarios @ifischer

Creo que su principal problema es que se pierde la información de tipo de valor, mediante el simple m.erasure ya que esto vuelve Class[_] en lugar de Class[T] lo que realmente quiere aquí. Hacer un yeso antes del resto te ahorrará algunas cosas desagradables.

También los comodines no vinculados utilizados en JPA 2.0 son un poco molestos ya que necesita saltar algunos aros para evitarlos.

Como no tiene mucho sentido pedir ningún atributo, saqué el primer atributo del parámetro *. Esto también significa que no necesita comenzar con conjunction.

que acorta algunos nombres para que el código se ajusta en la caja sin saltos de línea: se aplica

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

Gracias. Desafortunadamente, solo pude hacer una pequeña prueba, pero en el momento parece que funciona. Ya que es la solución más limpia, le doy los 100 puntos. – ifischer

+0

gracias de nuevo por esta solución. Todavía estoy impresionado;) ¿Podría hacerme un favor y hacer algunos comentarios y explicaciones en el código? especialmente la firma del método, campo, controles y JList. Me ayudaría mucho para una mejor comprensión. Estoy escribiendo una tesis sobre esto, así que tengo que entenderlo completamente – ifischer

+1

@ifischer gracias por aceptarlo. Agregué algunos comentarios. No dude en preguntar si todavía hay partes que no están claras. – Moritz

2

esto debería funcionar:

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

} 

[Editar]

Aaargh 1 11 !!!!!

Creo que necesita escribir findByAttributes(...), no findByAttributes[T](...), sino que T sombrea la T de la clase DAO (que es la "correcta"). No estoy seguro de que esto resuelva tu problema, pero tal como está, está mal.

[Edit1]

No he leído la API lo suficientemente cuidadoso. Creo que quieres usar this Version of get.

Así que tendríamos que proporcionar solo el segundo parámetro de tipo de SingularAttribute. El problema es que este sería el mismo que en AttributeValuePair [_]. Honestamente, no sé cómo preceder aquí. Usted podría intentar

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

o

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

Si esto no funciona, tenemos por lo menos algunos mensajes de error interesantes, lo que nos puede dar una pista :-)

+0

Gracias. Agregué una edición relacionada a mi pregunta. – ifischer

+0

Hay cuatro versiones de get en javax.persistence.criteria.Path (que es una superclase de Root), por lo que puede intentar dar al compilador una pista: ... queryRoot.get [SingularAttribute [T, _] ] (pair._1) ... – Landei

+0

¡Gracias! Pero todavía no funciona. Se agregó otra edición. – ifischer

2

éste compila sin errores. Sin embargo, yo no intento ejecutarlo, por lo que puede obtener algunas excepciones (por ejemplo, de queryRoot.asInstanceOf[Root[T]], tengo un poco mal presentimiento sobre él):

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

Por cierto, en SuperDAO.findByAttributes los paréntesis/params de cb.equal parece estar un poco confundido. Se ve bien en el otro método.

Acerca del tipo _$1: Creo que cuando diga SingularAttribute[T,_] será un tipo llamado existencial. Es una abreviatura de SingularAttribute[T,X] forSome { type X }. Entonces, el _ significa que realmente no sabemos qué es X, pero de seguro hay un tipo fijo allí. Como puede tener varios tipos existenciales, el compilador simplemente los llama _$1, _$2 y así sucesivamente. Son nombres creados sintéticamente en lugar de X -es.
Los tipos existenciales se usan principalmente cuando se usan genéricos Java con comodines o tipos crudos. En estos casos, algunos trucos (como la introducción de un método adicional con su propio tipo de param) pueden ser necesarios para una correcta verificación de tipos.

+0

Impresionante. Parece que funciona hasta ahora! Hará algunas pruebas ... – ifischer

+0

Cool. Espero que funcione. Una cosa que me vino a la mente fue que quizás podrías usar el tipo dado por m.erasure en lugar de T dentro del método (o en otro extraído de él). De esta forma, es posible que puedas evitar el feo asInstanceOf, que no es 100% correcto desde el punto de vista del tipeo, simplemente Java no se preocupa por el borrado. –

Cuestiones relacionadas