Переместите реализацию универсального метода в абстрактный суперкласс - PullRequest
6 голосов
/ 22 июля 2010

РЕДАКТИРОВАТЬ: переписал вопрос. Добавлена ​​награда, так как это важно для меня. Последний совет, с которым я могу заставить работать 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]]
  }
}

Кстати, findByAttributes действительно хорош в использовании. Пример:

val userList = userEJB.findByAttributes(
  User_.title -> Title.MR, 
  User_.email -> "email@test.com"
)

Я понял, что 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 похоже работает. Придется немного это проверить. Но я также был бы признателен за более короткое решение, если оно вообще возможно!

Ответы [ 3 ]

2 голосов
/ 28 июля 2010

Этот компилируется без ошибок.Однако я не пытался запустить его, поэтому вы можете получить некоторые исключения (например, из 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 в SuperDAO.findByAttributes скобках/ params of cb.equal кажется немного запутанным.В другом методе все выглядит нормально.

О типе _$1: думаю, когда вы скажете SingularAttribute[T,_], это будет так называемый экзистенциальный тип.Это сокращение для SingularAttribute[T,X] forSome { type X }.Таким образом, _ означает, что мы на самом деле не знаем, что такое X, но наверняка есть фиксированный тип.Поскольку у вас может быть несколько экзистенциальных типов, компилятор просто вызывает их _$1, _$2 и так далее.Они являются синтетически созданными именами вместо X -es.
Экзистенциальные типы используются в основном, когда вы используете универсальные Java-символы с подстановочными или необработанными типами.В этих случаях для правильной проверки типа могут потребоваться некоторые приемы (например, введение дополнительного метода с собственным параметром типа).

2 голосов
/ 30 июля 2010

РЕДАКТИРОВАТЬ: Добавлены комментарии по запросу @ 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
  }
}
2 голосов
/ 22 июля 2010

Это должно (?) Работать:

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 !!!! * * 1006

Я думаю, вам нужно написать findByAttributes(...), а не findByAttributes[T](...), иначе T затеняет T класса DAO (который является "правильным"). Я не уверен, что это решит вашу проблему, но на самом деле это неправильно.

[Edit1]

Я недостаточно внимательно прочитал API. Я думаю, что вы хотите использовать эту версию get .

Таким образом, мы должны предоставить только второй тип параметра SingularAttribute. Проблема в том, что это будет то же самое, что и в AttributeValuePair [_]. Я, честно говоря, не знаю, как здесь предшествовать. Вы можете попробовать

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

или

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

Если это не сработает, мы получим по крайней мере несколько интересных сообщений об ошибках, которые могут дать нам подсказку: -)

...