JPA Criteria API: gefilterte Listen mit fetch laden

rholzmueller

Mitglied
Ich arbeite aktuell mit JPA2 (Hibernate Impl. 3.6) und dem Criteria Builder von JPA2. Folgende Problemstellung habe ich:
  • Root Entity mit Liste von Child Entities (klassiche 1:n Verbindung)
  • suche nach einen Root Entities für einen speziellen Child Entity
  • das spezielle Child Entity soll gleich per fetch join gemappt werden

Für die Filterung biete die Criteria API folgendes an
Java:
    CriteriaQuery<RootEntity> qry = em.getCriteriaBuilder().createQuery(RootEntity.class);
    Root<RootEntity> rootQry = qry.from(RootEntity.class);
    Join<RootEntity,ChildEntity> join = rootQry.join(...);
    Path<String> path = join.get("column");
    Predicate pred = em.getCriteriaBuilder().equal(path, "value");
    qry.where(pred);
Dies funktioniert auch soweit.

Für das Fetchen von Objekten bietet die Criteria API folgendes an ;
Java:
    CriteriaQuery<RootEntity> qry = em.getCriteriaBuilder().createQuery(RootEntity.class);
    Root<RootEntity> rootQry = qry.from(RootEntity.class);
    Fetch<RootEntity,ChildEntity> fetch = rootQry.fetch(...);
Dies funktioniert auch soweit. Bei der fetch-Operation kann leider keine where Bedingung erstellt werden.

Jetzt habe ich das Problem, das ich beide Wege irgend wie kombinieren will. Es soll auf die ChildEntities gefiltert werden und die gefilterte Liste soll gleich per fetch dazu gemappt werden. Es soll kein zusätzlichen Datenbankzugriff für die Befüllung der Objekte erfolgen.

probierter Lösungsansatz aus dem Internet
Im Internet wurde vorgeschlagen, sowohl den Join-Operator (für die Filterung) als auch den Fetch-Operator (für das Laden der Objekte) zu verwenden. Dies ist leider inhaltlich nicht ganz richtig, da die ChildEntities zweimal in dem SQL dazu gejoint, aber nur einmal gefiltert werden. Ich bekomme zwar nur RootEntities, welche den Suchkriterien entsprechen, aber zu den RootEntity werden alle zugeordneten ChildEntities erstellt und nicht nur die ChildEntities, welche den Suchkriterien entsprechen.

mit Hibernate @Filter
mit Hibernate Mitteln habe ich das gewünschte Ergebnis hinbekommen. Dazu kann man die Filter von Hibernate verwenden. Diese Lösung gefällt mir nicht wirklich, da ich die typischere Criteria API verlassen muss und nicht mehr implementierungsunabhängig bin.

Hat jemand Ideen, wie das gewünschte Verhalte mit der Criteria API von JPA2 umzusetzen geht?
 

rholzmueller

Mitglied
Danke für den Hinweis. Ich habe ihn mir mal angeschaut. MultiSelect ist leider nicht die Lösung.

Zur Verdeutlichung, was ich erreichen will, versuche ich dies an einem kleinen Datenbeispiel.

Hier die Entity Definitionen
Java:
@Entity
class RootEntity() {
  @Id
  @Column(name="ID")
  String id;

  @OneToMany(mappedBy = "root", fetch= FetchType.LAZY)
  List<ChildEntities> childs;

  ...
}

@Entity
class ChildEntity() {
  @Id
  @Column(name="ID")
  String id;

  @JoinColumn(name = "ID", referencedColumnName = "ID")
  @ManyToOne(mappedBy = "root", fetch= FetchType.LAZY)
  RootEntity root;
 
  ...
}


Beispiel für eine Datenbasis (1:n Beziehung)
Code:
-Root1 
    -Child1
    -Child2
    -Child3
-Root2 
    -Child4
    -Child5

Aufgabenstellung
Jetzt will ich das RootEntity ermitteln, welches als ChildEntity "Child1" hat.
Dies kann ich mit der Join-Methode der Criteria API ermitteln. Das Ergebnis ist das RootEntity "Root1". Die childs in dem RootEntity sind noch nicht gefüllt (fetch Strategie Lazy, laut Entity Annotation).

Jetzt möchte ich programmtechnisch die childs per fetch join mit initialisieren. Dabei möchte ich bloß die Childs initialisieren, auf die ich auch filtere.

Das gewünschte Ergebnis wäre also
RootEntity "root1" mit List<Childs> = {ChildEntity "child1"}

Ich hoffe, ich konnte an dem Beispiel das Problem deutlicher darstellen.
 
A

Andgalf

Gast
Hmmm ..... du könntest den Fetchtype bei RootEntity.childs auf EAGER setzen. Bin mir da nicht sicher aber ich meine dann werden auch zwei Querys ausgeführt und das wolltest du ja nicht.

Ansonsten bleibt imho nur die Möglichkeit mit entsprechenden Joins zu arbeiten.
 

EasyEagle

Aktives Mitglied
Hallo rholzmueller,

bei mir ist es leider schon ne Zeit her, dass ich mit Hibernate programmiert habe.

Ich hab hier mal einen groben (eventuellen) Lösungsweg für dich zusammengebaut:
[Java]
RootEntity root1;
ChildEntity child1;

Example rootExample = Example.create(root1).excludeZeroes();
Criteria rootCriteria = getSession().createCriteria(RootEntity.getClass()).add(rootExample);

Example childExample = Example.create(child1).excludeZeroes();
Criteria childCriteria = rootCriteria.createCriteria("childs").add(childExample);

rootCriteria.setFetchMode("childs", FetchMode.JOIN);

return new List<RootEntity>((List<RootEntity>) rootCriteria.list());
[/code]

Ich weiß, "root1" und "child1" sind null und deswegen kann obiger Codeabschnitt überhaupt nicht funktionieren!
Die jeweilige Instanz müsstest du ja über deine Filterung bekommen, bzw zumindest die Attribute.
Soweit ich mich erinnern kann müssen es keine vollständigen Instanzen sein (also nicht alle Attribute befüllt), da hier ja Examples erstellt werden.

Desweiteren kann ich mich erinnern, dass es sowas gab (eventuell hilft das):

[Java]
List<ChildEntity> childList = new LinkedList<ChildEntity>();
childList.add(child1);
rootCriteria.add(Restrictions.eq("childs", childList));
...
[/code]

Aber wie gesagt, es ist bei mir schon ne Zeit her. Das ist mir halt beim Durchlesen deines Threads eingefallen.

Mir ist außerdem aufgefallen, dass du bei
Code:
RootEntity
eine
Code:
List<ChildEntities> childs;
hast. Deine Kind-Klasse heißt aber
Code:
ChildEntity
. Natürlich sind das nur exemplarische Namen, aber ist da noch eine n-m-Tabelle dazwischen?
Dann muss nämlich das Criteria auch noch über diese Tabelle gehen.

Ich hoffe ich konnte dir ein wenig helfen :)

lg

EDIT: Mir ist gerade noch aufgefallen, dass meine Zeile
Code:
 Criteria childCriteria = rootCriteria.createCriteria("childs").add(childExample);
so auch nicht funktionieren kann, weil es ja eine Liste sein müsste. :oops:
 
Zuletzt bearbeitet:

rholzmueller

Mitglied
Danke für die Antworten. Ich habe mir diese angeschaut. Hier meine Anmerkungen zu den Lösungsvorschlägen.

@Andgalf
Den FetchTyp auf EAGER setzen, bringt leider nichts. Erstens werden zwei Selects, so wie du es schon vermutet hast, abgesetzt. Damit könnte ich aber noch leben. Aber das ursprüngliche Problem wird dadurch nicht gelöst. Ich kann die Childs, welche mit FetchType EAGER annotiert sind, leider nicht mit Criteria API von JPA2 filtern.

@EasyEagle
Mir ist außerdem aufgefallen, dass du bei RootEntity eine List<ChildEntities> childs; hast. Deine Kind-Klasse heißt aber ChildEntity . Natürlich sind das nur exemplarische Namen, aber ist da noch eine n-m-Tabelle dazwischen?
Dann muss nämlich das Criteria auch noch über diese Tabelle gehen.
Sehr aufmerksam, es ist aber keine m:n Beziehung, sondern nur ein Schreibfehler. Muss natürlich
Code:
List<ChildEntity> childs;
heißen.

Weiterhin wollte ich eigentlich die neue Criteria API von JPA2 verwenden. Mit Hibernate Mittel werden Filterungen über Collection mit
Code:
@Filter
umgesetzt. Dies funktioniert auch soweit, hat aber den Nachteil, dass ich die typischere Criteria API verlassen muss und nicht mehr implementierungsunabhängig bin. Darum war meine Frage, wie dies in der Criteria API von JPA2 umgesetzt wird.


Fazit
Ich merke, dies schein ein nicht ganz einfaches Thema zu sein. Anscheinend ist dies in der Criteria API von JPA2 nicht vorgesehen. Falls jemand noch Ideen hat, so bin ich gerne offen diese zu Testen. Ich werde aber für mich das Thema erstmal innerlich abschließen und die Umsetzung mit Hibernate Mitteln (
Code:
@Filter
) realisieren.

Vielen Dank an alle für die Lösungsmöglichkeiten.
 

Ähnliche Java Themen


Oben