2015-12-13 8 views
11

Ich möchte eine Abfrage ausführen, die einer bestimmten Unterklasseneigenschaft entspricht, also versuche ich treat() zu verwenden.JPA-Kriterien API-Abfrage-Unterklasseneigenschaft

In diesem Beispiel möchte ich:

alle Probanden mit dem Namen mit 'a' beginnen,
oder alle Themen, die Personen sind, mit Nachnamen beginnend mit 'a'

private List<Subject> q1() 
{ 
    CriteriaBuilder b = em.getCriteriaBuilder(); 

    CriteriaQuery<Subject> q = b.createQuery(Subject.class); 
    Root<Subject> r = q.from(Subject.class); 
    q.select(r); 
    q.distinct(true); 
    q.where(
     b.or(
      b.like(r.get(Subject_.name), "a%"), 
      b.like(b.treat(r, Person.class).get(Person_.lastName), "a%"))); 

    return em.createQuery(q).getResultList(); 
} 

Offensichtlich Person erstreckt Subject, Subject abstrakt ist, Vererbung ist SINGLE_TABLE, und Subject@DiscriminatorOptions(force = true) für andere reas hat ons (nicht einfließend).

Aber die generierten SQL ist dies:

select distinct subject0_.ID as ID2_71_, subject0_.CODE as CODE3_71_, ... 
from SUBJECT subject0_ 
where subject0_.DTYPE='Person' and (subject0_.name like 'a%' or subject0_.lastName like 'a%') 

während ich erwarte:

select distinct subject0_.ID as ID2_71_, subject0_.CODE as CODE3_71_, ... 
from SUBJECT subject0_ 
where subject0_.name like 'a%' or (subject0_.DTYPE='Person' and subject0_.lastName like 'a%') 

Gibt es eine Möglichkeit, die erwartete Abfrage mit Kriterien Builder zu produzieren?

Beachten Sie, dass

  • unter Verwendung eines anderen Wurzel-q.from(Person.class)
  • Unterabfragen - q.subquery(Person.class)
  • nachName Feld bis zu Gegenstand
  • mit Native Queries bewegen
  • Verwendung Entity Graphs

sind nicht akzeptabel.

Ich bin in etwas interessiert, die angegeben werden können und direkt in WHERE Klausel (produziert nur von CriteriaBuilder und/oder die einzelnen Wurzel, genau wie die treat() Klausel) verwendet wird, wenn es tut existieren.

Antwort

6

Die Lösung ist, mit Hibernate und in Dieses spezifische Szenario, sehr einfach:

private List<Subject> q1() 
{ 
    CriteriaBuilder b = em.getCriteriaBuilder(); 

    CriteriaQuery<Subject> q = b.createQuery(Subject.class); 
    Root<Subject> r = q.from(Subject.class); 
    q.select(r); 
    q.distinct(true); 
    q.where(
     b.or(
      b.like(r.get(Subject_.name), "a%"), 
      b.and(
       b.equal(r.type(), Person.class), 
       b.like(((Root<Person>) (Root<?>) r).get(Person_.lastName), "a%")))); 

    return em.createQuery(q).getResultList(); 
} 

Beachten Sie die Doppelbesetzung, die Kompilierungsfehler vermeidet und die Abfrageklausel für die gleiche Tabelle ausführen kann. Dies erzeugt:

select distinct subject0_.ID as ID2_71_, subject0_.CODE as CODE3_71_, ... 
from SUBJECT subject0_ 
where subject0_.DTYPE in ('Office', 'Team', 'Role', 'Person', ...) 
    and (subject0_.name like 'a%' 
     or subject0_.DTYPE='Person' and (subject0_.lastName like 'a%')) 

Es gibt keine Notwendigkeit, das Modell oder etwas anderes zu ändern.

4

Update:

Ihre erwartete SQL - während es mit reinem JPA crtieria api genereated werden kann - es wird nicht funktionieren und wird eine Ausnahme becasue Sie (Hibernate) werfen kann nicht die abstrakte Klasse Thema Instantiate. org.hibernate.InstantiationException:

von verursacht

Wenn die Klasse nicht abstrakt ist es funktioniert: Kann abstrakte Klasse oder Schnittstelle nicht instanziiert.dies wie:

CriteriaBuilder b = getEntityManager().getCriteriaBuilder(); 
CriteriaQuery<Contact> q; 
q = b.createQuery(Contact.class); 
Root r = q.from(Contact.class); 

    q.select(r); 
    q.distinct(true); 
q.where(
     b.or(
       b.like(r.get(Contact_.name),"t%"), 
       b.and(
         b.equal(r.get(Contact_.contact_type),"customer"), 
         b.like(r.get(Customer_.lastName),"t%") 
       ) 
     ) 
); 

return getEntityManager().createQuery(q).getResultList(); 

(Mein Kunde ist Ihre Person und meine Fach-Klasse ist die Kontaktklasse)


Zugriff auf die Unterscheidungs ​​Spalte als eine Nur-Lese könnte eine Arbeits Abhilfe für Sie sein. wenn discriminator_column ist contact_type tun:

@Column(name = "contact_type",insertable = false,updatable = false) 
@XmlTransient 
private String contact_type; 

Dann Kontakt eine abstrakte Klasse mit Kunden als Unterklasse sind die folgenden:

CriteriaBuilder b = getEntityManager().getCriteriaBuilder(); 
     CriteriaQuery<Contact> q = b.createQuery(Contact.class); 
      Root<Contact> r = q.from(Contact.class); 
      q.distinct(true); 

      q.where(
       b.or(
         b.like(r.get(Contact_.name), "t%"), 
         b.and(
           b.equal(r.get(Customer_.contact_type), "customer"), 
           b.like(r.get(Customer_.name), "%t") 
         ) 
       ) 
       ); 
      return getEntityManager().createQuery(q).getResultList(); 

Produziert

select 

distinct contact0_.id as id2_1_, 
    contact0_.contact_type as contact_1_1_, 
    contact0_.name as name3_1_ 
from 
    Contact contact0_ 
where 
    contact0_.name like ? 
    or contact0_.contact_type=? 
    and (
     contact0_.name like ? 
    ) 
+0

Sie greifen auf * Customer_.name * zu, was mit * Contact_.name * identisch ist. Es funktioniert nicht, wenn Sie eine Eigenschaft verwenden, die für * Customer * definiert ist und zu einem Kompilierungsfehler führt. * Die Methode get (SingularAttribute ) im Typ Pfad gilt nicht für die Argumente (SingularAttribute ) * –

+1

Siehe mein Update, wenn Sie den Root-Typ weglassen, dass das Problem nicht auftritt. – user993553

+0

Sie sind mit etwas beschäftigt, das wenig mit Metamodell Query Translation zu tun hat ... aber Ihr Update wies mich in die richtige Richtung. Obwohl es das Kopfgeld oder die Akzeptanz nicht wert ist, ist es trotzdem ein paar +1 wert. Vielen Dank. –